TechSetupGuides
Intermediatedockerdocker-composecontainersorchestrationdevopsmicroservicesyamlinfrastructuredeploymentdevelopment

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.

  1. 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
  2. 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)
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. Step 8

    Environment Variables

    Compose supports multiple ways to set environment variables, with a clear precedence order.

    Environment variable sources (highest to lowest precedence):

    1. Compose CLI (docker compose run -e)
    2. Shell environment variables
    3. Service environment section
    4. Service env_file files
    5. Dockerfile ENV instruction
    # 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
  9. 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
  10. 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
  11. 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
  12. 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:
  13. 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
  14. Step 14

    Troubleshooting

    Common issues and debugging techniques for Docker Compose.

    Debugging workflow:

    1. Check service status with docker compose ps
    2. View logs with docker compose logs
    3. Inspect configuration with docker compose config
    4. Execute commands with docker compose exec
    5. 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
  15. 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
  16. Step 16

    Resources & Next Steps

    Documentation:

    Community:

    Tools:

    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

0 people marked this as worked·Sign in to mark your own.

Sign in to join the discussion.

No comments yet.