Introduction
Docker Compose is excellent for development, but running it in production requires careful configuration around restart policies, health checks, logging, and security. This guide covers a production-hardened Compose setup.
Project Structure
A production Compose project should follow this structure: ``` myproject/ ├── docker-compose.yml ├── .env # Environment variables ├── nginx/ │ └── default.conf ├── app/ │ ├── Dockerfile │ └── ... ├── db/ │ └── init.sql └── monitoring/ └── prometheus.yml ```
Production docker-compose.yml
Here's a complete production Compose file with best practices: ```yaml version: '3.8' services: app: build: ./app restart: unless-stopped env_file: .env expose: - "3000" depends_on: db: condition: service_healthy redis: condition: service_started healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s logging: driver: "json-file" options: max-size: "10m" max-file: "3" networks: - frontend - backend nginx: image: nginx:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro - ./certbot/www:/var/www/certbot:ro - ./certbot/conf:/etc/letsencrypt:ro depends_on: - app networks: - frontend db: image: postgres:16-alpine restart: unless-stopped env_file: .env volumes: - postgres_data:/var/lib/postgresql/data - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 networks: - backend redis: image: redis:7-alpine restart: unless-stopped command: redis-server --requirepass $${REDIS_PASSWORD} volumes: - redis_data:/data networks: - backend networks: frontend: driver: bridge backend: driver: bridge internal: true # No external access volumes: postgres_data: driver: local redis_data: driver: local ```
Environment Variables (.env)
Keep secrets out of Compose files:
```bash
# .env file — add to .gitignore!
POSTGRES_USER=myapp
POSTGRES_PASSWORD=
Zero-Downtime Deployments
For seamless updates, use a reverse proxy with blue-green deployment pattern: ```bash #!/bin/bash # deploy.sh - Blue-green deployment GREEN_COMPOSE=docker-compose.green.yml BLUE_COMPOSE=docker-compose.blue.yml # Deploy to green GREEN_UP=$(docker compose -f $GREEN_COMPOSE ps -q) if [ -n "$GREEN_UP" ]; then ACTIVE=$GREEN_COMPOSE STANDBY=$BLUE_COMPOSE else ACTIVE=$BLUE_COMPOSE STANDBY=$GREEN_COMPOSE fi docker compose -f $STANDBY up -d --build sleep 30 # Health check window docker compose -f $ACTIVE down docker compose -f $STANDBY up -d # Full switch ```
Logging & Monitoring
Centralized logging with the ELK stack or Loki: ```yaml # Add to docker-compose.yml prometheus: image: prom/prometheus volumes: - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml - prometheus_data:/prometheus networks: - backend grafana: image: grafana/grafana ports: - "3001:3000" volumes: - grafana_data:/var/lib/grafana depends_on: - prometheus networks: - frontend ```
Resource Limits
Prevent containers from consuming all server resources: ```yaml services: app: deploy: resources: limits: cpus: '0.50' memory: 512M reservations: cpus: '0.25' memory: 256M ```
Backup Strategy
Automated database backups: ```bash #!/bin/bash # backup.sh — run via cron TIMESTAMP=$(date +%Y%m%d_%H%M%S) docker exec $(docker compose ps -q db) pg_dump -U $POSTGRES_USER $POSTGRES_DB \ | gzip > /backups/db_$TIMESTAMP.sql.gz # Keep last 30 days find /backups -name "*.sql.gz" -mtime +30 -delete ```
Conclusion
Docker Compose is production-ready when configured correctly. The key practices: always use health checks, separate networks, configure restart policies, implement logging, and automate backups. Start with the template above and adapt it to your stack.
Published on June 7, 2026 · Filed under DevOps
← Back to Blog