Dev — docker-compose.dev.yml
services:
web:
build:
context: .
dockerfile: Dockerfile
target: development # build only up to the dev stage
volumes:
- .:/app # mount source code — changes reflect instantly
environment:
DJANGO_SETTINGS_MODULE: myproject.settings.development
DEBUG: "true"
DB_HOST: db
CACHE_URL: redis://cache:6379
command: python manage.py runserver 0.0.0.0:8000
ports:
- "8000:8000"
depends_on:
db:
condition: service_healthy
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: devpassword
POSTGRES_DB: myproject_dev
POSTGRES_USER: postgres
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 5s
retries: 5
volumes:
- db-data:/var/lib/postgresql/data
ports:
- "5432:5432" # expose for TablePlus, pgAdmin, etc.
cache:
image: redis:7-alpine
volumes:
db-data:Production — docker-compose.prod.yml
services:
web:
image: mydjango:${IMAGE_TAG} # pinned tag set by CI/CD pipeline — no build: key
restart: unless-stopped
command: >
gunicorn myproject.wsgi:application
--bind 0.0.0.0:8000
--workers 4
--timeout 120
environment:
DJANGO_SETTINGS_MODULE: myproject.settings.production
DB_HOST: db
CACHE_URL: redis://cache:6379
SECRET_KEY: ${SECRET_KEY}
DATABASE_URL: ${DATABASE_URL}
ALLOWED_HOSTS: ${ALLOWED_HOSTS}
volumes:
- static-files:/app/staticfiles # shared with nginx
ports:
- "127.0.0.1:8000:8000" # loopback only — nginx proxies to this
depends_on:
db:
condition: service_healthy
deploy:
resources:
limits:
memory: 512m
cpus: "0.5"
db:
image: postgres:15
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 5s
retries: 5
volumes:
- db-data:/var/lib/postgresql/data
# No ports: — db not exposed to the host in production
deploy:
resources:
limits:
memory: 1g
cache:
image: redis:7-alpine
restart: unless-stopped
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- static-files:/app/staticfiles:ro
- ./certbot/conf:/etc/letsencrypt:ro
depends_on:
- web
volumes:
db-data:
static-files:Dev vs Prod Differences at a Glance
| Concern | Development | Production |
|---|---|---|
| Image source | build: from local Dockerfile | Pre-built image with pinned tag |
| Source code | Bind-mounted (- .:/app) | Baked into the image |
| Server | manage.py runserver (auto-reload) | gunicorn (multi-worker) |
| App port binding | 0.0.0.0:8000 (all interfaces) | 127.0.0.1:8000 (loopback only) |
| Database port | Exposed on 5432 for GUI tools | Not exposed |
| Secrets | Hardcoded dev values | Injected by CI/CD via ${VAR} |
| Restart policy | None | unless-stopped |
| Resource limits | None | cgroup limits via deploy.resources |
| Static files | Served by Django dev server | Collected to volume, served by nginx |
| nginx | Not present | Fronts the app, serves static files |
The Dockerfile Supports Both
The dev file uses build: { target: development } — it builds the image locally from the dev stage. The prod file uses a pre-built image: tag with no build: key — CI/CD built and pushed it; the server only pulls.
The same Dockerfile handles both via multi-stage builds. See dockerfile.