Developer onboarding guide¶
Welcome. This guide gets a new dev from a fresh clone to a running app, the CMS open in a browser, and a first PR. The backend is a single Django + Wagtail service; the Architecture doc covers the bigger picture.
Active build. The repo has content models (Passage, Book, and Writing, with letters/essays/chapters unified behind one
kindfield), plus taxonomy, a custom User model, Firebase auth, and infrastructure (Docker, AWS CDK). Mobile API endpoints are landing incrementally. API framework is DRF (ADR-0004), auth is Firebase (ADR-0003).
Prerequisites¶
Install these before starting:
| Tool | Install (macOS) | Install (Linux) | Purpose |
|---|---|---|---|
| uv | brew install uv |
curl -LsSf https://astral.sh/uv/install.sh \| sh |
Python deps + venv + Python toolchain (installs 3.12+ for you) |
| Docker + Compose | Docker Desktop | your distro's docker + docker-compose-plugin |
Local Postgres 18 + Redis 8 |
| git | preinstalled / brew install git |
apt install git |
Version control |
Python itself is not a prerequisite. uv provisions the interpreter pinned by
requires-python (>=3.12). Node/AWS CDK are only needed for infrastructure work
(see deployment.md), not day-to-day app development.
Verify:
Quick start (10 minutes)¶
# 1. Clone the repo
git clone git@github.com:Fueled/cs-lewis-backend.git
cd cs-lewis-backend
# 2. First-time setup: .env + deps + migrate + seed
make bootstrap
# 3. Start the server
make dev # http://localhost:8000
# 4. Verify it's alive
curl localhost:8000/-/alive/ # liveness (bare 200)
curl localhost:8000/-/health/ # DB + cache checks
make bootstrap runs make env install hooks migrate seed. It creates .env from the
example, installs deps into a project-local .venv/, installs git hooks (lefthook),
applies migrations, and seeds the database (sample dev users). Every step is
idempotent, so re-running is safe.
Log in to the CMS:
| User | Password | Role |
|---|---|---|
admin@cslewis.local |
admin123 |
Superuser |
editor@cslewis.local |
editor123 |
Staff (no superuser) |
- Wagtail CMS → http://localhost:8000/studio/
- Django admin → http://localhost:8000/backstage/
- API docs (Swagger) → http://localhost:8000/api/docs/
With no DATABASE_URL / REDIS_URL set, the app falls back to sqlite + local-memory
cache, so no services are needed. For Postgres/Redis parity with prod, use Docker Compose:
Compose runs migrate on start and serves via granian (the production server).
The admin lives at
/studio/and/backstage/, not/cms/and/admin/. Those prefixes are env-configurable (WAGTAIL_ADMIN_URL,DJANGO_ADMIN_URL) so the login pages aren't the routes bots scan. On the deployed dev environment these are https://dev-cslewis.fueled.engineering/studio/ (Wagtail CMS) and https://dev-cslewis.fueled.engineering/backstage/ (Django admin). See API Overview → Environments.
Environment variables¶
Local values live in .env (copied from .env.example); nothing sensitive is
committed. For the full variable list, defaults, and Firebase setup, see
Environment variables in the reference docs.
Services¶
Local services provisioned by compose.yml:
| Service | Port | Image | Purpose |
|---|---|---|---|
| web | 8000 | built from .aws/docker/Dockerfile-aws |
Django + Wagtail via granian |
| db | 5432 | postgres:18 |
Application database |
| redis | 6379 | redis:8-alpine |
Cache + (future) Celery broker |
Project architecture¶
A single Python service (Django + Wagtail) serves the editorial CMS and, once built, the mobile API from one codebase. It runs on AWS ECS (EC2), backed by Aurora Serverless v2 (Postgres), ElastiCache Redis, and S3/CloudFront. See Architecture, ADR-0001 (app stack) and ADR-0002 (IaC + compute).
Key rules¶
- Config comes from the environment (12-factor). No secrets in code; real secrets live in AWS Secrets Manager.
- Settings are layered: shared config in
config/settings/base.py, overridden bylocal.py(local dev) /prod.py(all AWS envs, dev and prod) /test.py. Add new settings tobase.pyunless they're env-specific. - API framework is DRF; auth is Firebase. Follow the application style in ADR-0004: function-based views, thin serializers, business logic in services.
- Migrations run as a one-off task in AWS, not on container start. Don't add
migrateto the imageCMD. (Compose does it locally because it's single-instance.)
Directory map¶
| Path | Purpose |
|---|---|
src/ |
Django project root (manage.py lives here, run commands from src/) |
src/data/ |
Single Django app: all models + migrations |
src/readers/ |
Read-only business logic (plain Python) |
src/actions/ |
State-changing business logic (plain Python) |
src/interfaces/api/ |
DRF views + URLs |
src/interfaces/cms/ |
Django admin + Wagtail admin (forms, viewsets, templates) |
src/interfaces/cli/ |
Management commands (seed, seed_tags, seed_related, seed_writings, sync_themes, import_book, import_articles) |
src/tests/ |
All tests |
src/config/settings/ |
Layered settings: base, local, prod, test |
src/config/urls.py |
Root URL config (admin prefixes from settings) |
.aws/iac/ |
AWS CDK (TypeScript), shared/network/compute/storage stacks |
.aws/docker/ |
Dockerfile-aws (app image), Dockerfile-seed |
.aws/codebuild/ |
Buildspecs + ECS task-def template |
.github/workflows/ |
CI/CD (deploy to dev/prod, docs) |
docs/ |
Reader-facing docs (this site) |
.context/ |
Internal working memory / decision wiki |
compose.yml |
Local Postgres + Redis + web |
Common commands¶
make help lists all targets. Most common:
| Command | Does |
|---|---|
make bootstrap |
First-time setup: .env + deps + git hooks + migrate + seed (sample dev users) |
make hooks |
(Re-)install git hooks (lefthook), included in bootstrap |
make dev |
Run the dev server |
make migrate / make makemigrations |
Database migrations |
make seed |
Seed sample dev users (idempotent) |
uv run python src/manage.py seed_writings [--publish] |
Seed sample Writings (letters/essays/chapters); --publish makes them live + What's New–eligible |
uv run python src/manage.py import_book --book … --excerpts … |
Import a processed book → Book + draft Passages |
make createsuperuser |
Create an admin user (prompts for email) |
make shell |
Django shell |
make test |
Run the test suite (pytest) |
make lint / make format |
Ruff lint / auto-format |
make check |
Django system checks |
make services / make services-down |
Start/stop local Postgres + Redis (Docker) |
make docs-serve |
Preview docs site (hot-reload) |
make docs-check |
Strict docs build, fails on broken links/nav |
For lower-level or one-off commands:
uv add <package> # add a runtime dep (updates pyproject + lock)
uv run python src/manage.py shell_plus # django-extensions enhanced shell
docker compose up --build # full stack: web + Postgres + Redis
docker compose down # add -v to wipe the pgdata volume
cd .aws/iac && just <recipe> # infrastructure, see docs/guides/deployment.md
Add a feature¶
The codebase follows Django RAPID Architecture.
It uses horizontal layers, not vertical apps. Read the django skill in
.agents/skills/django/SKILL.md for the full convention. The shape:
- Models go in
src/data/models/(single Django app, nostartapp). - Read-only business logic goes in
src/readers/. - State-changing logic goes in
src/actions/. - API views go in
src/interfaces/api/. - Admin customization (Django + Wagtail) goes in
src/interfaces/cms/. - Management commands go in
src/interfaces/cli/management/commands/. - Content types are Wagtail Snippets, not Pages. The CMS is headless, so the
mobile app reads content through the API, never Wagtail page routing
(ADR-0008). Build new content models as
snippets with the feature mixins (draft/live, revisions) like
Passage/Writing/Book; runmakemigrations+migrate. - The mobile API uses DRF with function-based views and
drf-spectacularfor OpenAPI (see ADR-0004). - Keep swappable services (e.g. the tag
Ranker) behind an interface so the Postgres → Meilisearch swap stays mechanical.
CI/CD pipeline¶
GitHub Actions → OIDC role → CodeBuild → ECR → ECS. PRs get both a lint gate and a
test gate (ci.yml, two parallel jobs).
| Trigger | Workflow | Effect |
|---|---|---|
| Pull request (any branch) | ci.yml → lint |
lefthook run pre-commit --all-files: ruff check/format, docs strict-build, IaC tsc --noEmit. Fails if anything would need fixing |
| Pull request (any branch) | ci.yml → test |
manage.py check + migrations check + pytest against a real Postgres service container |
Push to main (touching src/, .aws/, pyproject.toml) |
dev-aws-backend.yml |
Deploy to dev (dev-cslewis.fueled.engineering) |
Push a v*.*.* tag |
prod-aws-deploy.yml |
Deploy to prod |
Push to main (touching docs/, zensical.toml) |
docs.yml |
Rebuild the docs site |
Git workflow¶
- Branch from
main - Conventional commits:
feat:,fix:,chore:,refactor:,docs:(lowercase scope, imperative subject ≤72 chars, no trailing period) - Open a PR against
main; follow.github/PULL_REQUEST_TEMPLATE.md - No
Co-Authored-Byor AI watermarks in commits - Git hooks (lefthook, installed by
make bootstrap/make hooks) run ruff check+format on stagedsrc/**/*.pyat commit time and autofix/restage. The same checks re-run in CI (ci.yml→lint) on the full repo. Seelefthook.ymlat the project root
Testing¶
Tests use pytest + pytest-django. Locally, config/settings/test.py defaults
to an in-memory sqlite database, so there is zero setup. CI sets DATABASE_URL to a real
Postgres service container instead (ci.yml → test), since sqlite doesn't
exercise Postgres-specific behaviour (GIN indexes, etc.). To match CI locally:
make test # or: uv run pytest, sqlite, zero setup
# against real Postgres (mirrors CI):
make services # starts the compose.yml db
DATABASE_URL=postgres://cslewis:cslewis@localhost:5432/cslewis uv run pytest
Tests live in src/tests/, not inside each layer.
Common gotchas¶
- Run
manage.pyfromsrc/. That's where it lives, and it putsconfigon the path. - Zero-setup local uses sqlite + locmem. Fine for
runserver, but usedocker compose upwhen you need Postgres/Redis behaviour (GIN indexes, real cache).
The admin URLs, the DRF + Firebase stack, and the AWS migration task are covered above in Quick start and Key rules.
Onboarding checklist¶
- Complete the Quick start steps (app running, health check passing)
- Log into the CMS at
/studio/with a superuser - Read API Overview and Architecture
- Read
AGENT_GUIDE.mdat the project root - Run the linter (
uv run ruff check src) and verify it passes - Pick up your first ticket and open a PR
- Ask your onboarding buddy if anything is unclear
Contacts¶
- Team Lead: Saurabh Kumar
- Slack:
#cs-lewis(general),#cs-lewis-engineering(engineering) - Tracker: Linear, team CSL