Docker Compose Cheat Sheet
Docker Compose lets you define and run multi-container applications with a single YAML file. This reference covers everything from basic commands to production-ready patterns.
Essential CLI Commands
# Start services (detached)
docker compose up -d
# Stop services
docker compose down
# Stop and remove volumes
docker compose down -v
# View running services
docker compose ps
# Follow logs
docker compose logs -f
# Follow logs for one service
docker compose logs -f web
# Execute command in running container
docker compose exec web bash
# Run one-off command (new container)
docker compose run --rm web python manage.py migrate
# Rebuild images
docker compose build
# Pull latest images
docker compose pull
# Scale a service
docker compose up -d --scale worker=3
# Restart a single service
docker compose restart web
# View resource usage
docker compose top
docker-compose.yml Structure
version: "3.9"
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:
networks:
default:
name: myapp_network
Build Options
services:
web:
build:
context: . # Build context path
dockerfile: Dockerfile.prod # Custom Dockerfile
args:
NODE_ENV: production # Build-time args
target: production # Multi-stage build target
cache_from:
- myregistry/myapp:latest
Volumes Reference
services:
web:
volumes:
# Named volume (managed by Docker)
- mydata:/app/data
# Bind mount (host path)
- ./src:/app/src
# Read-only bind mount
- ./config:/app/config:ro
# Anonymous volume
- /app/node_modules
volumes:
mydata:
driver: local
# External volume (pre-existing)
external_db:
external: true
Networking
services:
frontend:
networks:
- frontend_net
backend:
networks:
- frontend_net
- backend_net
db:
networks:
- backend_net
networks:
frontend_net:
driver: bridge
backend_net:
driver: bridge
internal: true # No external access
Services on the same network can reach each other by service name as hostname. frontend can call http://backend:8080 — Docker's embedded DNS resolves it.
Environment Variables
services:
web:
# Inline values
environment:
NODE_ENV: production
PORT: 3000
# From host environment
environment:
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
# From .env file (default: .env in same dir as compose file)
env_file:
- .env
- .env.local
# .env file
DATABASE_URL=postgres://localhost/mydb
SECRET_KEY=changeme
Variable substitution in compose file:
services:
web:
image: myapp:${APP_VERSION:-latest} # Default to 'latest' if unset
ports:
- "${HOST_PORT:-3000}:3000"
Health Checks
services:
api:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s # How often to run
timeout: 10s # Max time for one check
retries: 3 # Failures before 'unhealthy'
start_period: 40s # Grace period after start
Other test formats:
# Shell form
test: ["CMD-SHELL", "wget -qO- http://localhost/health || exit 1"]
# Disable inherited healthcheck
healthcheck:
disable: true
Restart Policies
| Policy | Behavior |
|---|---|
no |
Never restart (default) |
always |
Always restart |
on-failure |
Restart only on non-zero exit |
unless-stopped |
Restart unless manually stopped |
Use unless-stopped for production services, on-failure for batch jobs.
Resource Limits
services:
web:
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
Note: deploy.resources requires Compose v3+ and works with docker compose (not the old docker-compose).
Profiles (Conditional Services)
services:
web:
image: myapp
debug-tools:
image: nicolaka/netshoot
profiles: ["debug"]
db-admin:
image: adminer
profiles: ["debug", "admin"]
# Start web only
docker compose up -d
# Start web + debug tools
docker compose --profile debug up -d
Multiple Compose Files (Override Pattern)
# Base config
docker compose -f docker-compose.yml up
# Base + override (merged)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up
# docker-compose.prod.yml — overrides development defaults
services:
web:
build:
target: production
environment:
NODE_ENV: production
restart: always
Common Patterns
Wait for database before starting app:
services:
app:
depends_on:
db:
condition: service_healthy # Waits for healthcheck to pass
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 5s
retries: 10
Hot-reload development setup:
services:
web:
build: .
command: npm run dev
volumes:
- .:/app
- /app/node_modules # Preserve container's node_modules
ports:
- "3000:3000"
Multi-stage: dev vs prod:
# docker-compose.yml (dev)
services:
web:
build:
target: development
volumes:
- .:/app
command: npm run dev
# docker-compose.prod.yml
services:
web:
build:
target: production
command: node dist/server.js
restart: always
→ Convert a docker run command to a Compose file instantly with the Docker Run → Compose Converter.