07 - Docker Compose
What Is Docker Compose?
Docker Compose lets you define and run multi-container applications with a single YAML file. Instead of running multiple docker run commands, you describe everything in docker-compose.yml.
bash# Without Compose (painful) docker network create myapp docker volume create db-data docker run -d --name db --network myapp -v db-data:/var/lib/postgresql/data -e POSTGRES_PASSWORD=secret postgres:16 docker run -d --name redis --network myapp redis:7-alpine docker run -d --name api --network myapp -p 3000:3000 -e DB_HOST=db -e REDIS_HOST=redis myapi docker run -d --name web --network myapp -p 80:80 mynginx # With Compose (one command) docker compose up -d
Compose File Structure
yaml# docker-compose.yml (or compose.yml) # Optional: specify compose version (modern compose ignores this) # version: "3.8" # Legacy, not needed with Docker Compose V2 services: # --- Service definitions --- web: image: nginx:alpine ports: - "80:80" api: build: ./api ports: - "3000:3000" environment: - DB_HOST=db db: image: postgres:16 volumes: - db-data:/var/lib/postgresql/data # --- Named volumes --- volumes: db-data: # --- Custom networks --- networks: frontend: backend:
Complete Real-World Example
yaml# compose.yml - Full-stack web application services: # ======= Frontend ======= web: build: context: ./frontend dockerfile: Dockerfile args: - NODE_ENV=production ports: - "3000:3000" environment: - API_URL=http://api:8080 depends_on: api: condition: service_healthy networks: - frontend restart: unless-stopped # ======= Backend API ======= api: build: context: ./backend dockerfile: Dockerfile target: production # Multi-stage build target ports: - "8080:8080" environment: - DATABASE_URL=postgresql://app:secret@db:5432/myapp - REDIS_URL=redis://redis:6379 - JWT_SECRET=${JWT_SECRET} # From .env file depends_on: db: condition: service_healthy redis: condition: service_started healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 5s retries: 3 start_period: 10s networks: - frontend - backend restart: unless-stopped deploy: resources: limits: memory: 512M cpus: "1.0" # ======= Database ======= db: image: postgres:16-alpine environment: - POSTGRES_DB=myapp - POSTGRES_USER=app - POSTGRES_PASSWORD=secret volumes: - db-data:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro ports: - "5432:5432" # Expose for local dev tools healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d myapp"] interval: 10s timeout: 5s retries: 5 networks: - backend restart: unless-stopped # ======= Cache ======= redis: image: redis:7-alpine command: redis-server --appendonly yes --maxmemory 256mb volumes: - redis-data:/data networks: - backend restart: unless-stopped # ======= Background Worker ======= worker: build: context: ./backend target: production command: ["node", "worker.js"] environment: - DATABASE_URL=postgresql://app:secret@db:5432/myapp - REDIS_URL=redis://redis:6379 depends_on: db: condition: service_healthy redis: condition: service_started networks: - backend restart: unless-stopped volumes: db-data: driver: local redis-data: driver: local networks: frontend: driver: bridge backend: driver: bridge
Service Configuration Deep Dive
Build Options
yamlservices: api: # Simple build build: ./api # Detailed build config build: context: ./api # Build context directory dockerfile: Dockerfile.prod # Custom Dockerfile name target: production # Multi-stage target args: # Build arguments - NODE_ENV=production cache_from: # Cache sources - myregistry/api:cache platforms: # Multi-platform - linux/amd64 - linux/arm64
Environment Variables
yamlservices: api: # Inline variables environment: - DB_HOST=db - DB_PORT=5432 # Map syntax environment: DB_HOST: db DB_PORT: "5432" # From .env file env_file: - .env - .env.local # Overrides .env # Reference host environment variable environment: - API_KEY # Uses host's $API_KEY value - SECRET=${MY_SECRET:-default_value} # With default
.env File
bash# .env (automatically loaded by Compose) POSTGRES_PASSWORD=secret JWT_SECRET=my-super-secret API_KEY=abc123 COMPOSE_PROJECT_NAME=myapp # Prefix for container names
Depends On
yamlservices: api: depends_on: # Simple form (just startup order) - db - redis # Advanced form (with health conditions) db: condition: service_healthy # Wait for healthcheck to pass redis: condition: service_started # Just wait for container start migrations: condition: service_completed_successfully # Wait for exit 0
Volumes
yamlservices: api: volumes: # Named volume - data:/app/data # Bind mount (short syntax) - ./src:/app/src # Bind mount (long syntax) - type: bind source: ./src target: /app/src read_only: true # Anonymous volume (prevent overwriting node_modules) - /app/node_modules # tmpfs - type: tmpfs target: /tmp tmpfs: size: 100000000 # 100 MB volumes: data: driver: local # External volume (created outside Compose) shared: external: true
Networking
yamlservices: api: networks: - frontend - backend # With aliases networks: backend: aliases: - api-server - backend-api networks: frontend: driver: bridge backend: driver: bridge ipam: config: - subnet: 172.28.0.0/16
Resource Limits
yamlservices: api: deploy: resources: limits: memory: 512M cpus: "1.5" reservations: memory: 256M cpus: "0.5"
Essential Compose Commands
bash# Start all services docker compose up # Start in background docker compose up -d # Start specific services docker compose up -d api db # Build and start docker compose up -d --build # Stop all services docker compose down # Stop and remove volumes too docker compose down -v # Stop and remove everything (images too) docker compose down --rmi all -v # View running services docker compose ps # View logs docker compose logs docker compose logs -f api # Follow specific service docker compose logs --tail 50 # Last 50 lines # Execute command in running service docker compose exec api bash docker compose exec db psql -U app -d myapp # Run a one-off command docker compose run --rm api npm test # Scale a service docker compose up -d --scale worker=3 # Restart a service docker compose restart api # Rebuild a specific service docker compose build api docker compose up -d api # View service resource usage docker compose top docker compose stats
Multiple Compose Files (Override Pattern)
yaml# docker-compose.yml (base) services: api: build: ./api environment: - NODE_ENV=production # docker-compose.override.yml (auto-loaded for dev) services: api: build: target: development volumes: - ./api/src:/app/src # Live reload environment: - NODE_ENV=development - DEBUG=true ports: - "9229:9229" # Debug port # docker-compose.prod.yml (production overrides) services: api: image: myregistry/api:latest # Use pre-built image restart: always deploy: replicas: 3
bash# Development (base + override automatically) docker compose up -d # Production (base + prod, skip override) docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d # Or use COMPOSE_FILE env var export COMPOSE_FILE=docker-compose.yml:docker-compose.prod.yml docker compose up -d
Compose Profiles
Group services that should start together:
yamlservices: api: build: ./api # No profile = always starts db: image: postgres:16 # No profile = always starts adminer: image: adminer profiles: - debug # Only starts with --profile debug prometheus: image: prom/prometheus profiles: - monitoring grafana: image: grafana/grafana profiles: - monitoring
bash# Start only default services (api, db) docker compose up -d # Start with debug tools docker compose --profile debug up -d # Start with monitoring docker compose --profile monitoring up -d # Start everything docker compose --profile debug --profile monitoring up -d
Docker Compose Watch (Dev Live Reload)
yaml# compose.yml services: api: build: ./api develop: watch: - action: sync # Sync files without rebuilding path: ./api/src target: /app/src - action: rebuild # Rebuild on dependency changes path: ./api/package.json - action: sync+restart # Sync and restart the service path: ./api/config target: /app/config
bashdocker compose watch # Starts services with file watching
FAANG Interview Angle
Common questions:
- "How does Docker Compose differ from Docker Swarm or Kubernetes?"
- "How do services communicate in Docker Compose?"
- "How would you handle secrets in Compose?"
- "What's your development workflow with Docker Compose?"
- "How do you manage different environments?"
Key answers:
- Compose is for defining multi-container apps on a single host; Swarm/K8s handle orchestration across hosts
- Services on the same Compose network communicate by service name (DNS)
- Use .env files locally, Docker secrets or external vaults in production
- Base compose + override file pattern (dev/staging/prod)
docker compose watchfor development hot-reload
Official Links
- Docker Compose Overview
- Compose File Reference
- Compose CLI Reference
- Compose Watch
- Environment Variables