Docker Compose Production Deployment: Health Checks and Resource Limits
Docker Compose is often treated as a local development tool, but it is officially supported for production: on a single server or a handful of hosts, Compose can run a maintainable application stack. The key is separating "development configuration" from "production configuration" and adding the production essentials: health checks, resource limits and restart policies.
1. Why Compose Can Be Used in Production
Compose is a single-host multi-container orchestrator. For projects with thousands to tens of thousands of daily active users that do not need horizontal scaling across many nodes, it is far lighter than Kubernetes: no control plane, no steep learning curve, and a single docker compose up -d to go live. When you need cluster-level scheduling, upgrade to Kubernetes instead.
2. Key Changes for Production Configuration
Per the official guidance, do not reuse compose.yaml as-is in production. Put the differences in a separate compose.production.yaml and layer them with -f:
docker compose -f compose.yaml -f compose.production.yaml up -d
Typical production changes include:
- Remove application-code bind mounts: in production the code should stay inside the image and never be editable from the host.
- Change port bindings: do not expose databases or Redis to the public internet.
- Reduce log verbosity: set environment variables to suppress debug output.
- Set a restart policy: for example
restart: always, so a crash does not leave the service down. - Add log aggregation: for example JSON logs plus a collector.
A concrete split: in the development compose.yaml, the web service mounts source via bind mount and exposes port 3000 locally; compose.production.yaml carries only the overrides:
# compose.production.yaml
services:
web:
image: myapp:1.0.0 # override image instead of build
ports:
- "127.0.0.1:8080:8080" # bind to localhost only; put Nginx in front
environment:
- NODE_ENV=production
restart: always
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
On the server, docker compose -f compose.yaml -f compose.production.yaml up -d starts everything with production settings, while dev machines keep using the original file untouched.
3. Health Checks and Startup Order
The short syntax of depends_on only guarantees that a dependency starts first, not that it is ready. In production use the long syntax with healthcheck so the web service truly waits until the database is available:
services:
web:
image: myapp:1.0.0
depends_on:
db:
condition: service_healthy
restart: always
db:
image: postgres:16
healthcheck:
test: ["CMD", "pg_isready", "-U", "appuser"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
start_period is critical: it gives a container time to warm up, preventing a slow-starting service from being marked unhealthy and restarted repeatedly.
4. Resource Limits
An unbounded container can exhaust the memory of the whole machine. In production, declare CPU and memory limits for every service:
services:
web:
image: myapp:1.0.0
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
pids_limit: 512
read_only: true
tmpfs:
- /tmp
The read_only plus tmpfs combination makes the filesystem read-only, sharply reducing the ability to write malicious files after a compromise; pids_limit also helps defend against fork-bomb style attacks.
How do you size the limits? There is no universal answer, but a reasonable starting point: run first and watch docker stats for the peaks, then set limits at ~1.5x the peak and reservations at ~80% of the baseline. When a 4GB box has to host a database, an app and Redis at once, remember to count MySQL's innodb_buffer_pool_size and Redis's maxmemory into the total, or container limits and process memory end up fighting each other.
5. The Right Way to Roll Out Updates
docker compose build web
docker compose up --no-deps -d web
Build first, then up --no-deps to avoid recreating dependent services. After the update, verify health:
docker compose ps
docker compose logs --tail=100 web
The point of up --no-deps is rebuilding only the web service. To get closer to zero downtime, boot a staging instance from the new image tag and verify before switching, or rehearse a --rollback. Do not sneak database schema changes into docker compose up: run migrations as a separate step with a backup in place, then update the application code.
6. Security and Secrets
Never hard-code passwords in a Compose file in production. Prefer a .env file with environment-variable injection, and use Docker Secrets for more sensitive credentials:
services:
web:
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
For more container security practices see container security best practices, and for configuration and secret management see environment variables and configuration management.
7. Monitoring and Operations
After go-live, wire up monitoring: use monitoring and alerting to track restart counts and resource usage, and follow the backup and restore runbook for data protection. For fresh hosts, see the server initialization script.
Common Pitfalls
- Forget to back up named volumes: containers can be recreated, volumes cannot. Back one up with
docker run --rm -v myvolume:/data -v $PWD:/backup alpine tar czf /backup/vol.tgz -C /data .. - Commit .env to the repo:
.envusually holds passwords; add it to.gitignoreand route secrets through Docker Secrets or a dedicated secret manager. - Expose every port: binding database or Redis ports to
0.0.0.0hands internal services to the public internet — a prime target for scanners. - Ignore log rotation: without
max-size, log files grow until they fill the disk; one of the most common production incidents.
16IDC Note
Productionizing Compose is really about turning development convenience into operational discipline: health checks make deployments verifiable, resource limits let one machine safely host many applications, and restart policies let failures self-heal. For small and mid-sized projects this is the crucial step from "it runs" to "it runs reliably", and a natural stepping stone before migrating to Kubernetes later.
Reference: Docker Compose production guide https://docs.docker.com/compose/how-tos/production/ ; Compose file reference https://docs.docker.com/reference/compose-file/services/ ; Backing up volumes https://docs.docker.com/storage/volumes/#back-up-restore-or-migrate-data-volumes
Source: https://docs.docker.com/compose/how-tos/production/ , https://docs.docker.com/reference/compose-file/services/