Docker Compose Patterns & Best Practices
A reference guide for production Docker Compose deployments. Covers networking, volumes, health checks, secrets management, resource limits, and multi-service design patterns.
Table of Contents
- Networking Patterns
- Volume Management
- Health Checks
- Environment Variables & Secrets
- Resource Limits
- Multi-Service Patterns
- Reverse Proxy with Traefik
- Logging
- Startup Order & Dependencies
- Production Checklist
Networking Patterns
Isolated Stack Networks
Each Compose stack should define its own bridge network. Services within the same network can reach each other by container name. Services in different stacks are isolated by default.
services:
app:
networks:
- backend
db:
networks:
- backend
networks:
backend:
driver: bridge
Shared Networks for Cross-Stack Communication
When two stacks need to talk (e.g., an app stack reaching a shared database), use an external network:
# In the database stack
networks:
shared-db:
name: shared-db
driver: bridge
# In the app stack
networks:
shared-db:
external: true
Exposing Ports Safely
Only expose ports that external clients need. Internal service-to-service traffic should use the Docker network, not published ports.
services:
# Public-facing — expose port
nginx:
ports:
- "443:443"
# Internal only — no port mapping needed
api:
expose:
- "3000"
Bind to localhost during development to avoid exposing services to your LAN:
ports:
- "127.0.0.1:5432:5432"
Volume Management
Named Volumes vs Bind Mounts
| Type | Use Case | Example |
|---|---|---|
| Named volume | Database data, persistent state | db-data:/var/lib/postgresql/data |
| Bind mount | Config files, source code (dev only) | ./nginx.conf:/etc/nginx/nginx.conf:ro |
| tmpfs | Temporary files, caches | tmpfs: /tmp |
services:
postgres:
volumes:
# Named volume — Docker manages the directory
- pgdata:/var/lib/postgresql/data
# Bind mount — read-only config file
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
volumes:
pgdata:
driver: local
Read-Only Mounts
Mount config files as read-only (:ro) to prevent accidental writes:
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
Volume Backup Pattern
# Backup a named volume to a tar archive
docker run --rm \
-v pgdata:/data:ro \
-v "$(pwd)":/backup \
alpine tar czf /backup/pgdata-$(date +%F).tar.gz -C /data .
Health Checks
Every long-running service should define a health check. This enables depends_on with condition: service_healthy and lets Docker report accurate status.
HTTP Health Check
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
TCP Health Check
For services that don't have an HTTP endpoint:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres || exit 1"]
interval: 10s
timeout: 5s
retries: 5
Important Parameters
| Parameter | Purpose | Recommended |
|---|---|---|
interval |
Time between checks | 10–30s |
timeout |
Max time to wait for a check to respond | 5–10s |
retries |
Failures before marking unhealthy | 3–5 |
start_period |
Grace period for slow-starting services | 30–120s |
Environment Variables & Secrets
Using .env Files
Docker Compose automatically loads a .env file from the project directory:
services:
app:
environment:
- DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
Docker Secrets (Swarm Mode)
For production deployments on Docker Swarm, use secrets instead of environment variables:
services:
app:
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Separation of Concerns
Keep different types of configuration separate:
.env # Shared defaults (ports, versions)
.env.local # Machine-specific overrides (not committed)
.env.production # Production values (committed, no real secrets)
Never commit real passwords or tokens. Use a secrets manager (Vault, AWS Secrets Manager) for production.
Resource Limits
Always set memory and CPU limits in production to prevent a single container from consuming all host resources.
services:
elasticsearch:
deploy:
resources:
limits:
cpus: "2.0"
memory: 2g
reservations:
cpus: "0.5"
memory: 512m
Note: deploy.resources works in both Swarm mode and standalone Compose (with Docker Compose v2+).
JVM-Based Services
For Elasticsearch, Logstash, and other JVM services, set both the container memory limit and the JVM heap:
environment:
- ES_JAVA_OPTS=-Xms1g -Xmx1g # JVM heap
deploy:
resources:
limits:
memory: 2g # Container limit (heap + overhead)
Rule of thumb: container limit = JVM heap × 2 (to allow for off-heap, GC, and OS overhead).
Multi-Service Patterns
Sidecar Pattern
Run helper containers alongside your main service:
services:
app:
image: myapp:latest
# Log shipper sidecar
filebeat:
image: docker.elastic.co/beats/filebeat:8.13.4
volumes:
- app-logs:/var/log/app:ro
depends_on:
- app
Init Container Pattern
Run a one-shot container to prepare state before the main service starts:
services:
migrate:
image: myapp:latest
command: ["python", "manage.py", "migrate"]
depends_on:
db:
condition: service_healthy
app:
image: myapp:latest
depends_on:
migrate:
condition: service_completed_successfully
Worker Pattern
Scale background workers independently from the web process:
services:
web:
image: myapp:latest
command: ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]
worker:
image: myapp:latest
command: ["celery", "-A", "tasks", "worker", "--loglevel=info"]
deploy:
replicas: 3
Reverse Proxy with Traefik
Traefik auto-discovers services via Docker labels. No manual Nginx config files needed.
Basic Label Configuration
services:
app:
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
- "traefik.http.services.app.loadbalancer.server.port=3000"
Path-Based Routing
labels:
- "traefik.http.routers.api.rule=Host(`example.com`) && PathPrefix(`/api`)"
- "traefik.http.middlewares.api-strip.stripprefix.prefixes=/api"
- "traefik.http.routers.api.middlewares=api-strip"
Rate Limiting Middleware
labels:
- "traefik.http.middlewares.rate-limit.ratelimit.average=100"
- "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
- "traefik.http.routers.app.middlewares=rate-limit"
Logging
JSON Logging Driver
Configure structured logging for easier parsing by ELK or Loki:
services:
app:
logging:
driver: json-file
options:
max-size: "10m" # Rotate after 10 MB
max-file: "3" # Keep 3 rotated files
tag: "{{.Name}}" # Tag with container name
Sending Logs to Logstash
logging:
driver: gelf
options:
gelf-address: "udp://localhost:12201"
tag: "myapp"
Startup Order & Dependencies
Basic Ordering
depends_on:
- db
- redis
This only waits for the container to start, not for the service to be ready.
Wait for Healthy Dependencies
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
This is the correct approach — it waits until the health check passes before starting the dependent service.
Wait for One-Shot Tasks
depends_on:
migrate:
condition: service_completed_successfully
Useful for database migrations or seed scripts that must finish before the app starts.
Production Checklist
Before deploying to production, verify each item:
- [ ] All services have health checks with appropriate
start_periodvalues - [ ] Resource limits (memory + CPU) are set for every service
- [ ] Named volumes are used for persistent data (not bind mounts)
- [ ] No hardcoded secrets — all passwords come from
.envor Docker secrets - [ ] Restart policy is
unless-stoppedoralwaysfor every service - [ ] Log rotation is configured (json-file driver with
max-sizeandmax-file) - [ ] Networks are isolated — only expose what needs to be public
- [ ] Images use specific tags (not
:latest) for reproducibility - [ ] Ports are not exposed for internal-only services
- [ ] Backups are configured for database volumes
- [ ] TLS is enabled via Traefik or an external load balancer
- [ ] Monitoring (Prometheus + Grafana or similar) is deployed
Part of Docker Compose Templates — (c) 2026 Datanest Digital (datanest.dev)
This is 1 of 6 resources in the DevOps Toolkit Pro toolkit. Get the complete [Docker Compose Templates] with all files, templates, and documentation for $XX.
Or grab the entire DevOps Toolkit Pro bundle (6 products) for $178 — save 30%.
Top comments (0)