Docker Compose for Production
While Kubernetes handles large-scale deployments, Docker Compose is excellent for small to medium production workloads. This guide covers production-grade configurations.
Production-Ready docker-compose.yml
services:
api:
image: myapp/api:${TAG:-latest}
restart: unless-stopped
# Resource limits
deploy:
resources:
limits:
cpus: '2'
memory: 512M
reservations:
memory: 256M
replicas: 2
# Health check
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
environment:
NODE_ENV: production
PORT: 3000
# Secrets (not in environment!)
secrets:
- db_password
- jwt_secret
networks:
- frontend
- backend
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
postgres:
image: postgres:15-alpine
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myapp
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
secrets:
- db_password
networks:
- backend
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes --requirepass-file /run/secrets/redis_password
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
volumes:
- redis_data:/data
secrets:
- redis_password
networks:
- backend
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
networks:
- frontend
depends_on:
- api
secrets:
db_password:
file: ./secrets/db_password.txt
jwt_secret:
file: ./secrets/jwt_secret.txt
redis_password:
file: ./secrets/redis_password.txt
volumes:
postgres_data:
redis_data:
networks:
frontend:
backend:
internal: true # Not accessible from outside
Reading Secrets in Application
// Node.js: read Docker secrets from /run/secrets/
const fs = require('fs');
function getSecret(name) {
const secretPath = `/run/secrets/${name}`;
try {
return fs.readFileSync(secretPath, 'utf8').trim();
} catch {
// Fallback to environment variable for local dev
return process.env[name.toUpperCase()];
}
}
const config = {
dbPassword: getSecret('db_password'),
jwtSecret: getSecret('jwt_secret'),
};
Rolling Updates
# Pull new image
docker compose pull api
# Rolling restart (update one replica at a time)
docker compose up -d --no-deps --scale api=2 api
# Or use update order
docker compose up -d api
# Compose will stop old container after new one is healthy
Backup and Restore
# Backup PostgreSQL
docker compose exec postgres pg_dump -U myapp myapp | gzip > backup-$(date +%Y%m%d).sql.gz
# Restore
gunzip -c backup-20240101.sql.gz | docker compose exec -T postgres psql -U myapp myapp
# Backup volumes
docker run --rm -v myapp_postgres_data:/data -v $(pwd)/backups:/backup alpine tar czf /backup/postgres-$(date +%Y%m%d).tar.gz /data
Environment-Specific Overrides
# docker-compose.override.yml (auto-loaded in development)
services:
api:
build: . # Build from source in dev
volumes:
- .:/app # Hot reload
environment:
NODE_ENV: development
ports:
- "9229:9229" # Debug port
postgres:
ports:
- "5432:5432" # Expose in dev
# Production: explicit file selection
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Development: uses override automatically
docker compose up -d
Monitoring with Compose
# Add Prometheus + Grafana
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
networks:
- backend
grafana:
image: grafana/grafana
ports:
- "3001:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD_FILE: /run/secrets/grafana_password
secrets:
- grafana_password
networks:
- backend
Summary
Production Docker Compose patterns:
- Always use health checks for dependency ordering
- Store secrets as files, not environment variables
- Use internal networks to isolate backend services
- Set resource limits to prevent container starvation
- Use environment-specific override files
- Automate backups for stateful services