Docker Compose lets you define and run multi-container applications with a single YAML file. This cheat sheet covers everything from basic service definitions to production-ready stack configurations.
File Structure
A docker-compose.yml file consists of four main sections:
# docker-compose.yml
services: # Container definitions
web:
image: nginx:alpine
api:
build: ./api
volumes: # Persistent storage
db-data:
networks: # Custom networks
frontend:
backend:Service Configuration
Image vs Build
services:
# Use a pre-built image
db:
image: postgres:16
# Build from Dockerfile
api:
build: ./api
# Build with custom options
web:
build:
context: ./frontend
dockerfile: Dockerfile.prod
args:
NODE_ENV: productionPort Mapping
services:
web:
ports:
- "80:80" # host:container
- "443:443"
- "3000:3000" # Expose to localhost
- "127.0.0.1:8080:80" # Bind to specific interfaceEnvironment Variables
services:
api:
# Inline environment variables
environment:
- NODE_ENV=production
- DB_HOST=db
- DB_PORT=5432
# From external file
env_file:
- .env
- .env.productionRestart Policies
services:
web:
restart: "no" # Default: never restart
api:
restart: always # Always restart
db:
restart: unless-stopped # Restart unless manually stopped
worker:
restart: on-failure # Restart only on failure
# restart: on-failure:5 # Max 5 retriesHealth Checks
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30sVolumes
services:
db:
volumes:
# Bind mount (host path : container path)
- ./data:/var/lib/postgresql/data
# Named volume
- db-data:/var/lib/postgresql/data
# Read-only mount
- ./config:/etc/app/config:ro
volumes:
db-data: # Named volume declaration
driver: localNetworks
services:
web:
networks:
- frontend
api:
networks:
- frontend
- backend
db:
networks:
- backend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # No external accessComplete Stack Examples
Node.js + MongoDB + Redis
services:
app:
build: .
ports:
- "3000:3000"
environment:
- MONGO_URL=mongodb://mongo:27017/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- mongo
- redis
mongo:
image: mongo:7
volumes:
- mongo-data:/data/db
restart: unless-stopped
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
mongo-data:Django + PostgreSQL
services:
web:
build: .
command: gunicorn myapp.wsgi:application --bind 0.0.0.0:8000
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgres://postgres:secret@db:5432/myapp
depends_on:
db:
condition: service_healthy
volumes:
- static:/app/static
db:
image: postgres:16
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: secret
volumes:
- pg-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 5s
timeout: 5s
retries: 5
volumes:
pg-data:
static:WordPress + MySQL + phpMyAdmin
services:
wordpress:
image: wordpress:latest
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wp_user
WORDPRESS_DB_PASSWORD: wp_pass
WORDPRESS_DB_NAME: wordpress
volumes:
- wp-content:/var/www/html
depends_on:
- db
db:
image: mysql:8
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wp_user
MYSQL_PASSWORD: wp_pass
MYSQL_ROOT_PASSWORD: root_secret
volumes:
- db-data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin:latest
ports:
- "8081:80"
environment:
PMA_HOST: db
volumes:
wp-content:
db-data:Essential CLI Commands
| Command | Description |
|---|---|
| docker compose up -d | Start services in background |
| docker compose down | Stop and remove containers |
| docker compose down -v | Stop and remove containers + volumes |
| docker compose logs -f | Follow service logs |
| docker compose logs api | Logs for specific service |
| docker compose ps | List running containers |
| docker compose exec api sh | Shell into running container |
| docker compose build | Build/rebuild images |
| docker compose pull | Pull latest images |
| docker compose restart api | Restart specific service |
| docker compose config | Validate and view config |
| docker compose top | Show running processes |
Environment Files (.env)
Docker Compose automatically reads a .env file in the same directory:
# .env file
POSTGRES_VERSION=16
POSTGRES_PASSWORD=my_secret_pass
APP_PORT=3000
# docker-compose.yml
services:
db:
image: postgres:${POSTGRES_VERSION}
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
app:
ports:
- "${APP_PORT}:3000"Common Mistakes
| Mistake | Fix |
|---|---|
| Using `latest` tag in production | Pin specific version tags (e.g., `postgres:16.1`) |
| Storing secrets in docker-compose.yml | Use .env files or Docker secrets |
| Not using named volumes for data | Named volumes persist across `docker compose down` |
| Missing restart policy | Add `restart: unless-stopped` for production services |
| Not setting resource limits | Use `deploy.resources.limits` for memory/CPU |
FAQ
What is the difference between docker-compose and docker compose?
docker-compose (with hyphen) is the legacy Python-based tool (V1). docker compose (space, no hyphen) is the new Go-based plugin (V2) integrated into Docker CLI. Docker Compose V2 is now the default and recommended version.
How do I persist data in Docker Compose?
Use named volumes for database data and other persistent storage. Named volumes survive docker compose down. Bind mounts map host directories directly and are useful for development.
How do containers communicate in Docker Compose?
All services in a docker-compose.yml share a default network. Services can reach each other using the service name as hostname (e.g., `db:5432` from the app service to the database service).
Can I use Docker Compose in production?
Yes, but for larger deployments consider Docker Swarm or Kubernetes. Docker Compose is great for single-host deployments, development environments, and CI/CD pipelines.