前言:Docker Compose 的必要性

实际应用程序大多由多个服务组成。Web 服务器、数据库、缓存、消息队列等各种组件需要协同工作。用单独的 docker run 命令管理这样的多容器环境既繁琐又容易出错。

Docker Compose 是解决这个问题的工具,它允许用一个 YAML 文件定义多个容器,并用一条命令管理整个应用程序栈。

1. 什么是 Docker Compose?

1.1 Docker Compose 的定义

Docker Compose 是用于定义和运行多容器 Docker 应用程序的工具。在 docker-compose.yml 文件中声明式地定义应用程序的服务、网络和卷。

Docker Compose 的主要特点:

  • 声明式定义:用 YAML 文件定义整个应用程序栈
  • 单命令管理:用 docker compose up 启动所有服务
  • 环境隔离:为每个项目创建独立的网络
  • 变量支持:通过环境变量和 .env 文件进行灵活配置
  • 开发便利性:特别适用于本地开发环境配置

1.2 Docker Compose V2

Docker Desktop 和最新的 Docker Engine 默认包含 Compose V2。V2 用 Go 重写,使用 docker compose(无连字符)格式。

# V1(旧版)
docker-compose up

# V2(推荐)
docker compose up

2. docker-compose.yml 结构

2.1 基本结构

# docker-compose.yml
version: "3.9"  # Compose 文件版本(可选)

services:       # 服务(容器)定义
  web:
    image: nginx:alpine
    ports:
      - "80:80"

  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret

volumes:        # 卷定义(可选)
  db-data:

networks:       # 网络定义(可选)
  backend:

2.2 最小配置示例

# 最简单的 docker-compose.yml
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"

3. 服务定义详解

3.1 image - 指定镜像

services:
  web:
    # Docker Hub 镜像
    image: nginx:alpine

  db:
    # 仓库镜像
    image: registry.example.com/mydb:1.0

  app:
    # 特定摘要
    image: myapp@sha256:abc123...

3.2 build - 构建镜像

services:
  app:
    # 简单构建
    build: .

  api:
    # 详细构建选项
    build:
      context: ./api
      dockerfile: Dockerfile.prod
      args:
        VERSION: "2.0"
        BUILD_ENV: production
      target: production  # 多阶段构建目标

  frontend:
    # 构建后指定镜像标签
    build: ./frontend
    image: myapp/frontend:latest

3.3 ports - 端口映射

services:
  web:
    image: nginx
    ports:
      # 主机:容器
      - "80:80"
      - "443:443"

      # 主机端口自动分配
      - "80"

      # 只绑定到特定 IP
      - "127.0.0.1:3000:3000"

      # UDP 端口
      - "53:53/udp"

      # 长格式
      - target: 80
        published: 8080
        protocol: tcp
        mode: host

3.4 volumes - 卷挂载

services:
  db:
    image: postgres:15
    volumes:
      # 命名卷 (Named Volume)
      - db-data:/var/lib/postgresql/data

      # 绑定挂载(主机路径)
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

      # 只读挂载
      - ./config:/etc/app/config:ro

      # 长格式
      - type: volume
        source: db-data
        target: /var/lib/postgresql/data
        volume:
          nocopy: true

volumes:
  db-data:  # 卷声明
  cache-data:
    driver: local

3.5 environment - 环境变量

services:
  app:
    image: myapp
    environment:
      # Map 格式
      NODE_ENV: production
      DEBUG: "false"
      DATABASE_URL: postgres://user:pass@db:5432/mydb

  db:
    image: postgres
    environment:
      # Array 格式
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=myapp

  worker:
    image: myworker
    # 从文件加载环境变量
    env_file:
      - .env
      - .env.local

3.6 command 和 entrypoint

services:
  app:
    image: node:20
    # 覆盖默认命令
    command: npm run dev

  worker:
    image: python:3.11
    # 数组格式
    command: ["python", "-m", "celery", "worker"]

  custom:
    image: alpine
    # 覆盖入口点
    entrypoint: /custom-entrypoint.sh
    command: ["--config", "/etc/app/config.yml"]

4. 依赖管理 (depends_on)

4.1 基本依赖

services:
  web:
    image: nginx
    depends_on:
      - api
      - db

  api:
    image: myapi
    depends_on:
      - db
      - redis

  db:
    image: postgres:15

  redis:
    image: redis:alpine

4.2 条件依赖 (service_healthy)

services:
  web:
    image: nginx
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

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

  redis:
    image: redis:alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

重要depends_on 只保证启动顺序,不保证服务处于"就绪"状态。在生产环境中请结合 healthcheck 使用 condition: service_healthy

5. 网络设置

5.1 默认网络行为

Docker Compose 会为项目自动创建默认网络。同一网络中的服务可以通过服务名相互通信。

services:
  web:
    image: nginx
    # 可以通过 "db" 主机名访问 db 服务

  db:
    image: postgres
    # 可以通过 "web" 主机名访问 web 服务

5.2 自定义网络

services:
  frontend:
    image: nginx
    networks:
      - frontend-net

  api:
    image: myapi
    networks:
      - frontend-net
      - backend-net

  db:
    image: postgres
    networks:
      - backend-net

networks:
  frontend-net:
    driver: bridge
  backend-net:
    driver: bridge
    internal: true  # 阻止外部访问

5.3 网络别名和固定 IP

services:
  db:
    image: postgres
    networks:
      backend:
        aliases:
          - database
          - postgres-primary
        ipv4_address: 172.28.0.10

networks:
  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.0.0/16

6. 实战示例:WordPress + MySQL

# wordpress/docker-compose.yml
services:
  wordpress:
    image: wordpress:latest
    restart: unless-stopped
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress_password
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress-data:/var/www/html
    depends_on:
      db:
        condition: service_healthy
    networks:
      - wordpress-net

  db:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress_password
      MYSQL_ROOT_PASSWORD: root_password
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      - wordpress-net

volumes:
  wordpress-data:
  db-data:

networks:
  wordpress-net:
    driver: bridge

7. 实战示例:Node.js + MongoDB + Redis

# nodejs-stack/docker-compose.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: development
      MONGODB_URI: mongodb://mongo:27017/myapp
      REDIS_URL: redis://redis:6379
    volumes:
      - .:/app
      - /app/node_modules
    depends_on:
      mongo:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-net
    command: npm run dev

  mongo:
    image: mongo:7
    restart: unless-stopped
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: secret
      MONGO_INITDB_DATABASE: myapp
    volumes:
      - mongo-data:/data/db
      - ./mongo-init.js:/docker-entrypoint-initdb.d/init.js:ro
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      - app-net

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - app-net

  mongo-express:
    image: mongo-express
    restart: unless-stopped
    ports:
      - "8081:8081"
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: admin
      ME_CONFIG_MONGODB_ADMINPASSWORD: secret
      ME_CONFIG_MONGODB_URL: mongodb://admin:secret@mongo:27017/
      ME_CONFIG_BASICAUTH: "false"
    depends_on:
      - mongo
    networks:
      - app-net

volumes:
  mongo-data:
  redis-data:

networks:
  app-net:
    driver: bridge

8. Docker Compose 命令

8.1 基本命令

# 启动服务(后台)
docker compose up -d

# 启动服务(前台,输出日志)
docker compose up

# 构建镜像后启动
docker compose up --build

# 只启动特定服务
docker compose up -d web db

# 停止并删除服务
docker compose down

# 同时删除卷
docker compose down -v

# 同时删除镜像
docker compose down --rmi all

8.2 状态检查命令

# 运行中的服务列表
docker compose ps

# 所有服务(包括已停止的)
docker compose ps -a

# 查看服务日志
docker compose logs

# 特定服务日志
docker compose logs web

# 实时日志跟踪
docker compose logs -f

# 只看最近100行
docker compose logs --tail=100

# 包含时间戳
docker compose logs -t

8.3 服务管理命令

# 重启服务
docker compose restart
docker compose restart web

# 停止服务(保留容器)
docker compose stop

# 启动已停止的服务
docker compose start

# 服务扩展
docker compose up -d --scale web=3

# 应用配置更改
docker compose up -d --force-recreate

8.4 在容器内执行命令 (exec)

# 在容器中执行命令
docker compose exec web sh
docker compose exec db psql -U postgres

# 以特定用户执行
docker compose exec --user root web sh

# 设置环境变量
docker compose exec -e DEBUG=true app npm test

# 用新容器执行 (run)
docker compose run --rm app npm test
docker compose run --rm db psql -U postgres

9. 环境配置分离

9.1 使用 .env 文件

# .env 文件
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret123
APP_PORT=3000
NODE_ENV=development
# docker-compose.yml
services:
  app:
    ports:
      - "${APP_PORT}:3000"
    environment:
      NODE_ENV: ${NODE_ENV}

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

9.2 使用多个 Compose 文件

# docker-compose.yml(基本配置)
services:
  app:
    build: .
    environment:
      NODE_ENV: production

  db:
    image: postgres:15
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:
# docker-compose.override.yml(开发环境,自动应用)
services:
  app:
    build:
      context: .
      target: development
    volumes:
      - .:/app
    environment:
      NODE_ENV: development
    ports:
      - "3000:3000"

  db:
    ports:
      - "5432:5432"
# docker-compose.prod.yml(生产环境)
services:
  app:
    restart: always
    deploy:
      replicas: 3
    environment:
      NODE_ENV: production

  db:
    restart: always
# 开发环境(基本 + override 自动合并)
docker compose up

# 生产环境
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# 明确指定多个文件
docker compose -f docker-compose.yml -f docker-compose.override.yml up

9.3 使用配置文件(可选服务)

services:
  app:
    image: myapp

  db:
    image: postgres:15

  # 只在开发环境使用
  adminer:
    image: adminer
    profiles:
      - debug
    ports:
      - "8080:8080"

  # 只在测试环境使用
  test-runner:
    image: myapp-test
    profiles:
      - test
# 只启动默认服务
docker compose up

# 包含 debug 配置文件
docker compose --profile debug up

# 多个配置文件
docker compose --profile debug --profile test up

10. 有用的配置选项

10.1 restart 策略

services:
  app:
    image: myapp
    restart: "no"           # 不重启(默认值)
    # restart: always       # 始终重启
    # restart: on-failure   # 只在失败时重启
    # restart: unless-stopped # 除非手动停止否则重启

10.2 资源限制

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

10.3 日志设置

services:
  app:
    image: myapp
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

结论

Docker Compose 是管理多容器应用程序的必备工具。本文内容总结如下:

  • docker-compose.yml 结构:services、volumes、networks 的作用和定义方法
  • 服务定义:image、build、ports、volumes、environment 等核心配置
  • 依赖管理:利用 depends_on 和 healthcheck 实现稳定的启动顺序
  • 网络:服务间通信和网络隔离
  • 实战示例:WordPress、Node.js 技术栈等实际使用案例
  • 命令:up、down、ps、logs、exec 等主要命令
  • 环境配置:利用 .env 文件、override 文件、配置文件

下一篇将介绍 Kubernetes 的基本概念架构,迈向容器编排的世界。