Home / Notebooks / DevOps
DevOps
beginner

Docker Compose Essentials

Essential Docker Compose concepts for multi-container applications

March 10, 2024
Updated regularly

Docker Compose Essentials

Quick reference guide for Docker Compose fundamentals.

What is Docker Compose?

Docker Compose is a tool for defining and running multi-container Docker applications:

  • Define services in a YAML file
  • Start all services with a single command
  • Manage networking between containers
  • Handle volumes and persistent data
  • Environment configuration for different stages
  • Perfect for development and testing
  • Installation

    macOS and Windows

    Docker Compose comes bundled with Docker Desktop.

    Linux

    # ========== Download Docker Compose ==========
    sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    
    # ========== Make Executable ==========
    sudo chmod +x /usr/local/bin/docker-compose
    
    # ========== Verify Installation ==========
    docker-compose --version
    

    Basic Structure

    Simple docker-compose.yml

    version: '3.8'
    
    services:
      web:
        image: nginx:latest
        ports:
          - "80:80"
        
      database:
        image: postgres:15
        environment:
          POSTGRES_PASSWORD: secret
    

    Complete Example

    version: '3.8'
    
    services:
      # Web Application
      app:
        build: .
        container_name: my-app
        ports:
          - "3000:3000"
        environment:
          - NODE_ENV=development
          - DATABASE_URL=postgres://db:5432/myapp
        depends_on:
          - db
          - redis
        volumes:
          - ./src:/app/src
          - node_modules:/app/node_modules
        networks:
          - app-network
        restart: unless-stopped
      
      # Database
      db:
        image: postgres:15
        container_name: postgres-db
        environment:
          POSTGRES_DB: myapp
          POSTGRES_USER: admin
          POSTGRES_PASSWORD: secret123
        volumes:
          - postgres_data:/var/lib/postgresql/data
          - ./init.sql:/docker-entrypoint-initdb.d/init.sql
        networks:
          - app-network
        ports:
          - "5432:5432"
      
      # Cache
      redis:
        image: redis:7-alpine
        container_name: redis-cache
        ports:
          - "6379:6379"
        networks:
          - app-network
    
    volumes:
      postgres_data:
      node_modules:
    
    networks:
      app-network:
        driver: bridge
    

    Basic Commands

    Start and Stop

    # ========== Start Services ==========
    docker-compose up
    
    # Start in detached mode (background)
    docker-compose up -d
    
    # Start specific service
    docker-compose up app
    
    # Build and start
    docker-compose up --build
    
    # ========== Stop Services ==========
    docker-compose down
    
    # Stop and remove volumes
    docker-compose down -v
    
    # Stop and remove images
    docker-compose down --rmi all
    
    # ========== Stop Without Removing ==========
    docker-compose stop
    
    # ========== Start Stopped Services ==========
    docker-compose start
    

    Service Management

    # ========== View Running Services ==========
    docker-compose ps
    
    # ========== View Logs ==========
    docker-compose logs
    
    # Follow logs
    docker-compose logs -f
    
    # Logs for specific service
    docker-compose logs app
    
    # Last 100 lines
    docker-compose logs --tail=100
    
    # ========== Execute Commands ==========
    docker-compose exec app bash
    docker-compose exec db psql -U admin -d myapp
    
    # Run one-off command
    docker-compose run app npm test
    
    # ========== Restart Services ==========
    docker-compose restart
    
    # Restart specific service
    docker-compose restart app
    
    # ========== Scale Services ==========
    docker-compose up --scale app=3
    

    Build Configuration

    Using Dockerfile

    services:
      app:
        build:
          context: .
          dockerfile: Dockerfile
          args:
            NODE_ENV: development
        image: myapp:latest
    

    Build with Different Dockerfile

    services:
      app:
        build:
          context: ./app
          dockerfile: Dockerfile.dev
          target: development
    

    Multi-stage Build Target

    services:
      # Development
      app-dev:
        build:
          context: .
          target: development
        volumes:
          - ./src:/app/src
      
      # Production
      app-prod:
        build:
          context: .
          target: production
    

    Environment Variables

    Using .env File

    # .env
    NODE_ENV=production
    DATABASE_URL=postgres://db:5432/myapp
    SECRET_KEY=your-secret-key
    API_PORT=3000
    
    # docker-compose.yml
    version: '3.8'
    
    services:
      app:
        image: myapp
        ports:
          - "${API_PORT}:3000"
        environment:
          - NODE_ENV=${NODE_ENV}
          - DATABASE_URL=${DATABASE_URL}
          - SECRET_KEY=${SECRET_KEY}
    

    Environment File

    services:
      app:
        env_file:
          - .env
          - .env.local
    

    Inline Environment

    services:
      app:
        environment:
          NODE_ENV: production
          DATABASE_URL: postgres://db:5432/myapp
          DEBUG: "false"
    

    Volumes

    Named Volumes

    services:
      db:
        image: postgres:15
        volumes:
          - postgres_data:/var/lib/postgresql/data
    
    volumes:
      postgres_data:
        driver: local
    

    Bind Mounts

    services:
      app:
        image: myapp
        volumes:
          # Bind mount (local directory)
          - ./src:/app/src
          - ./config:/app/config:ro  # Read-only
          
          # Named volume
          - node_modules:/app/node_modules
    

    Volume Configuration

    volumes:
      postgres_data:
        driver: local
        driver_opts:
          type: none
          o: bind
          device: /path/on/host
      
      app_data:
        external: true  # Use existing volume
    

    Networks

    Default Network

    # All services automatically join default network
    services:
      app:
        image: myapp
      
      db:
        image: postgres
        # app can reach db at hostname "db"
    

    Custom Networks

    services:
      frontend:
        image: nginx
        networks:
          - frontend-net
      
      backend:
        image: node
        networks:
          - frontend-net
          - backend-net
      
      database:
        image: postgres
        networks:
          - backend-net
    
    networks:
      frontend-net:
      backend-net:
    

    Network Configuration

    networks:
      app-network:
        driver: bridge
        ipam:
          config:
            - subnet: 172.28.0.0/16
      
      external-net:
        external: true
    

    Health Checks

    Container Health Check

    services:
      app:
        image: myapp
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
          interval: 30s
          timeout: 10s
          retries: 3
          start_period: 40s
    

    Depends On with Condition

    services:
      app:
        image: myapp
        depends_on:
          db:
            condition: service_healthy
      
      db:
        image: postgres
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U postgres"]
          interval: 10s
          timeout: 5s
          retries: 5
    

    Common Patterns

    Full-Stack Application

    version: '3.8'
    
    services:
      # Frontend
      frontend:
        build: ./frontend
        ports:
          - "3000:3000"
        environment:
          - REACT_APP_API_URL=http://localhost:8080
        volumes:
          - ./frontend/src:/app/src
        depends_on:
          - backend
      
      # Backend API
      backend:
        build: ./backend
        ports:
          - "8080:8080"
        environment:
          - DATABASE_URL=postgres://postgres:secret@db:5432/myapp
          - REDIS_URL=redis://redis:6379
        depends_on:
          - db
          - redis
        volumes:
          - ./backend/src:/app/src
      
      # Database
      db:
        image: postgres:15
        environment:
          POSTGRES_DB: myapp
          POSTGRES_PASSWORD: secret
        volumes:
          - db_data:/var/lib/postgresql/data
      
      # Cache
      redis:
        image: redis:7-alpine
        ports:
          - "6379:6379"
    
    volumes:
      db_data:
    

    Microservices Architecture

    version: '3.8'
    
    services:
      # API Gateway
      gateway:
        image: nginx:alpine
        ports:
          - "80:80"
        volumes:
          - ./nginx.conf:/etc/nginx/nginx.conf
        depends_on:
          - user-service
          - product-service
      
      # User Service
      user-service:
        build: ./services/user
        environment:
          - DATABASE_URL=postgres://db:5432/users
        depends_on:
          - db
      
      # Product Service
      product-service:
        build: ./services/product
        environment:
          - DATABASE_URL=postgres://db:5432/products
        depends_on:
          - db
      
      # Shared Database
      db:
        image: postgres:15
        environment:
          POSTGRES_PASSWORD: secret
        volumes:
          - db_data:/var/lib/postgresql/data
    
    volumes:
      db_data:
    

    Development with Hot Reload

    version: '3.8'
    
    services:
      app:
        build:
          context: .
          target: development
        ports:
          - "3000:3000"
        volumes:
          # Mount source code for hot reload
          - ./src:/app/src
          - ./package.json:/app/package.json
          # Exclude node_modules
          - /app/node_modules
        environment:
          - NODE_ENV=development
          - CHOKIDAR_USEPOLLING=true  # For file watching
        command: npm run dev
    

    Override Files

    docker-compose.override.yml

    # Base: docker-compose.yml
    version: '3.8'
    services:
      app:
        image: myapp
        environment:
          - NODE_ENV=production
    
    # Development override (automatically used)
    # docker-compose.override.yml
    version: '3.8'
    services:
      app:
        environment:
          - NODE_ENV=development
          - DEBUG=true
        volumes:
          - ./src:/app/src
    

    Multiple Compose Files

    # ========== Production ==========
    docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
    
    # ========== Staging ==========
    docker-compose -f docker-compose.yml -f docker-compose.staging.yml up
    
    # ========== Development (uses override automatically) ==========
    docker-compose up
    

    Example: docker-compose.prod.yml

    version: '3.8'
    
    services:
      app:
        image: myapp:1.0.0
        restart: always
        environment:
          - NODE_ENV=production
        # No volume mounts for production
      
      db:
        restart: always
        # Use external managed database
        # environment:
        #   DATABASE_URL: ${PROD_DATABASE_URL}
    

    Database Initialization

    PostgreSQL Init Script

    services:
      db:
        image: postgres:15
        volumes:
          - postgres_data:/var/lib/postgresql/data
          - ./init-scripts:/docker-entrypoint-initdb.d
        environment:
          POSTGRES_DB: myapp
          POSTGRES_USER: admin
          POSTGRES_PASSWORD: secret
    
    -- init-scripts/01-schema.sql
    CREATE TABLE users (
        id SERIAL PRIMARY KEY,
        name VARCHAR(100),
        email VARCHAR(255) UNIQUE
    );
    
    CREATE TABLE orders (
        id SERIAL PRIMARY KEY,
        user_id INTEGER REFERENCES users(id),
        total DECIMAL(10, 2)
    );
    

    MySQL Init Script

    services:
      db:
        image: mysql:8
        volumes:
          - mysql_data:/var/lib/mysql
          - ./init.sql:/docker-entrypoint-initdb.d/init.sql
        environment:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: myapp
          MYSQL_USER: user
          MYSQL_PASSWORD: password
    

    Service Discovery

    Container Communication

    services:
      app:
        image: myapp
        environment:
          # Use service name as hostname
          - DATABASE_URL=postgres://db:5432/myapp
          - REDIS_URL=redis://cache:6379
        depends_on:
          - db
          - cache
      
      db:
        image: postgres:15
        # Accessible at hostname "db"
      
      cache:
        image: redis:7
        # Accessible at hostname "cache"
    

    Network Aliases

    services:
      db:
        image: postgres:15
        networks:
          backend:
            aliases:
              - database
              - postgres-db
              - primary-db
    
    networks:
      backend:
    

    Resource Limits

    CPU and Memory Limits

    services:
      app:
        image: myapp
        deploy:
          resources:
            limits:
              cpus: '0.5'
              memory: 512M
            reservations:
              cpus: '0.25'
              memory: 256M
    

    For Compose v2

    services:
      app:
        image: myapp
        mem_limit: 512m
        mem_reservation: 256m
        cpus: 0.5
    

    Profiles

    Conditional Service Start

    services:
      app:
        image: myapp
        # Always starts
      
      debug-tools:
        image: debug-image
        profiles: ["debug"]
      
      monitoring:
        image: prometheus
        profiles: ["monitoring", "production"]
    
    # ========== Start with Profile ==========
    docker-compose --profile debug up
    
    # Multiple profiles
    docker-compose --profile debug --profile monitoring up
    
    # Without profile (only app starts)
    docker-compose up
    

    Best Practices

    1. Version Control

    # .gitignore
    .env
    .env.local
    *.env.local
    docker-compose.override.yml
    
    # .env.example (commit this)
    NODE_ENV=development
    DATABASE_URL=postgres://db:5432/myapp
    SECRET_KEY=your-secret-here
    

    2. Service Names

    # Use descriptive service names
    services:
      web-frontend:
        # ...
      
      api-backend:
        # ...
      
      postgres-database:
        # ...
    

    3. Health Checks

    services:
      app:
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost/health"]
          interval: 30s
          timeout: 10s
          retries: 3
    

    4. Restart Policies

    services:
      app:
        restart: unless-stopped  # or always, on-failure, no
    

    5. Resource Management

    services:
      app:
        deploy:
          resources:
            limits:
              memory: 1G
    

    Troubleshooting

    View Logs

    # ========== All Services ==========
    docker-compose logs
    
    # ========== Specific Service ==========
    docker-compose logs app
    
    # ========== Follow Logs ==========
    docker-compose logs -f
    
    # ========== Last N Lines ==========
    docker-compose logs --tail=50
    
    # ========== With Timestamps ==========
    docker-compose logs -t
    

    Debug Container

    # ========== Execute Shell ==========
    docker-compose exec app sh
    
    # ========== Check Process ==========
    docker-compose exec app ps aux
    
    # ========== Check Network ==========
    docker-compose exec app ping db
    
    # ========== View Environment ==========
    docker-compose exec app env
    

    Cleanup

    # ========== Remove Stopped Containers ==========
    docker-compose down
    
    # ========== Remove Volumes ==========
    docker-compose down -v
    
    # ========== Remove Everything ==========
    docker-compose down -v --rmi all --remove-orphans
    
    # ========== Prune Unused Resources ==========
    docker system prune -a
    

    Common Issues

    # ========== Port Already in Use ==========
    # Change port mapping
    # ports:
    #   - "8080:3000"  # Use different host port
    
    # Or stop conflicting service
    lsof -ti:3000 | xargs kill
    
    # ========== Volume Permission Issues ==========
    # Fix volume permissions
    docker-compose exec app chown -R node:node /app
    
    # ========== Service Not Starting ==========
    # Check logs
    docker-compose logs app
    
    # Build without cache
    docker-compose build --no-cache app
    
    # ========== Network Issues ==========
    # Recreate network
    docker-compose down
    docker-compose up
    

    Advanced Features

    Extensions

    x-common-variables: &common-vars
      NODE_ENV: production
      LOG_LEVEL: info
    
    services:
      app1:
        image: myapp
        environment:
          <<: *common-vars
          SERVICE_NAME: app1
      
      app2:
        image: myapp
        environment:
          <<: *common-vars
          SERVICE_NAME: app2
    

    Wait for Services

    services:
      app:
        image: myapp
        depends_on:
          db:
            condition: service_healthy
        command: >
          sh -c "
            echo 'Waiting for database...' &&
            sleep 5 &&
            npm start
          "
      
      db:
        image: postgres:15
        healthcheck:
          test: ["CMD-SHELL", "pg_isready"]
    

    Build Arguments

    services:
      app:
        build:
          context: .
          args:
            - NODE_VERSION=18
            - BUILD_DATE=${BUILD_DATE}
    

    Tips

  • Use .env files for sensitive data (don't commit)
  • Set restart policies for production services
  • Name volumes for persistence across recreations
  • Use health checks to ensure service readiness
  • Leverage depends_on for service ordering
  • Use profiles for optional services
  • Keep images small with multi-stage builds
  • Document your setup in README
  • Use override files for environment-specific config
  • Clean up regularly to free disk space
  • Common Commands Reference

    # ========== Lifecycle ==========
    docker-compose up -d              # Start in background
    docker-compose down               # Stop and remove
    docker-compose restart            # Restart services
    docker-compose stop               # Stop without removing
    docker-compose start              # Start stopped services
    
    # ========== Build ==========
    docker-compose build              # Build images
    docker-compose build --no-cache   # Build without cache
    docker-compose up --build         # Build and start
    
    # ========== Logs ==========
    docker-compose logs -f            # Follow logs
    docker-compose logs app           # Service-specific logs
    
    # ========== Execute ==========
    docker-compose exec app bash      # Open shell
    docker-compose run app npm test   # Run command
    
    # ========== Management ==========
    docker-compose ps                 # List services
    docker-compose top                # Display processes
    docker-compose config             # Validate and view config
    
    # ========== Cleanup ==========
    docker-compose down -v            # Remove volumes
    docker-compose down --rmi all     # Remove images
    

    Resources

  • Docker Compose Documentation
  • Compose File Reference
  • Docker Documentation
  • Awesome Docker Compose
  • Docker Hub
  • Topics

    DockerDocker ComposeDevOpsContainersDevelopment

    Found This Helpful?

    If you have questions or suggestions for improving these notes, I'd love to hear from you.