Docker Compose

This chapter will introduce Docker Compose in detail, teaching you how to use YAML files to define and manage multi-container applications, achieving container orchestration and service management.

Introduction to Docker Compose

What is Docker Compose?

Docker Compose is a tool for defining and running multi-container Docker applications. By using a YAML file to configure application services, you can then create and start all services with a single command.

Core Concepts

  • Service: A component of the application, such as a web server, database, etc.
  • Project: A complete application consisting of a set of related services
  • Network: Network environment for service-to-service communication
  • Volume: Storage for data persistence and sharing

Advantages of Docker Compose

  1. Simplify deployment: Start the entire application stack with one command
  2. Environment isolation: Each project has independent networks and namespaces
  3. Service discovery: Containers can communicate with each other by service name
  4. Configuration management: Unified management of all service configurations
  5. Scalability: Easily scale the number of service instances

Installing Docker Compose

Linux Installation

# Method 1: Download using curl
sudo curl -L "https://github.com/docker/compose/releases/download/v2.12.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Method 2: Install using pip
pip3 install docker-compose

# Method 3: Use package manager (Ubuntu)
sudo apt-get update
sudo apt-get install docker-compose-plugin

# Verify installation
docker-compose --version

Windows and macOS

Docker Desktop already includes Docker Compose, no separate installation needed.

docker-compose.yml File Structure

Basic Structure

version: '3.8'  # Compose file version

services:       # Define services
  web:
    # Service configuration
  db:
    # Service configuration

volumes:        # Define volumes (optional)
  # Volume configuration

networks:       # Define networks (optional)
  # Network configuration

Version Compatibility

Compose VersionDocker Engine Version
3.819.03.0+
3.718.06.0+
3.618.02.0+
3.517.12.0+

Service Configuration Details

Basic Service Configuration

version: '3.8'

services:
  web:
    # Use existing image
    image: nginx:latest

    # Or build image
    build: .
    # build:
    #   context: .
    #   dockerfile: Dockerfile

    # Container name
    container_name: my-web-server

    # Port mapping
    ports:
      - "8080:80"
      - "8443:443"

    # Environment variables
    environment:
      - NODE_ENV=production
      - DEBUG=false

    # Volume mounts
    volumes:
      - ./html:/usr/share/nginx/html
      - logs:/var/log/nginx

    # Networks
    networks:
      - frontend

    # Dependencies
    depends_on:
      - db

    # Restart policy
    restart: unless-stopped

Build Configuration

services:
  web:
    build:
      context: .                    # Build context
      dockerfile: Dockerfile.prod   # Specify Dockerfile
      args:                        # Build arguments
        - VERSION=1.0
        - BUILD_DATE=2023-01-01
      target: production           # Multi-stage build target
      cache_from:                  # Cache source
        - myapp:cache
    image: myapp:latest           # Image name after build

Environment Variable Configuration

services:
  web:
    # Method 1: Direct definition
    environment:
      - NODE_ENV=production
      - PORT=3000
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb

    # Method 2: Use object format
    environment:
      NODE_ENV: production
      PORT: 3000
      DATABASE_URL: postgresql://user:pass@db:5432/mydb

    # Method 3: Read from file
    env_file:
      - .env
      - .env.local

    # Method 4: Inherit from host environment
    environment:
      - NODE_ENV
      - DATABASE_URL=${DATABASE_URL}

Volume Configuration

services:
  web:
    volumes:
      # Bind mount
      - ./src:/app/src
      - ./config.json:/app/config.json:ro  # Read-only

      # Named volume
      - app-data:/app/data
      - logs:/var/log

      # Anonymous volume
      - /app/node_modules

      # tmpfs mount
      - type: tmpfs
        target: /tmp
        tmpfs:
          size: 100M

# Define named volumes
volumes:
  app-data:
    driver: local
  logs:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /host/logs

Network Configuration

services:
  web:
    networks:
      - frontend
      - backend

  db:
    networks:
      - backend

# Define networks
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # Internal network, cannot access external

Dependencies and Startup Order

services:
  web:
    depends_on:
      - db
      - redis
    # Or use conditional dependencies (requires healthcheck)
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  db:
    image: postgres:13
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  redis:
    image: redis:alpine

Real-World Application Examples

Example 1: Web Application + Database

version: '3.8'

services:
  # Web application service
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
    depends_on:
      - db
    volumes:
      - ./uploads:/app/uploads
    networks:
      - app-network
    restart: unless-stopped

  # PostgreSQL database
  db:
    image: postgres:13
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-network
    restart: unless-stopped

  # Redis cache
  redis:
    image: redis:alpine
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    networks:
      - app-network
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:

networks:
  app-network:
    driver: bridge

Example 2: Microservices Architecture

version: '3.8'

services:
  # Nginx reverse proxy
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - api
      - web
    networks:
      - frontend
    restart: unless-stopped

  # Frontend application
  web:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    networks:
      - frontend
    restart: unless-stopped

  # API service
  api:
    build:
      context: ./backend
      dockerfile: Dockerfile
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/api
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis
    networks:
      - frontend
      - backend
    restart: unless-stopped

  # Database
  db:
    image: postgres:13
    environment:
      - POSTGRES_DB=api
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - backend
    restart: unless-stopped

  # Cache
  redis:
    image: redis:alpine
    volumes:
      - redis_data:/data
    networks:
      - backend
    restart: unless-stopped

volumes:
  db_data:
  redis_data:

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true

Docker Compose Commands

Basic Commands

# Start services
docker-compose up

# Start in background
docker-compose up -d

# Start specific services
docker-compose up web db

# Build images
docker-compose build

# Build and start
docker-compose up --build

# Stop services
docker-compose stop

# Stop and remove containers
docker-compose down

# Stop and remove containers, networks, volumes
docker-compose down -v

# View service status
docker-compose ps

# View logs
docker-compose logs

# View logs in real-time
docker-compose logs -f

# View specific service logs
docker-compose logs web

# Enter container
docker-compose exec web bash

# Run one-time command
docker-compose run web npm install

# Scale service instances
docker-compose up --scale web=3

# Restart service
docker-compose restart

# Pause service
docker-compose pause

# Resume service
docker-compose unpause

Advanced Commands

# Validate configuration file
docker-compose config

# Display configuration (after variable resolution)
docker-compose config --services

# Pull images
docker-compose pull

# Push images
docker-compose push

# View port mapping
docker-compose port web 80

# View service processes
docker-compose top

# Send signal
docker-compose kill -s SIGINT web

# Remove stopped containers
docker-compose rm

# Force remove
docker-compose rm -f

Environment Management

Multi-Environment Configuration

# Development environment
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up

# Production environment
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up

# Testing environment
docker-compose -f docker-compose.yml -f docker-compose.test.yml up

Configuration File Examples

docker-compose.yml (base configuration):

version: '3.8'

services:
  web:
    build: .
    volumes:
      - ./app:/app
    networks:
      - app-network

  db:
    image: postgres:13
    networks:
      - app-network

networks:
  app-network:

docker-compose.dev.yml (development environment override):

version: '3.8'

services:
  web:
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
    command: npm run dev

  db:
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=devdb
      - POSTGRES_USER=dev
      - POSTGRES_PASSWORD=devpass

docker-compose.prod.yml (production environment override):

version: '3.8'

services:
  web:
    ports:
      - "80:3000"
    environment:
      - NODE_ENV=production
    restart: unless-stopped

  db:
    environment:
      - POSTGRES_DB=proddb
      - POSTGRES_USER=prod
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - prod_db_data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  prod_db_data:

Health Checks and Dependency Management

Health Checks

version: '3.8'

services:
  web:
    image: nginx
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  db:
    image: postgres
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 30s
      timeout: 5s
      retries: 5

Chapter Summary

This chapter comprehensively introduced Docker Compose usage:

Key Points:

  • Service orchestration: Use YAML files to define multi-container applications
  • Network management: Service discovery and network isolation
  • Data management: Volumes and persistent storage
  • Environment management: Multi-environment configuration and variable management
  • Scalability: Service scaling and load balancing
  • Monitoring and debugging: Log management and troubleshooting

Best Practices:

  • Use version control to manage Compose files
  • Reasonably design service dependencies
  • Use health checks to ensure service availability
  • Separate configuration and code
  • Regularly backup important data

In the next chapter, we will learn about Docker security best practices, including image security, container security, and network security.

Further Reading