Chapter 6: Docker Compose Orchestration

Haiyue
17min

Chapter 6: Docker Compose Orchestration

Learning Objectives
  • Understand the concepts and importance of service orchestration
  • Master docker-compose.yml file writing
  • Learn multi-container application deployment and management
  • Proficiently use Compose for development environment setup

Knowledge Points

What is Docker Compose

Docker Compose is Docker’s official orchestration tool, used to define and run multi-container Docker applications. Configure application services through a YAML file, then use a single command to create and start all services from the configuration.

Compose Core Concepts:

  • Service: A component of an application, such as database, web server, etc.
  • Project: A complete application composed of a group of related services
  • Container: The running instance of a service

Compose File Structure

version: '3.8'  # Compose file format version

services:       # Define services
  web:
    image: nginx:alpine
    ports:
      - "80:80"

  database:
    image: postgres:13
    environment:
      - POSTGRES_DB=mydb

volumes:        # Define volumes
  data-volume:

networks:       # Define networks
  app-network:
    driver: bridge

Docker Compose Installation

Linux System Installation

# Download Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/v2.21.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# Add execution permissions
sudo chmod +x /usr/local/bin/docker-compose

# Create symbolic link (optional)
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

# Verify installation
docker-compose --version

Install Using pip

# Install using pip (Python environment)
pip install docker-compose

# Verify installation
docker-compose --version

Compose File Writing

Basic Syntax

# docker-compose.yml
version: '3.8'

services:
  # Web service
  web:
    image: nginx:alpine
    container_name: my-nginx
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
      - ./conf/nginx.conf:/etc/nginx/nginx.conf:ro
    restart: unless-stopped
    depends_on:
      - api
    networks:
      - frontend

  # API service
  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    container_name: my-api
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
    volumes:
      - ./api:/usr/src/app
      - /usr/src/app/node_modules
    depends_on:
      - db
      - redis
    networks:
      - frontend
      - backend

  # Database service
  db:
    image: postgres:13-alpine
    container_name: my-postgres
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    ports:
      - "5432:5432"
    restart: unless-stopped
    networks:
      - backend

  # Redis cache
  redis:
    image: redis:alpine
    container_name: my-redis
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    ports:
      - "6379:6379"
    restart: unless-stopped
    networks:
      - backend

# Volume definitions
volumes:
  postgres_data:
    driver: local
  redis_data:
    driver: local

# Network definitions
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true

Environment Variable Configuration

# .env file
POSTGRES_DB=myapp
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret123
API_PORT=3000
WEB_PORT=8080
# Using environment variables in docker-compose.yml
version: '3.8'

services:
  web:
    image: nginx:alpine
    ports:
      - "${WEB_PORT}:80"

  api:
    build: ./api
    ports:
      - "${API_PORT}:3000"
    environment:
      - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}

  db:
    image: postgres:13
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

Build Configuration

services:
  web:
    build:
      context: ./web
      dockerfile: Dockerfile.prod
      args:
        - NODE_ENV=production
        - API_URL=http://api:3000
      target: production
      cache_from:
        - node:16-alpine
        - my-app:latest
    image: my-web-app:latest

Common Compose Commands

Basic Operation Commands

# Start services (run in background)
docker-compose up -d

# Start specific services
docker-compose up web db

# View running status
docker-compose ps

# View service logs
docker-compose logs
docker-compose logs -f web  # Follow specific service logs
docker-compose logs --tail=100 api  # Display last 100 lines

# Stop services
docker-compose stop

# Stop and remove containers
docker-compose down

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

# Restart services
docker-compose restart
docker-compose restart web

Management and Debug Commands

# Build or rebuild services
docker-compose build
docker-compose build --no-cache web

# Pull service images
docker-compose pull

# Execute commands in service
docker-compose exec web bash
docker-compose exec db psql -U postgres -d mydb

# Run one-time commands
docker-compose run --rm web curl http://api:3000/health

# View service configuration
docker-compose config

# Validate compose file
docker-compose config --quiet

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

# View port mappings
docker-compose port web 80

Project Management Commands

# Specify project name
docker-compose -p myproject up -d

# Specify compose file
docker-compose -f docker-compose.prod.yml up -d

# Use multiple compose files
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# View projects
docker-compose ls

# Pause/unpause services
docker-compose pause
docker-compose unpause

# Send signals to services
docker-compose kill -s SIGUSR1 web

Practical Cases

LAMP Architecture Application

# docker-compose.yml
version: '3.8'

services:
  web:
    image: httpd:2.4-alpine
    container_name: apache-server
    ports:
      - "80:80"
    volumes:
      - ./www:/usr/local/apache2/htdocs
      - ./apache/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro
    depends_on:
      - php
    networks:
      - lamp-network

  php:
    build:
      context: ./php
      dockerfile: Dockerfile
    container_name: php-fpm
    volumes:
      - ./www:/var/www/html
    depends_on:
      - mysql
    networks:
      - lamp-network

  mysql:
    image: mysql:8.0
    container_name: mysql-server
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: lampdb
      MYSQL_USER: lampuser
      MYSQL_PASSWORD: lamppass
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    ports:
      - "3306:3306"
    networks:
      - lamp-network

  phpmyadmin:
    image: phpmyadmin:latest
    container_name: phpmyadmin
    environment:
      PMA_HOST: mysql
      PMA_USER: root
      PMA_PASSWORD: rootpass
    ports:
      - "8080:80"
    depends_on:
      - mysql
    networks:
      - lamp-network

volumes:
  mysql_data:

networks:
  lamp-network:
    driver: bridge

Microservices Architecture

# docker-compose.microservices.yml
version: '3.8'

services:
  # API Gateway
  gateway:
    image: nginx:alpine
    container_name: api-gateway
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - user-service
      - order-service
      - product-service
    networks:
      - frontend

  # User service
  user-service:
    build: ./services/user
    container_name: user-service
    environment:
      - DATABASE_URL=postgresql://postgres:password@user-db:5432/users
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - user-db
      - redis
    networks:
      - frontend
      - user-backend

  # Order service
  order-service:
    build: ./services/order
    container_name: order-service
    environment:
      - DATABASE_URL=postgresql://postgres:password@order-db:5432/orders
      - USER_SERVICE_URL=http://user-service:3000
    depends_on:
      - order-db
    networks:
      - frontend
      - order-backend

  # Product service
  product-service:
    build: ./services/product
    container_name: product-service
    environment:
      - DATABASE_URL=postgresql://postgres:password@product-db:5432/products
    depends_on:
      - product-db
    networks:
      - frontend
      - product-backend

  # Database services
  user-db:
    image: postgres:13-alpine
    container_name: user-database
    environment:
      POSTGRES_DB: users
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - user_db_data:/var/lib/postgresql/data
    networks:
      - user-backend

  order-db:
    image: postgres:13-alpine
    container_name: order-database
    environment:
      POSTGRES_DB: orders
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - order_db_data:/var/lib/postgresql/data
    networks:
      - order-backend

  product-db:
    image: postgres:13-alpine
    container_name: product-database
    environment:
      POSTGRES_DB: products
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - product_db_data:/var/lib/postgresql/data
    networks:
      - product-backend

  # Shared services
  redis:
    image: redis:alpine
    container_name: redis-cache
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    networks:
      - user-backend

  # Monitoring services
  prometheus:
    image: prom/prometheus
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
    networks:
      - monitoring

  grafana:
    image: grafana/grafana
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana
    depends_on:
      - prometheus
    networks:
      - monitoring

volumes:
  user_db_data:
  order_db_data:
  product_db_data:
  redis_data:
  grafana_data:

networks:
  frontend:
    driver: bridge
  user-backend:
    driver: bridge
    internal: true
  order-backend:
    driver: bridge
    internal: true
  product-backend:
    driver: bridge
    internal: true
  monitoring:
    driver: bridge

Development Environment Configuration

# docker-compose.dev.yml
version: '3.8'

services:
  # Frontend development environment
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    container_name: frontend-dev
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - CHOKIDAR_USEPOLLING=true
      - REACT_APP_API_URL=http://localhost:8000
    command: npm start

  # Backend development environment
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    container_name: backend-dev
    ports:
      - "8000:8000"
    volumes:
      - ./backend:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://developer:devpass@postgres:5432/devdb
      - REDIS_URL=redis://redis:6379
    depends_on:
      - postgres
      - redis
    command: npm run dev

  # Development database
  postgres:
    image: postgres:13-alpine
    container_name: postgres-dev
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: devdb
      POSTGRES_USER: developer
      POSTGRES_PASSWORD: devpass
    volumes:
      - postgres_dev_data:/var/lib/postgresql/data
      - ./database/init-dev.sql:/docker-entrypoint-initdb.d/init.sql:ro

  # Development cache
  redis:
    image: redis:alpine
    container_name: redis-dev
    ports:
      - "6379:6379"
    volumes:
      - redis_dev_data:/data

  # Database management tool
  adminer:
    image: adminer
    container_name: adminer-dev
    ports:
      - "8080:8080"
    depends_on:
      - postgres

volumes:
  postgres_dev_data:
  redis_dev_data:

Advanced Features

Health Check Configuration

services:
  web:
    image: nginx:alpine
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  api:
    build: ./api
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 1m30s
      timeout: 10s
      retries: 3

Resource Limits

services:
  web:
    image: nginx:alpine
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s

Configuration and Secret Management

services:
  app:
    image: my-app:latest
    configs:
      - source: app_config
        target: /etc/app/config.yml
        mode: 0644
    secrets:
      - db_password
      - api_key

configs:
  app_config:
    external: true

secrets:
  db_password:
    external: true
  api_key:
    external: true

Multi-Environment Configuration

# Base configuration
# docker-compose.yml
version: '3.8'
services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"

# Development environment override
# docker-compose.override.yml
version: '3.8'
services:
  web:
    volumes:
      - ./src:/usr/share/nginx/html
    environment:
      - DEBUG=true

# Production environment configuration
# docker-compose.prod.yml
version: '3.8'
services:
  web:
    restart: unless-stopped
    environment:
      - DEBUG=false
    deploy:
      replicas: 3

Using different environments:

# Development environment (automatically uses override file)
docker-compose up -d

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

# Test environment
docker-compose -f docker-compose.yml -f docker-compose.test.yml up -d

Performance Optimization and Best Practices

Build Optimization

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      cache_from:
        - my-app:latest
        - my-app:cache
      target: production
    image: my-app:latest

Startup Order Control

# Use healthcheck to control startup order
services:
  db:
    image: postgres:13
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    image: my-app:latest
    depends_on:
      db:
        condition: service_healthy

Log Management

services:
  web:
    image: nginx:alpine
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        labels: "production_status"
        env: "NODE_ENV"
Docker Compose Best Practices
  1. Version Control: Include compose files in Git version control
  2. Environment Isolation: Use different compose files to manage different environments
  3. Health Checks: Configure health checks for critical services
  4. Resource Limits: Set appropriate resource limits for production environments
  5. Log Management: Configure appropriate log drivers and rotation policies
  6. Security Configuration: Use secrets to manage sensitive information
Important Notes
  • Compose is mainly for single-machine orchestration, cluster environments should use Kubernetes
  • Production environments need to consider high availability and load balancing
  • Backup and recovery strategies for data volumes are important
  • Network security configuration cannot be ignored in production environments

Summary

Through this chapter, you should have mastered:

  • Compose Basics: Understand Docker Compose’s role and core concepts
  • File Writing: Proficiently write docker-compose.yml configuration files
  • Service Orchestration: Able to orchestrate complex multi-container applications
  • Environment Management: Master multi-environment configuration and management techniques
  • Best Practices: Understand optimization strategies for production environment use

In the next chapter, we will learn about container monitoring and log management, establishing a comprehensive operations monitoring system.