Production-Ready Docker Compose Best Practices
Production-ready Docker Compose best practices are the guidelines used to run multi-container applications securely, reliably, consistently, and maintainably in real environments.
Docker Compose is commonly used for local development, staging, testing, small production deployments, monitoring stacks, and single-server microservices platforms. When used carefully, it can support real production workloads on VPS, AWS EC2, Azure VM, Google Compute Engine, on-prem servers, and internal enterprise environments.
Why Docker Compose Best Practices Matter
A Docker Compose file that works locally may fail in production if it is not designed properly.
Common production problems include:
- Containers not restarting after crash
- Database data loss
- Hardcoded passwords
- Uncontrolled log growth
- Port conflicts
- Unhealthy containers still receiving traffic
- Security risks due to exposed services
- Slow troubleshooting because of poor logging
βDocker Compose is simple, but production Compose must be disciplined.β
Production Docker Compose Architecture
Users
|
Nginx / Load Balancer
|
API Gateway
|
+----------------------+----------------------+
| | |
Portfolio Service Interview Service Payment Service
| | |
+----------+-----------+----------+-----------+
|
MySQL
|
Redis
Monitoring:
Prometheus + Grafana + Loki + Promtail
Core Best Practices
- Use fixed image tags
- Use environment variables and .env files
- Never hardcode secrets
- Use named volumes for persistent data
- Use isolated networks
- Add health checks
- Use restart policies
- Configure log rotation
- Limit CPU and memory
- Back up volumes regularly
- Expose only required ports
- Use monitoring and alerting
1. Use Fixed Image Versions
Avoid using latest in production because it can change unexpectedly.
Bad Example
image: mysql:latest
Good Example
image: mysql:8.0
image: redis:7.2
image: grafana/grafana:10.4.3
Fixed versions make rollback, debugging, and deployment consistency easier.
2. Use .env File for Configuration
Keep environment-specific values outside the Compose file.
.env Example
SPRING_PROFILES_ACTIVE=prod
MYSQL_ROOT_PASSWORD=change-this-password
DB_USERNAME=root
DB_PASSWORD=change-this-password
RAZORPAY_KEY_ID=your-key
RAZORPAY_KEY_SECRET=your-secret
docker-compose.yml Usage
environment:
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
In production, store the .env file securely and never commit it to GitHub.
3. Never Hardcode Secrets
Do not write database passwords, payment keys, JWT secrets, OAuth secrets, or internal admin secrets directly in docker-compose.yml.
Bad Example
environment:
DB_PASSWORD: root
JWT_SECRET: mysecret
Better Example
environment:
DB_PASSWORD: ${DB_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
For stronger security, use Docker secrets, Vault, AWS Secrets Manager, Azure Key Vault, or Kubernetes Secrets when moving to orchestrated platforms.
4. Use Named Volumes for Persistent Data
Databases and uploaded files must not depend only on container writable layers.
Good Example
services:
mysql:
image: mysql:8.0
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
Named volumes survive container deletion and recreation.
5. Use Bind Mounts Carefully
Bind mounts are useful when the host path must be controlled directly, such as uploads or Nginx configuration.
Example
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./uploads:/uploads
Use :ro for read-only mounts wherever possible.
6. Use Separate Networks
Do not put every service on one open network when you can separate traffic.
Recommended Network Design
frontend-network:
Nginx, API Gateway
backend-network:
API Gateway, Microservices
data-network:
Microservices, MySQL, Redis
Example
networks:
frontend:
backend:
data:
This reduces unnecessary access between containers.
7. Expose Only Required Ports
In production, avoid exposing databases directly to the public internet.
Bad Example
mysql:
ports:
- "3306:3306"
Better Example
mysql:
expose:
- "3306"
Use ports only for services that need host/public access.
Use expose for internal container-to-container communication.
8. Add Health Checks
Health checks help Docker detect whether a container is truly healthy, not just running.
Spring Boot Health Check
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
MySQL Health Check
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
Health checks are very important for production troubleshooting and controlled startup.
9. Understand depends_on Limitation
depends_on controls container startup order, but it does not guarantee
application readiness.
depends_on:
- mysql
This means MySQL container starts before the app, but MySQL may still not be ready to accept connections.
Better Approach
- Add health checks
- Add retry logic in application
- Use connection pool retry configuration
- Use readiness checks in orchestrated environments
10. Use Restart Policies
Restart policies help recover containers after crashes or server reboot.
Recommended
restart: unless-stopped
This restarts the service automatically unless it was manually stopped.
11. Configure Log Rotation
Docker logs can consume huge disk space if not limited.
Per-Service Logging Example
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
This prevents one noisy service from filling your disk.
12. Use Centralized Logging
For production, forward logs to tools like:
- Loki + Promtail
- ELK Stack
- OpenSearch
- AWS CloudWatch
Logging Flow
Containers
|
Docker Logs
|
Promtail
|
Loki
|
Grafana
13. Set CPU and Memory Limits
One service should not consume all server resources.
Compose Resource Example
deploy:
resources:
limits:
cpus: "1.0"
memory: 768M
reservations:
memory: 256M
Resource control is very important for APIs, databases, background jobs, and search services.
14. Use Non-Root Containers
Your application images should run as a non-root user.
Dockerfile Example
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
Docker Compose should run production images that already follow this rule.
15. Use Read-Only Filesystems Where Possible
For stateless services, use read-only root filesystems and mount only required writable paths.
read_only: true
tmpfs:
- /tmp
This reduces attack surface and accidental filesystem changes.
16. Use Profiles for Optional Services
Compose profiles allow optional tools like monitoring or debugging to be enabled only when needed.
profiles:
- monitoring
Run with Profile
docker compose --profile monitoring up -d
17. Keep Compose Files Modular
Use separate files for different environments.
docker-compose.yml
docker-compose.override.yml
docker-compose.prod.yml
docker-compose.monitoring.yml
Run Production Compose
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
18. Use Image Registries
In production, do not build images manually on the server every time. Build in CI/CD and pull versioned images from a registry.
CI/CD Pipeline
|
Build Docker Image
|
Push to Registry
|
Production Server Pulls Image
19. Use a Reverse Proxy
Put Nginx, Traefik, or another reverse proxy in front of backend services.
Internet
|
Nginx
|
API Gateway
|
Microservices
Only the reverse proxy should expose HTTP/HTTPS publicly.
20. Secure Docker Socket
Avoid mounting Docker socket into application containers.
Dangerous
- /var/run/docker.sock:/var/run/docker.sock
Docker socket access can effectively provide root-level control over the host.
21. Use Backup Strategy for Volumes
Production volumes must be backed up regularly.
Volume Backup Example
docker run --rm \
-v mysql-data:/volume \
-v $(pwd):/backup \
ubuntu \
tar czf /backup/mysql-data-backup.tar.gz /volume
Database Dump Example
docker exec mysql \
mysqldump -u root -p${MYSQL_ROOT_PASSWORD} portfolio_db \
> portfolio_db_backup.sql
For databases, logical backups like mysqldump are often safer
than only raw volume backups.
22. Add Monitoring
Production Compose should include or connect to monitoring.
Monitoring Stack
Prometheus
Grafana
Loki
Promtail
Node Exporter
cAdvisor
23. Validate Compose Before Deployment
Always validate final merged configuration before production deployment.
docker compose config
This helps detect invalid YAML, missing variables, and final resolved values.
24. Use Safe Deployment Process
Pull New Images
|
Run docker compose config
|
Backup Database/Volumes
|
docker compose up -d
|
Check Health
|
Check Logs
|
Rollback if Needed
25. Production-Ready Docker Compose Example
services:
api-gateway:
image: dhanishempower/api-gateway:1.0.0
container_name: api-gateway
restart: unless-stopped
ports:
- "9090:9090"
environment:
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
PORTFOLIO_SERVICE_URL: http://portfolio-service:8080
INTERVIEW_SERVICE_URL: http://interview-service:8082
PAYMENT_SERVICE_URL: http://payment-service:8084
depends_on:
- portfolio-service
- interview-service
- payment-service
networks:
- backend
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
portfolio-service:
image: dhanishempower/portfolio-service:1.0.0
restart: unless-stopped
environment:
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
DB_URL: jdbc:mysql://mysql:3306/portfolio_db
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
expose:
- "8080"
depends_on:
- mysql
networks:
- backend
- data
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
interview-service:
image: dhanishempower/interview-service:1.0.0
restart: unless-stopped
environment:
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
DB_URL: jdbc:mysql://mysql:3306/interview_db
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
expose:
- "8082"
depends_on:
- mysql
networks:
- backend
- data
payment-service:
image: dhanishempower/payment-service:1.0.0
restart: unless-stopped
environment:
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
DB_URL: jdbc:mysql://mysql:3306/payment_db
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
RAZORPAY_KEY_ID: ${RAZORPAY_KEY_ID}
RAZORPAY_KEY_SECRET: ${RAZORPAY_KEY_SECRET}
expose:
- "8084"
depends_on:
- mysql
networks:
- backend
- data
mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
expose:
- "3306"
networks:
- data
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
redis:
image: redis:7.2
restart: unless-stopped
expose:
- "6379"
networks:
- data
networks:
backend:
data:
volumes:
mysql-data:
Production Checklist
[ ] Fixed image tags used
[ ] No secrets hardcoded
[ ] .env file secured
[ ] Databases use named volumes
[ ] Only required ports exposed
[ ] Internal services use expose
[ ] Health checks added
[ ] Restart policies added
[ ] Log rotation configured
[ ] Resource limits defined
[ ] Networks separated
[ ] Backups automated
[ ] Monitoring configured
[ ] docker compose config validated
Common Production Mistakes
- Using latest image tags
- Hardcoding passwords
- Exposing MySQL publicly
- No volume backup strategy
- No log rotation
- No health checks
- No restart policy
- Putting all services on one flat network
Interview Answer
Production-ready Docker Compose best practices include using fixed image versions, secure environment variables, named volumes for persistence, isolated networks, health checks, restart policies, log rotation, resource limits, monitoring, and regular backup strategies.
In production, Docker Compose files should avoid hardcoded secrets, avoid exposing
databases publicly, use secure .env files, validate configuration with
docker compose config, and keep services observable through logs,
metrics, and health checks.
Quick Summary Table
| Best Practice | Why It Matters |
|---|---|
| Fixed image tags | Predictable deployments |
| Named volumes | Persistent data |
| Health checks | Reliable startup and monitoring |
| Log rotation | Prevents disk full issues |
| Network isolation | Improves security |
| Backups | Disaster recovery |
Useful Internal Links
- Docker Interview Questions
- Docker Compose Interview Questions
- DevOps Interview Questions
- Microservices Interview Questions
- Kubernetes Interview Questions
- AWS Interview Questions
Final Conclusion
Docker Compose can be production-friendly when it is designed carefully with security, persistence, observability, reliability, and maintainability in mind.
For small-to-medium production systems, Docker Compose with Nginx, named volumes, health checks, restart policies, log rotation, monitoring, and automated backups can provide a stable deployment model. For large-scale enterprise systems, Kubernetes is usually the next step.