docker compose cheatsheet.
Basic compose.yml
services:
web:
build: .
ports: ["8000:8000"]
environment:
DATABASE_URL: postgres://postgres:x@db:5432/myapp
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: x
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 5
volumes:
postgres_data:
Commands
docker compose up # foreground
docker compose up -d # detached
docker compose up --build # rebuild
docker compose up web # specific service
docker compose down # stop + remove
docker compose down -v # also remove volumes (DANGER)
docker compose logs -f web
docker compose exec web bash
docker compose ps
docker compose restart web
docker compose pull
docker compose config # validate + render
build
services:
web:
build:
context: .
dockerfile: Dockerfile.dev
args:
VERSION: "1.0"
target: dev # multi-stage stop
environment
services:
web:
environment:
- DATABASE_URL=postgres://...
- DEBUG=1
env_file: .env
env_file:
- .env
- .env.local
ports
ports:
- "8000:8000" # host:container
- "127.0.0.1:8000:8000" # bind specific
- "8000-8010:8000-8010" # range
volumes
services:
web:
volumes:
- .:/app # bind mount
- /app/node_modules # anonymous (preserve from image)
- data:/var/lib/data # named volume
volumes:
data:
data2:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.10
device: ":/path"
networks
services:
web:
networks: [backend, frontend]
db:
networks: [backend]
proxy:
networks: [frontend]
networks:
backend:
frontend:
Services on same network resolve each other by service name. By default, all in one network.
depends_on
services:
web:
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
Conditions: service_started, service_healthy, service_completed_successfully.
restart
services:
web:
restart: unless-stopped
Options: no, always, on-failure, unless-stopped.
healthcheck
services:
web:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
resource limits
services:
web:
deploy:
resources:
limits:
cpus: "1"
memory: 512M
reservations:
memory: 256M
profiles (optional services)
services:
web:
image: web:latest
worker:
image: worker:latest
profiles: ["full"]
monitoring:
image: prom/prometheus
profiles: ["monitoring"]
docker compose up # just web
docker compose --profile full up # web + worker
docker compose --profile full --profile monitoring up
override files
# compose.yml + compose.override.yml auto-merged
docker compose up
# Specific files
docker compose -f compose.yml -f compose.prod.yml up
# compose.override.yml (dev defaults)
services:
web:
volumes: [".:/app"] # hot-reload code
environment:
DEBUG: "1"
scale
docker compose up --scale web=3
services:
web:
deploy:
replicas: 3
exec / run
docker compose exec web bash # in running container
docker compose run --rm web pytest # new ephemeral container
docker compose run --rm web sh -c "python manage.py migrate"
ENV substitution
services:
web:
image: myapp:${TAG:-latest}
ports:
- "${PORT:-8000}:8000"
TAG=v1.0 PORT=9000 docker compose up
# Or in .env (auto-loaded)
extends (reuse)
services:
base:
image: python:3.13
environment:
TZ: UTC
web:
extends:
service: base
command: gunicorn config.wsgi
logging
services:
web:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
secrets (Swarm or Compose)
services:
web:
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txt
In container: /run/secrets/db_password.
Common patterns
Web + worker + db
services:
web:
build: .
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
depends_on: [db, redis]
worker:
build: .
command: celery -A config worker -l info
depends_on: [db, redis]
beat:
build: .
command: celery -A config beat -l info
depends_on: [db, redis]
db:
image: postgres:16
redis:
image: redis:7-alpine
Reverse proxy
services:
proxy:
image: nginx:alpine
ports: ["80:80", "443:443"]
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./certs:/etc/nginx/certs
depends_on: [web]
Common mistakes
depends_onwithoutcondition: service_healthy→ app starts before DB.- Sharing volumes across services without coordination.
- Hard-coding ports for multi-instance → conflict.
- Compose v1 (
docker-compose) syntax vs v2 (docker compose) — minor differences. - Forgetting
--buildafter Dockerfile change.
Read this next
If you want my compose templates (web/worker/db/redis/nginx), they’re at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .