Development & Deployment Workflow¶
Architecture Overview¶
All environments (dev, staging, prod) run on a single server behind one shared
nginx reverse proxy (nginx-proxy). They share a PostgreSQL instance (shared_postgres)
and a Redis instance (shared_redis), isolated by database name and Redis db number.
┌─────────────────────────────────────────────────┐
│ nginx-proxy │
│ :80 (redirect) / :443 (SSL) │
└────┬──────────────┬──────────────┬─────────────┘
│ │ │
aletheia-dev. │ aletheia- │ aletheia. │
groupe-suffren.com│ staging. │ groupe- │
│ groupe- │ suffren.com │
│ suffren.com │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐
│ dev-web │ │stag-web │ │prod-web │
│ :8000 │ │ :8000 │ │ :8000 │
└─────────┘ └──────────┘ └──────────┘
│ │ │
┌────┴──────────────┴──────────────┴─────┐
│ shared_postgres / shared_redis │
└────────────────────────────────────────┘
Local development stack (separate)¶
docker compose up → localhost:8000
├── aletheia_web (Django runserver, source-mounted)
├── aletheia_db (Postgres, port 5432)
├── aletheia_redis (Redis, port 6379)
├── aletheia_celery
└── aletheia_celery_beat
Environments¶
| Local | Dev (server) | Staging (server) | Prod (server) | |
|---|---|---|---|---|
| URL | localhost:8000 |
aletheia-dev.groupe-suffren.com |
aletheia-staging.groupe-suffren.com |
aletheia.groupe-suffren.com |
| Compose files | docker-compose.yml + override.yml (auto) |
docker-compose.yml + dev.yml |
docker-compose.yml + staging.yml |
docker-compose.yml + prod.yml |
| Env file | Inline in override.yml |
/opt/docker/aletheia/envs/.env.dev |
/opt/docker/aletheia/envs/.env.staging |
/opt/docker/aletheia/envs/.env.prod |
| Django settings | development |
development |
production |
production |
| DEBUG | True |
True |
False |
False |
| Web server | runserver (auto-reload) |
gunicorn | gunicorn | gunicorn |
| Source | Mounted (live) | Baked into image | Baked into image | Baked into image |
| Database | Local aletheia_db container |
shared_postgres / aletheia_dev |
shared_postgres / aletheia_staging |
shared_postgres / aletheia_prod |
| Redis | Local aletheia_redis / db 0 |
shared_redis / db 4-5 |
shared_redis / db 2-3 |
shared_redis / db 0-1 |
| Console (disabled) | Console (disabled) | Brevo SMTP | Real SMTP | |
| Sentry | Disabled | Disabled | Enabled | Enabled |
| Auth | None | nginx basic auth | nginx basic auth | None (public) |
| Memory (web) | Unlimited | 512M | 512M | 768M |
Day-to-Day Workflow¶
1. Develop locally¶
make up # start local stack (Postgres, Redis, Django runserver)
# ... edit code — changes reload automatically ...
make test # run tests
make format # auto-format (black + isort)
make lint # check linting (black, isort, flake8, mypy)
make migrate # apply new migrations
make shell # open a bash shell in the web container
make logs # tail all container logs
2. Commit and push¶
3. Deploy to staging for validation¶
This builds the Docker image, runs migrations, collects static files, and restarts the containers. The code is baked into the image (no source mount on server).
4. Release to production¶
git tag v1.2
git push origin v1.2
make deploy ENV=prod REF=v1.2 # production requires an explicit tag
Makefile Usage¶
Every make command defaults to the local environment. Use ENV= to target a
server environment:
make ps # local containers
make ps ENV=staging # staging containers
make logs ENV=prod # tail production logs
make db-backup ENV=prod # backup production database
make migrate ENV=staging # run migrations on staging
Key commands¶
| Command | Purpose |
|---|---|
make up / make down |
Start/stop containers |
make build / make rebuild |
Build images (rebuild = no cache) |
make test / make test-coverage |
Run pytest |
make lint / make format |
Code quality |
make migrations / make migrate |
Database schema |
make shell |
Bash into web container |
make db-backup / make db-restore |
Database backup/restore |
make deploy ENV=<env> |
Deploy via deploy.sh |
make i18n-update |
Extract, translate, compile i18n |
make sync-all |
Full Doctolib sync |
Branching Model¶
Simple linear history with two branches:
develop— ongoing work, deployed to dev/stagingmain— production releases only
Tags (v1.0, v1.1, ...) mark release points. Production deploys require a tag.
deploy.sh¶
Located at the repo root. Handles all server deployments:
./deploy.sh dev # deploy develop branch to dev
./deploy.sh staging # deploy develop branch to staging
./deploy.sh staging feature # deploy a specific branch to staging
./deploy.sh prod v1.2 # deploy tag v1.2 to production (tag required)
What it does:
1. git fetch --all --tags
2. git checkout <ref> (branch or tag)
3. docker compose build (code baked into image)
4. Restores previous git checkout
5. python manage.py migrate
6. python manage.py collectstatic
7. docker compose up -d
8. nginx -s reload
Nginx Configuration¶
Nginx configs live in /opt/docker/nginx/conf.d/:
| File | Purpose |
|---|---|
aletheia-dev.conf |
Active dev config |
aletheia-dev.conf.full |
Full proxy config (template) |
aletheia-dev.conf.temp |
Placeholder 503 page |
aletheia-staging.conf |
Active staging config |
aletheia-prod.conf |
Active prod config |
Dev and staging are protected by basic auth (credentials in /opt/docker/nginx/.htpasswd).
Production has rate limiting (limit_req zone=general burst=20 nodelay).
Known Caveats¶
Dev server environment is redundant¶
Dev and staging both deploy from develop with the same architecture. Unless there is
a specific need for a second server environment (separate dataset, second tester), staging
alone covers the "test on server before prod" use case. Consider either:
- Dropping the dev environment entirely (local + staging + prod)
- Giving dev a distinct role (e.g., auto-deploy on push via CI/CD)
Dev nginx config may show a 503 placeholder¶
After initial SSL setup, the dev nginx config (aletheia-dev.conf) may still be the
placeholder that returns 503 "deployment in progress". To activate:
cp /opt/docker/nginx/conf.d/aletheia-dev.conf.full /opt/docker/nginx/conf.d/aletheia-dev.conf
docker exec nginx-proxy nginx -s reload
No CI/CD pipeline¶
Deployments are manual via deploy.sh / make deploy. There is no automated testing
or deployment on push. All quality checks (tests, linting) must be run locally before
deploying.
Single-server architecture¶
All environments share one server, one Postgres instance, and one Redis instance. Isolation is by Docker project name, database name, and Redis db number — not by infrastructure. A resource-intensive operation in one environment can affect others.
Code is baked into server images¶
On the server, source code is built into the Docker image (no volume mount). Every
code change requires a full redeploy (make deploy ENV=...). This is intentional —
it ensures reproducible deployments — but means there is no "hot reload" on server
environments.
Static files require collectstatic¶
Server environments serve static files via nginx from a shared volume. After deploying,
deploy.sh runs collectstatic automatically. If static files look stale, run: