Docker Compose - Declarative Multi-Container Orchestration
Install and configure Docker Compose to define and run multi-container Docker applications with YAML configuration files - covering installation, service definitions, networking, volumes, and production deployment patterns.
- Step 1
Overview
Docker Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application's services, networks, and volumes. Then, with a single command, you create and start all the services from your configuration.
Key capabilities:
- Declarative configuration: Define entire application stacks in
docker-compose.yml - Service orchestration: Start, stop, and manage multiple containers as a unit
- Networking: Automatic DNS-based service discovery between containers
- Volume management: Persistent data storage and bind mounts
- Environment isolation: Multiple isolated environments on a single host
- Development workflow: Fast iteration with live reload and hot-reload support
- Production ready: Deploy to production with profiles and override files
Why Docker Compose:
- Simplicity: Single file defines your entire application infrastructure
- Reproducibility: Same configuration works across dev, test, and production
- Version control: Infrastructure as code - track changes in git
- Fast iteration: Rebuild and restart services in seconds
- Industry standard: Used by millions of developers worldwide
Official site: https://docs.docker.com/compose/ GitHub: https://github.com/docker/compose (85K+ stars) Documentation: https://docs.docker.com/compose/compose-file/ Compose Specification: https://github.com/compose-spec/compose-spec - Declarative configuration: Define entire application stacks in
- Step 2
Installation Options
Docker Compose comes bundled with Docker Desktop for Mac and Windows. For Linux, you have multiple installation options.
Installation methods:
- Docker Desktop: Includes Docker Compose (recommended for Mac/Windows)
- Docker Plugin: Modern installation as a Docker CLI plugin
- Standalone binary: Classic standalone binary installation
- pip: Install via Python package manager
# Option 1: Docker Desktop (Mac/Windows) # Download from https://www.docker.com/products/docker-desktop # Compose is included automatically # Option 2: Docker Plugin (Linux - recommended) # Install Docker Engine first, then: sudo apt-get update sudo apt-get install docker-compose-plugin # Verify installation docker compose version # Output: Docker Compose version v2.29.0 # Option 3: Standalone Binary (Linux) COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') sudo curl -L "https://github.com/docker/compose/releases/download/v${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose # Verify docker-compose --version # Option 4: pip (any platform with Python) pip install docker-compose # Note: Modern installations use 'docker compose' (plugin) # Legacy installations use 'docker-compose' (standalone) - Step 3
Basic docker-compose.yml Structure
A Compose file defines services, networks, and volumes. The most common version is 3.8+ of the Compose file format.
Key sections:
- version: (optional in Compose v2) Compose file format version
- services: Container definitions with images, ports, volumes, etc.
- networks: Custom network configurations
- volumes: Named volumes for persistent storage
- configs/secrets: Configuration and sensitive data management
# docker-compose.yml - Basic example version: '3.8' services: # Web application service web: image: nginx:alpine ports: - "80:80" volumes: - ./html:/usr/share/nginx/html:ro environment: - NGINX_HOST=localhost - NGINX_PORT=80 networks: - frontend restart: unless-stopped # Application server app: build: context: ./app dockerfile: Dockerfile ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=postgres://user:pass@db:5432/mydb depends_on: - db networks: - frontend - backend restart: unless-stopped # Database service db: image: postgres:16-alpine volumes: - db-data:/var/lib/postgresql/data environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=pass - POSTGRES_DB=mydb networks: - backend restart: unless-stopped networks: frontend: driver: bridge backend: driver: bridge volumes: db-data: driver: local - Step 4
Essential Commands
Docker Compose provides a comprehensive CLI for managing your application stack.
Common operations:
- up: Create and start containers
- down: Stop and remove containers, networks
- ps: List running containers
- logs: View container logs
- exec: Run commands in running containers
- build: Build or rebuild services
# Start all services (detached mode) docker compose up -d # Start specific services docker compose up -d web app # View logs (follow mode) docker compose logs -f # View logs for specific service docker compose logs -f app # List running services docker compose ps # Execute command in running container docker compose exec app sh # Run one-off command docker compose run app npm test # Stop services (keeps containers) docker compose stop # Start stopped services docker compose start # Restart services docker compose restart # Stop and remove containers, networks (keeps volumes) docker compose down # Stop and remove everything including volumes docker compose down -v # Build or rebuild services docker compose build # Build without cache docker compose build --no-cache # Pull latest images docker compose pull # View service configuration docker compose config # Scale services docker compose up -d --scale app=3 - Step 5
Service Configuration
Services are the core of Compose - each service runs one container. Services can be configured with images, build instructions, environment variables, volumes, and more.
Key service options:
- image: Use pre-built image from registry
- build: Build from Dockerfile
- ports: Expose ports (HOST:CONTAINER)
- volumes: Mount volumes or bind mounts
- environment: Set environment variables
- depends_on: Define startup order
- command: Override default command
- entrypoint: Override default entrypoint
services: # Using pre-built image redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis-data:/data command: redis-server --appendonly yes # Building from Dockerfile api: build: context: ./api dockerfile: Dockerfile args: NODE_VERSION: 20 target: production ports: - "8080:8080" environment: - PORT=8080 - REDIS_URL=redis://redis:6379 env_file: - .env - .env.production volumes: - ./api:/app - /app/node_modules # Anonymous volume depends_on: redis: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s restart: unless-stopped # Worker service worker: build: context: ./worker environment: - REDIS_URL=redis://redis:6379 - WORKER_CONCURRENCY=4 depends_on: - redis deploy: replicas: 2 resources: limits: cpus: '0.5' memory: 512M restart: on-failure - Step 6
Networking in Compose
Compose creates a default network for your application. Services can communicate using their service names as hostnames via DNS resolution.
Network features:
- Automatic DNS: Services accessible by name (e.g.,
http://api:8080) - Isolated networks: Separate frontend and backend networks
- Custom networks: Define bridge, host, or overlay networks
- Network aliases: Multiple names for same service
services: # Frontend proxy nginx: image: nginx:alpine ports: - "80:80" networks: - frontend - backend # Can access both web and api services by name # Web application (frontend only) web: image: node:20-alpine networks: - frontend # API (backend only) api: image: node:20-alpine networks: backend: aliases: - api-server - backend-api # Accessible as 'api', 'api-server', or 'backend-api' # Database (backend only, isolated) postgres: image: postgres:16-alpine networks: - backend # Not accessible from frontend network networks: frontend: driver: bridge ipam: config: - subnet: 172.20.0.0/24 backend: driver: bridge internal: true # No external access # Use existing external network shared: external: true name: my-shared-network - Automatic DNS: Services accessible by name (e.g.,
- Step 7
Volume Management
Volumes provide persistent storage for containers. Compose supports named volumes, bind mounts, and tmpfs mounts.
Volume types:
- Named volumes: Managed by Docker, persist across container restarts
- Bind mounts: Mount host directory into container
- tmpfs: In-memory storage (not persisted)
- Volume drivers: Support for NFS, cloud storage, etc.
services: # Named volume (Docker-managed) postgres: image: postgres:16-alpine volumes: - postgres-data:/var/lib/postgresql/data # Data persists even if container is removed # Bind mount (host directory) web: image: nginx:alpine volumes: - ./html:/usr/share/nginx/html:ro # Read-only - ./nginx.conf:/etc/nginx/nginx.conf:ro # Direct access to host files # Multiple volume types app: image: node:20-alpine volumes: # Bind mount for development - ./src:/app/src # Anonymous volume for dependencies - /app/node_modules # Named volume for uploads - app-uploads:/app/uploads # tmpfs for temp files (in-memory) - type: tmpfs target: /app/tmp # Volume with specific driver backup: image: backup-tool volumes: - type: volume source: backup-data target: /backup volume: nocopy: true volumes: postgres-data: driver: local app-uploads: driver: local driver_opts: type: none o: bind device: /mnt/uploads backup-data: driver: local driver_opts: type: nfs o: addr=192.168.1.100,rw device: ":/path/to/backup" # Use existing volume shared-data: external: true - Step 8
Environment Variables
Compose supports multiple ways to set environment variables, with a clear precedence order.
Environment variable sources (highest to lowest precedence):
- Compose CLI (
docker compose run -e) - Shell environment variables
- Service
environmentsection - Service
env_filefiles - Dockerfile
ENVinstruction
# docker-compose.yml services: app: image: node:20-alpine # Option 1: Inline environment variables environment: - NODE_ENV=production - API_KEY=${API_KEY} # From shell or .env file - DATABASE_URL=postgres://user:pass@db:5432/mydb # Option 2: Environment file env_file: - .env # Default environment - .env.production # Override with production values # Variable interpolation in compose file web: image: nginx:${NGINX_VERSION:-alpine} ports: - "${WEB_PORT:-80}:80" # .env file (in same directory as docker-compose.yml) # These are used by Compose for variable substitution NGINX_VERSION=1.25-alpine WEB_PORT=8080 API_KEY=your-secret-key # .env.production (loaded by app service) NODE_ENV=production LOG_LEVEL=info DATABASE_POOL_SIZE=20 # Use from shell export API_KEY=shell-override docker compose up -d # Or inline API_KEY=inline-override docker compose up -d # View resolved configuration docker compose config - Compose CLI (
- Step 9
Development Workflow
Docker Compose excels in development environments with live reload, hot module replacement, and debugging support.
Development features:
- Bind mounts: Live code updates without rebuilds
- Override files: Separate dev/prod configurations
- Debugger access: Expose debugging ports
- Watch mode: Auto-rebuild on file changes (Compose v2.22+)
# docker-compose.yml (base configuration) services: app: build: context: ./app target: production environment: - NODE_ENV=production restart: unless-stopped # docker-compose.override.yml (auto-merged in development) services: app: build: target: development volumes: - ./app/src:/app/src # Live code reload - ./app/package.json:/app/package.json environment: - NODE_ENV=development - DEBUG=app:* ports: - "9229:9229" # Node.js debugger command: npm run dev # docker-compose.prod.yml (explicit production) services: app: restart: always logging: driver: json-file options: max-size: "10m" max-file: "3" # Development startup (uses override automatically) docker compose up -d # Production startup (ignores override) docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d # Watch mode (Compose 2.22+) # Automatically rebuild and restart on file changes services: app: develop: watch: - path: ./app/src action: sync target: /app/src - path: ./app/package.json action: rebuild # Start with watch enabled docker compose watch - Step 10
Dependency Management
Control startup order and ensure services are ready before dependent services start.
Dependency options:
- depends_on: Basic startup order
- condition: Wait for specific service state
- healthcheck: Define service health criteria
services: # Database with healthcheck postgres: image: postgres:16-alpine environment: - POSTGRES_PASSWORD=secret healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 # Redis with healthcheck redis: image: redis:7-alpine healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 5 # API waits for healthy database and redis api: build: ./api depends_on: postgres: condition: service_healthy redis: condition: service_healthy environment: - DATABASE_URL=postgres://postgres:secret@postgres:5432/db - REDIS_URL=redis://redis:6379 # Worker only waits for services to start (not healthy) worker: build: ./worker depends_on: - api # Will start when api container starts, not when it's healthy # Migration job - runs once and exits migrate: build: ./api command: npm run migrate depends_on: postgres: condition: service_healthy restart: "no" # Don't restart when migration completes - Step 11
Production Deployment
Deploy Compose applications to production with best practices for security, reliability, and monitoring.
Production considerations:
- Resource limits: Prevent resource exhaustion
- Restart policies: Auto-recovery from failures
- Logging: Centralized log management
- Secrets: Secure credential management
- Health checks: Monitor service health
# docker-compose.prod.yml services: web: image: myapp/web:${VERSION:-latest} deploy: replicas: 3 resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M restart_policy: condition: on-failure delay: 5s max_attempts: 3 window: 120s restart: always logging: driver: json-file options: max-size: "10m" max-file: "3" healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s postgres: image: postgres:16-alpine volumes: - postgres-data:/var/lib/postgresql/data secrets: - postgres_password environment: - POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password deploy: resources: limits: memory: 1G restart: always # Backup service backup: image: prodrigestivill/postgres-backup-local volumes: - ./backups:/backups environment: - POSTGRES_HOST=postgres - POSTGRES_DB=mydb - POSTGRES_USER=postgres - SCHEDULE=@daily secrets: - postgres_password depends_on: - postgres restart: unless-stopped secrets: postgres_password: file: ./secrets/postgres_password.txt volumes: postgres-data: driver: local driver_opts: type: none o: bind device: /mnt/data/postgres - Step 12
Common Patterns
Real-world application stacks using Docker Compose.
Example stacks:
- LAMP/LEMP: Web server + PHP + MySQL
- MEAN/MERN: MongoDB + Express + React + Node.js
- Microservices: Multiple services with gateway
- CI/CD: Build and test environments
# Full-stack web application (MERN) services: # Frontend (React) frontend: build: context: ./frontend dockerfile: Dockerfile ports: - "3000:3000" environment: - REACT_APP_API_URL=http://localhost:8080 volumes: - ./frontend/src:/app/src depends_on: - api # Backend API (Node.js + Express) api: build: context: ./api dockerfile: Dockerfile ports: - "8080:8080" environment: - PORT=8080 - MONGODB_URL=mongodb://mongo:27017/myapp - REDIS_URL=redis://redis:6379 - JWT_SECRET=${JWT_SECRET} volumes: - ./api/src:/app/src depends_on: mongo: condition: service_healthy redis: condition: service_healthy # MongoDB mongo: image: mongo:7 ports: - "27017:27017" volumes: - mongo-data:/data/db - ./mongo-init:/docker-entrypoint-initdb.d environment: - MONGO_INITDB_ROOT_USERNAME=admin - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD} - MONGO_INITDB_DATABASE=myapp healthcheck: test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] interval: 10s timeout: 5s retries: 5 # Redis cache redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis-data:/data command: redis-server --appendonly yes healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 5 # Nginx reverse proxy nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/ssl:/etc/nginx/ssl:ro depends_on: - frontend - api volumes: mongo-data: redis-data: - Step 13
Profiles for Multi-Environment
Compose profiles allow selective service activation for different environments or scenarios.
Profile use cases:
- Development tools: Only run in dev (e.g., adminer, mailhog)
- Testing: Enable test databases or mocks
- Debugging: Profilers, tracers, log aggregators
- Optional services: Background jobs, workers
services: # Core services (always run) api: build: ./api ports: - "8080:8080" postgres: image: postgres:16-alpine environment: - POSTGRES_PASSWORD=secret # Development-only services adminer: image: adminer ports: - "8081:8080" profiles: - dev - debug mailhog: image: mailhog/mailhog ports: - "1025:1025" # SMTP - "8025:8025" # Web UI profiles: - dev # Testing profile test-db: image: postgres:16-alpine environment: - POSTGRES_PASSWORD=test profiles: - test tmpfs: - /var/lib/postgresql/data # In-memory for speed # Debugging tools jaeger: image: jaegertracing/all-in-one:latest ports: - "16686:16686" profiles: - debug # Production monitoring prometheus: image: prom/prometheus ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml profiles: - monitoring - prod # Usage: # Default (core services only) docker compose up -d # Development mode docker compose --profile dev up -d # Testing mode docker compose --profile test up -d # Multiple profiles docker compose --profile dev --profile debug up -d # Production with monitoring docker compose --profile prod --profile monitoring up -d - Step 14
Troubleshooting
Common issues and debugging techniques for Docker Compose.
Debugging workflow:
- Check service status with
docker compose ps - View logs with
docker compose logs - Inspect configuration with
docker compose config - Execute commands with
docker compose exec - Check networking with
docker network inspect
# Check service status docker compose ps # Shows state, ports, and health status # View all logs docker compose logs # Follow logs for specific service docker compose logs -f api # Last 100 lines from all services docker compose logs --tail=100 # Logs with timestamps docker compose logs -t # Validate and view resolved compose file docker compose config # Check if services can resolve each other docker compose exec api ping postgres # Check environment variables docker compose exec api env # Inspect volumes docker volume ls docker volume inspect <volume_name> # Inspect networks docker network ls docker network inspect <network_name> # Force recreate containers docker compose up -d --force-recreate # Rebuild images docker compose build --no-cache # Remove everything and start fresh docker compose down -v docker compose up -d --build # Check resource usage docker stats # Common fixes: # 1. Port already in use # Change port mapping: "8081:8080" instead of "8080:8080" # 2. Volume permission issues # Fix ownership: docker compose exec api chown -R node:node /app # 3. Services can't communicate # Ensure they're on same network and using service names as hostnames # 4. Container keeps restarting # Check logs: docker compose logs <service> # Check healthcheck: docker inspect <container_id> # 5. Out of disk space # Prune unused resources: docker system prune -a --volumes - Check service status with
- Step 15
Docker Compose vs Docker Swarm vs Kubernetes
Understanding when to use Docker Compose and when to consider alternatives.
Comparison:
- Docker Compose: Single-host orchestration, development and small production
- Docker Swarm: Multi-host clustering, built into Docker Engine
- Kubernetes: Enterprise-grade orchestration, complex deployments
Docker Compose: ✓ Simple YAML syntax ✓ Fast local development ✓ Single host deployment ✓ No learning curve ✗ No high availability ✗ No auto-scaling ✗ Limited to one host Use for: Development, small apps, single-server production Docker Swarm: ✓ Multi-host clustering ✓ Built into Docker ✓ Simple setup ✓ Rolling updates ✓ Auto-scaling ✗ Less ecosystem support ✗ Fewer features than K8s Use for: Medium production deployments, existing Docker workflows Kubernetes: ✓ Industry standard ✓ Massive ecosystem ✓ Advanced features (CRDs, operators, etc.) ✓ Cloud-native ✗ Steep learning curve ✗ Complex setup ✗ Overhead for small apps Use for: Large-scale production, microservices, cloud-native apps Migration path: Develop with Compose → Deploy small apps with Compose → Deploy medium apps with Swarm → Deploy large apps with Kubernetes Compose can convert to Kubernetes manifests: kompose convert -f docker-compose.yml - Step 16
Resources & Next Steps
Documentation:
Community:
Tools:
- Awesome Compose - Sample compose files
- Kompose - Convert Compose to Kubernetes
- Docker Desktop - GUI for Docker
Next steps:
- Explore the Awesome Compose repository for real-world examples
- Learn Docker networking in depth
- Study Dockerfile best practices
- Consider Docker Swarm for multi-host deployments
- Explore Kubernetes for large-scale orchestration
GitHub: https://github.com/docker/compose Official site: https://docs.docker.com/compose/ Compose Spec: https://github.com/compose-spec/compose-spec Awesome Compose: https://github.com/docker/awesome-compose Kompose: https://kompose.io/ Docker Hub: https://hub.docker.com/
Feature requests
Sign in to suggest features or vote on existing ones.
No feature requests yet.
Discussion
Sign in to join the discussion.
No comments yet.