Docker Compose
Docker Compose configuration for multi-service development, testing, and local orchestration
You are an expert in Docker Compose for containerized application development and deployment. ## Key Points - Use `depends_on` with `condition: service_healthy` instead of relying on startup order alone. - Keep secrets out of compose files; use `env_file` pointing to `.env` files excluded from version control. - Use `tmpfs` mounts for test databases to avoid disk I/O and leftover state. - Forgetting that `compose.override.yaml` is loaded automatically, which can silently change behavior when you intended to use only the base file. - Using `links` instead of the default Compose network; `links` is a legacy feature and unnecessary in modern Compose.
skilldb get containerization-skills/Docker ComposeFull skill: 206 linesDocker Compose — Containerization
You are an expert in Docker Compose for containerized application development and deployment.
Overview
Docker Compose defines and runs multi-container applications using a declarative YAML file. It manages service dependencies, networking, volumes, and environment configuration, making it the standard tool for local development environments and integration testing.
Core Concepts
Service Definition
Each service maps to a container. Compose handles building, networking, and lifecycle:
# compose.yaml
services:
api:
build:
context: ./api
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://user:pass@db:5432/app
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 5s
timeout: 3s
retries: 5
volumes:
pgdata:
Networking
Compose creates a default network for all services. Services communicate by service name as hostname:
services:
web:
build: ./web
networks:
- frontend
- backend
api:
build: ./api
networks:
- backend
redis:
image: redis:7-alpine
networks:
- backend
networks:
frontend:
backend:
Volumes and Bind Mounts
services:
app:
build: .
volumes:
# Bind mount for live reload in development
- ./src:/app/src
# Named volume for persistent data
- app-data:/app/data
# Anonymous volume to prevent overwriting node_modules
- /app/node_modules
volumes:
app-data:
Implementation Patterns
Development vs. Production Overrides
Use a base file with environment-specific overrides:
# compose.yaml (base)
services:
api:
image: myapp/api:latest
environment:
NODE_ENV: production
# compose.override.yaml (auto-loaded in dev)
services:
api:
build: ./api
volumes:
- ./api/src:/app/src
environment:
NODE_ENV: development
DEBUG: "true"
ports:
- "9229:9229" # debugger
Integration Testing
# compose.test.yaml
services:
test-runner:
build:
context: .
target: test
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
environment:
DATABASE_URL: postgres://test:test@db:5432/testdb
command: npm run test:integration
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
tmpfs:
- /var/lib/postgresql/data # ephemeral for speed
redis:
image: redis:7-alpine
Run with: docker compose -f compose.test.yaml run --rm test-runner
Profiles for Optional Services
services:
api:
build: ./api
monitoring:
image: grafana/grafana:latest
profiles:
- observability
ports:
- "3001:3000"
Start with monitoring: docker compose --profile observability up
Best Practices
- Use
depends_onwithcondition: service_healthyinstead of relying on startup order alone. - Keep secrets out of compose files; use
env_filepointing to.envfiles excluded from version control. - Use
tmpfsmounts for test databases to avoid disk I/O and leftover state.
Core Philosophy
Docker Compose is the bridge between a developer's laptop and a production-like environment. Its primary value is making multi-service applications trivially reproducible: a new team member should be able to run docker compose up and have a working development environment in minutes, not hours. Every compose file should be written with this onboarding experience in mind.
Compose files are living documentation of your application's service topology. They declare which services exist, how they connect, what they depend on, and what configuration they need. When the compose file accurately reflects the production architecture (same database engine, same message broker, same service boundaries), the gap between "works on my machine" and "works in production" shrinks dramatically. Divergence between the compose setup and production is a source of integration bugs that only surface during deployment.
The separation between base configuration and environment-specific overrides is a core design pattern. The base compose.yaml describes the service topology and production-like defaults. The compose.override.yaml (auto-loaded in development) adds bind mounts for live reload, debug ports, and development-only settings. Test-specific compose files strip away unnecessary services and use ephemeral storage for speed. This layered approach avoids duplicating service definitions across environments.
Anti-Patterns
-
Hardcoding secrets directly in compose files. Putting database passwords, API keys, and tokens in plain text in
compose.yamlmeans they end up in version control. Useenv_filedirectives pointing to.envfiles that are excluded from Git, or use Docker secrets for sensitive values. -
Using Compose for production deployments. Compose is designed for development and testing. It lacks health-check-based rolling deployments, node scheduling, secret rotation, and the resilience features that production workloads require. Use Kubernetes, ECS, or Docker Swarm for production orchestration.
-
Relying on startup order instead of health checks.
depends_onwithoutcondition: service_healthyonly ensures that the dependency container has started, not that it is ready to accept connections. The application will crash on startup if the database is still initializing. -
Bind-mounting
node_modulesor vendor directories. Mounting the project root into the container can overwrite the container's installed dependencies with the host's (or an empty directory). Use anonymous volumes or.dockerignoreto prevent dependency directory conflicts. -
One mega compose file with 15+ services. A compose file that starts every service in the organization makes startup slow, resource-hungry, and fragile. Scope compose files to the services a developer actually needs, and use profiles or separate files for optional auxiliary services.
Common Pitfalls
- Forgetting that
compose.override.yamlis loaded automatically, which can silently change behavior when you intended to use only the base file. - Using
linksinstead of the default Compose network;linksis a legacy feature and unnecessary in modern Compose.
Install this skill directly: skilldb add containerization-skills
Related Skills
Container Registries
Container registry setup, authentication, and image management for ECR, GCR, GHCR, and Docker Hub
Container Security
Container image scanning, runtime hardening, and security best practices for production workloads
Docker Networking
Docker networking modes, custom networks, DNS resolution, and multi-host connectivity patterns
Dockerfile Optimization
Multi-stage builds, layer caching, and image size optimization for production Docker images
Helm Charts
Helm chart creation, templating, dependency management, and release lifecycle for Kubernetes
Kubernetes Autoscaling
Kubernetes autoscaling with HPA, VPA, Cluster Autoscaler, and event-driven scaling with KEDA