Docker Compose (Advanced)
Database Services
PostgreSQL
yaml
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME:-myapp}
# Performance tuning
POSTGRES_SHARED_BUFFERS: 256MB
POSTGRES_EFFECTIVE_CACHE_SIZE: 1GB
volumes:
# Data persistence
- postgres_data:/var/lib/postgresql/data
# Initialization scripts
- ./db/init:/docker-entrypoint-initdb.d:ro
# Backups
- ./backups:/backups
# SECURITY: Only expose in development, not production
# ports:
# - "${DB_PORT:-5432}:5432"
networks:
- app_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '1'
memory: 1G
volumes:
postgres_data:
driver: local
MySQL
yaml
services:
db:
image: mysql:8.0
restart: unless-stopped
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_NAME:-myapp}
MYSQL_USER: ${DB_USER:-myapp}
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
- ./db/init:/docker-entrypoint-initdb.d:ro
networks:
- app_network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
volumes:
mysql_data:
driver: local
MongoDB
yaml
services:
mongodb:
image: mongo:7
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER:-admin}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
MONGO_INITDB_DATABASE: ${MONGO_DB:-myapp}
volumes:
- mongodb_data:/data/db
- mongodb_config:/data/configdb
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
networks:
- app_network
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet
interval: 10s
timeout: 10s
retries: 5
volumes:
mongodb_data:
driver: local
mongodb_config:
driver: local
Cache and Message Queue Services
Redis
yaml
services:
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
- ./redis.conf:/usr/local/etc/redis/redis.conf:ro
networks:
- app_network
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
memory: 512M
volumes:
redis_data:
driver: local
RabbitMQ
yaml
services:
rabbitmq:
image: rabbitmq:3-management-alpine
restart: unless-stopped
environment:
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-admin}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_VHOST:-/}
volumes:
- rabbitmq_data:/var/lib/rabbitmq
ports:
- "5672:5672" # AMQP
- "15672:15672" # Management UI
networks:
- app_network
healthcheck:
test: rabbitmq-diagnostics -q ping
interval: 30s
timeout: 10s
retries: 5
volumes:
rabbitmq_data:
driver: local
Reverse Proxy
Nginx
yaml
services:
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
# Configuration
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
# Static files
- ./public:/usr/share/nginx/html:ro
# SSL certificates
- ./certs:/etc/nginx/certs:ro
# Logs
- nginx_logs:/var/log/nginx
depends_on:
- app
networks:
- app_network
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
nginx_logs:
driver: local
Traefik
yaml
services:
traefik:
image: traefik:v2.10
restart: unless-stopped
command:
- "--api.insecure=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- app_network
app:
build: .
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
networks:
- app_network
Development vs Production
Development Configuration
yaml
# compose.dev.yml
services:
app:
build:
context: .
dockerfile: Dockerfile
target: development
volumes:
# Mount source code for hot reload
- ./src:/app/src
- ./public:/app/public
# Don't mount node_modules (use container's version)
- /app/node_modules
ports:
# Expose debugger port
- "9229:9229"
environment:
- NODE_ENV=development
- DEBUG=app:*
command: npm run dev
db:
ports:
# Expose database port for local access in development
- "5432:5432"
# Usage: docker compose -f compose.yml -f compose.dev.yml up
Production Configuration
yaml
# compose.prod.yml
services:
app:
build:
target: production
restart: always
# Don't expose ports directly in production - use reverse proxy
expose:
- "3000"
environment:
- NODE_ENV=production
deploy:
replicas: 3
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
db:
# Don't expose database port in production
expose:
- "5432"
deploy:
resources:
limits:
cpus: '2'
memory: 2G
# Usage: docker compose -f compose.yml -f compose.prod.yml up -d
Environment Variables
.env File
bash
# .env NODE_ENV=production APP_PORT=3000 # Database DB_USER=postgres DB_PASSWORD=secure_password_here DB_NAME=myapp DB_PORT=5432 # Redis REDIS_PASSWORD=redis_password_here # Application JWT_SECRET=your_jwt_secret_here API_KEY=your_api_key_here # Version VERSION=1.0.0
Using Environment Variables
yaml
services:
app:
image: ${APP_NAME:-myapp}:${VERSION:-latest}
ports:
- "${APP_PORT:-3000}:3000"
environment:
# Use variables from .env
- DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:${DB_PORT}/${DB_NAME}
- API_KEY=${API_KEY}
env_file:
# Load all variables from .env
- .env
Full Stack Application Example
yaml
services:
# Frontend
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
target: production
image: myapp-frontend:latest
restart: unless-stopped
depends_on:
- backend
networks:
- app_network
labels:
- "traefik.enable=true"
- "traefik.http.routers.frontend.rule=Host(`myapp.com`)"
# Backend API
backend:
build:
context: ./backend
dockerfile: Dockerfile
image: myapp-backend:latest
restart: unless-stopped
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- uploads:/app/uploads
networks:
- app_network
labels:
- "traefik.enable=true"
- "traefik.http.routers.backend.rule=Host(`api.myapp.com`)"
# Background Worker
worker:
build:
context: ./backend
dockerfile: Dockerfile
command: npm run worker
restart: unless-stopped
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
networks:
- app_network
# Database
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Cache
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- app_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Reverse Proxy
traefik:
image: traefik:v2.10
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- app_network
volumes:
postgres_data:
redis_data:
uploads:
networks:
app_network:
driver: bridge
Best Practices
✅ DO
- •Remove version field - It's obsolete in Compose v2
- •Use health checks - Essential for orchestration
- •Use named volumes - Data persistence
- •Set resource limits - Prevent resource exhaustion
- •Use restart policies - Automatic recovery
- •Isolate with networks - Security
- •Pin image versions - Use specific tags, not :latest
- •Use .env files - Centralized configuration
- •Separate dev/prod configs - Different requirements
- •Use depends_on conditions - Proper startup order
❌ DON'T
- •Include version field - Generates warnings in Compose v2
- •Use :latest in production - Unpredictable
- •Expose database ports - Security risk in production
- •Hardcode secrets - Use environment variables
- •Use privileged mode - Security risk
- •Mount sensitive host directories - Security risk
- •Forget resource limits - Can exhaust host
- •Use bridge network for everything - Proper isolation matters
Common Commands
bash
# Start services docker compose up -d # Start with specific file docker compose -f compose.yml -f compose.prod.yml up -d # View logs docker compose logs -f app # Restart service docker compose restart app # Stop services docker compose down # Stop and remove volumes (CAUTION: deletes data) docker compose down -v # Build and start docker compose up --build # Scale service docker compose up -d --scale worker=3 # Execute command in running container docker compose exec app sh # Run one-off command docker compose run --rm app npm test # View service status docker compose ps # View resource usage docker compose stats
References and Sources
This guide is based on official Docker Compose documentation:
- •Docker Compose Specification
- •Docker Compose Services Reference
- •Docker Compose Health Checks
- •Docker Compose depends_on
- •Docker Compose Legacy Versions
Last updated: December 2025