Expert guidance for writing production-grade Docker Compose configurations.
When to Use
ALWAYS use this skill when the user mentions:
- "docker compose", "docker-compose.yml", "compose file"
- "how to write compose", "compose configuration"
- "multi-container", "multi-container", "service orchestration"
- "depends_on", "profiles", "override"
- "compose network", "compose volume"
Complete File Structure
# compose.yml
name: myapp
services: # Container definitions networks: # Network topology volumes: # Persistent storage secrets: # Sensitive data (Swarm) configs: # Non-sensitive configs (Swarm)
## Services — Core Configuration
```yaml
services:
web:
image: myapp:${TAG:-latest} # Pull from registry
# build: . # Or build from Dockerfile
# build: # Build with options
# context: .
# dockerfile: Dockerfile.prod
container_name: myapp-web # Explicit name (optional)
hostname: web
ports:
- "8080:8080" # host:container
- "127.0.0.1:8443:443" # bind to localhost
- "8080:8080/udp" # UDP
environment:
- NODE_ENV=production
- DB_HOST=db # Service name = hostname!
# env_file: .env.production # Load from file
volumes:
- app-logs:/var/log/app # Named volume
- ./config:/etc/app:ro # Bind mount (read-only)
- /tmp/app:/tmp # Bind mount (read-write)
depends_on:
db:
condition: service_healthy # Wait for health check
redis:
condition: service_started
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s
restart: unless-stopped
deploy: # Swarm-only
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
profiles: # Optional service
- debug
Networking
Default Network (Auto-Created)
# Compose auto-creates a default bridge network.
# All services can reach each other by service name.
services:
web:
ports: ["8080:8080"]
db:
# No ports exposed externally — only accessible by 'web'
Custom Networks
networks:
frontend: # Public-facing
backend:
internal: true # No external access
services:
web:
networks: [frontend, backend] # In both networks
db:
networks: [backend] # Backend only (isolated)
cache:
networks: [backend]
Environment Variables
Approaches
| Method | Where | Best For |
|---|---|---|
environment: |
In compose.yml | Simple, few vars |
env_file: |
External .env.prod |
Many vars, env-specific |
.env file |
Project root (auto-loaded) | Default values, $VARIABLE substitution |
.env File (Auto-Loaded)
# .env — auto-loaded by docker compose
TAG=v1.2.3
DB_PASSWORD=secret123
# compose.yml — uses ${TAG} and ${DB_PASSWORD}
services:
web:
image: myapp:${TAG:-latest}
db:
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
env_file
# .env.production
DB_HOST=prod-db.example.com
DB_PORT=5432
services:
web:
env_file: .env.production
Depends On — Service Order
services:
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
retries: 5
web:
depends_on:
db:
condition: service_healthy # Wait for DB to be ready
redis:
condition: service_started # Wait for Redis to start
Profiles — Optional Services
services:
app:
image: myapp
debug-tools:
image: nicolaka/netshoot
command: sleep infinity
profiles: [debug] # Only starts with --profile
elasticsearch:
image: elasticsearch:8.15.0
profiles: [analytics] # Only starts with --profile
docker compose --profile debug up # Starts app + debug-tools
docker compose --profile analytics up # Starts app + elasticsearch
Multi-Environment with Override
compose.yml # Base configuration
compose.override.yml # Local dev overrides (auto-applied)
compose.prod.yml # Production overrides
# Local: compose.yml + compose.override.yml (auto)
docker compose up -d
# Production: explicit files
docker compose -f compose.yml -f compose.prod.yml up -d
Migration to Kubernetes
kompose convert -f compose.yml # Generates K8s YAML files
kubectl apply -f .
Workflow — Recommended Orchestration Process
Step 1: Plan services: List all services, ports, volumes, environment variables
Step 2: Write compose.yml: Define services/networks/volumes
Step 3: Configure dependencies: depends_on + condition: service_healthy
Step 4: Local validation: docker compose up -d → docker compose ps → docker compose logs
Step 5: Production deployment: Add resource limits, restart policy, log rotation → docker compose -f compose.yml -f compose.prod.yml up -d
Gotchas — Common Pitfalls
- depends_on without healthcheck:
depends_ononly waits for container START, not READY. → Recovery: Always addcondition: service_healthy+healthcheck:block; usedocker compose psto verify health status. - Port conflict: Multiple services can't bind the same host port. → Recovery: Use different host ports or remove
ports:for internal-only services (they communicate via service name). - Bind mount paths: Relative paths are resolved from the compose file location. → Recovery: Use
./confignotconfig/; verify withdocker compose configto see resolved paths. .envfile security:.envfiles often contain secrets. → Recovery: Add.envto.gitignore; use Docker secrets for Swarm; use.env.examplewith placeholder values.docker composevsdocker-compose: Modern syntax isdocker compose(plugin). → Recovery:docker-compose(standalone binary) is deprecated; always usedocker compose(with space).
Boundary — Capability Boundaries (Applicable and Non-Applicable Scenarios)
| Category | Scenario | Description |
|---|---|---|
| ✅ Can do | Multi-container application orchestration | Complete definition of services/networks/volumes/secrets/configs |
| ✅ Can do | Environment management | .env / env_file / override file strategies |
| ✅ Can do | Dependency control | depends_on + healthcheck + profiles |
| ⚠️ Conditional | Multi-host deployment | Migrate to Swarm (docker stack deploy) or K8s |
| ⚠️ Conditional | Rolling updates/auto-scaling | Compose does not support this; requires Swarm or K8s |
| ❌ Out of scope | Writing Dockerfiles | Use docker-dockerfile |
| ❌ Out of scope | K8s deployment | Use kompose convert + K8s skills |
| ❌ Out of scope | Cloud service orchestration (Terraform) | IaaC tools |
When NOT to Use This Skill
| ❌ Skip | ✅ Use Instead |
|---|---|
| Single-container apps | docker-run |
| Writing Dockerfile | docker-dockerfile |
| Kubernetes deployment | K8s manifests / Helm charts |
| Docker basics | docker-basics |
Security & Stability
- Use
internal: truefor backend networks to prevent external access. - Never commit
.envfiles with secrets. Use CI secrets or Docker Swarm secrets. - Production Compose on single host: combine with systemd for auto-start on boot.
- Multi-host production: migrate to Swarm (
docker stack deploy) or Kubernetes.
📚 Official Documentation References
| Documentation | Address |
|---|---|
| Docker Compose overview | https://docs.docker.com/compose/ |
| Compose file reference | https://docs.docker.com/reference/compose-file/ |
| Compose CLI | https://docs.docker.com/reference/cli/docker/compose/ |
| Environment variables | https://docs.docker.com/compose/environment-variables/ |
| Compose networking | https://docs.docker.com/compose/networking/ |
| Compose in production | https://docs.docker.com/compose/production/ |
🧭 Docker Skills Journey
📍 You are here:
docker-compose— Multi-container orchestration
← Previous: docker-buildx / docker-networking
→ Next: docker-production / docker-cicd