Docker compose 快速指南

2025-12-26 20:58:00
丁国栋
原创 10
摘要:本文介绍一下快速使用Docker的docker-compose。

本文含有AI生成内容

Docker Compose 完整指南

一、Docker Compose 简介

1.1 什么是 Docker Compose

Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。通过一个 YAML 文件(通常是 docker-compose.yml),您可以配置应用程序的所有服务,然后通过单个命令创建和启动所有服务。

1.2 解决的问题与适用场景

解决的问题:

  • 简化多容器应用的部署和管理
  • 解决容器间依赖关系和启动顺序
  • 统一管理容器配置
  • 快速搭建开发、测试、生产环境
  • 实现容器初始化顺序控制

适用场景:

  • 微服务架构应用
  • Web 应用 + 数据库组合
  • 开发环境标准化
  • CI/CD 流水线
  • 本地开发和测试环境
  • 需要初始化脚本的应用

1.3 Docker Compose 与 Docker 的关系

  • Docker:专注于单个容器的生命周期管理
  • Docker Compose:专注于多容器应用的编排和管理
  • Docker Compose 使用 Docker API 来管理容器
  • 通常与 Docker Engine 配合使用
  • Compose 文件是 Docker 运行命令的声明式配置

二、安装与配置

2.1 系统要求

  • Linux 系统(Debian/Ubuntu 为例)
  • Docker Engine 已安装
  • 用户拥有 sudo 权限
  • 至少 2GB 可用磁盘空间

2.2 安装方法(Linux - Debian/Ubuntu 为例)

2.2.1 通过包管理器安装

步骤1:更新包索引

sudo apt-get update

步骤2:安装依赖包

sudo apt-get install -y \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

步骤3:添加 Docker 官方 GPG 密钥

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
    sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

步骤4:设置 Docker 仓库

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

步骤5:安装 Docker Compose Plugin

sudo apt-get update
sudo apt-get install -y docker-compose-plugin

2.2.2 通过二进制方式安装

步骤1:下载最新版本

# 获取最新版本号
COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep 'tag_name' | cut -d\" -f4)
# 下载二进制文件
sudo curl -L "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" \
    -o /usr/local/bin/docker-compose

步骤2:设置执行权限

sudo chmod +x /usr/local/bin/docker-compose

步骤3:创建符号链接(可选)

sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

2.2.3 官方文档遵循说明

2.3 验证安装

# 检查版本
docker-compose --version
# 或使用插件版本
docker compose version
# 验证功能
docker-compose --help

2.4 基本配置

创建 Docker Compose 配置文件目录:

mkdir ~/docker-projects
cd ~/docker-projects

三、核心概念

3.1 Compose 文件结构

version: '3.8'  # Compose 文件版本
services:       # 定义服务
  web:          # 服务名称
    image: nginx:latest
  db:
    image: postgres:13
  init-container:  # 初始化容器
    image: alpine:latest
    restart: "no"
networks:       # 定义网络
  frontend:
volumes:        # 定义卷
  db-data:

3.2 服务(Services)

  • 应用中的每个容器定义为一个服务
  • 可以指定镜像、构建配置、依赖关系
  • 支持扩展和缩放
  • 包括常规服务和初始化服务

3.3 网络(Networks)

  • 容器间的通信隔离
  • 支持自定义网络驱动
  • 可配置网络拓扑
  • 支持固定IP地址

3.4 卷(Volumes)

  • 持久化数据存储
  • 数据独立于容器生命周期
  • 支持多种存储驱动
  • 包括命名卷和主机路径绑定

3.5 环境变量

  • 配置容器运行时参数
  • 支持从文件加载
  • 支持不同环境的不同配置
  • 可在初始化容器中使用

3.6 初始化容器

  • 执行前置任务的容器
  • 完成后自动退出
  • 控制服务启动顺序
  • 确保数据准备就绪

四、docker-compose.yml 文件详解

4.1 文件结构示例

version: '3.8'
services:
  # 初始化容器
  init-db:
    image: postgres:13-alpine
    command: >
      sh -c "
        until pg_isready -h db -U postgres; do sleep 2; done &&
        psql -h db -U postgres -c 'CREATE DATABASE IF NOT EXISTS appdb;'
      "
    depends_on:
      db:
        condition: service_healthy
    restart: "no"
  # 主服务
  db:
    image: postgres:13
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 20
  webapp:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      init-db:
        condition: service_completed_successfully
    networks:
      - app-network
volumes:
  postgres-data:
networks:
  app-network:
    driver: bridge

4.2 常用配置项说明

配置项 说明 示例
image 使用的镜像 nginx:latest
build 构建上下文 ./app
ports 端口映射 "80:80"
volumes 卷挂载 ./data:/app/data
environment 环境变量 DEBUG=true
depends_on 服务依赖 - db
networks 网络连接 - frontend
restart 重启策略 always
healthcheck 健康检查 自定义检查命令
command 覆盖默认命令 自定义启动命令

4.3 服务配置详解

services:
  # 初始化服务示例
  init-service:
    image: alpine:latest
    # 初始化容器关键配置
    restart: "no"  # 只运行一次
    command: >
      sh -c "
        echo '开始初始化...' &&
        # 执行初始化任务
        sleep 5 &&
        echo '初始化完成'
      "
    # 依赖关系
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    # 网络配置
    networks:
      - app-network
    # 环境变量
    environment:
      INIT_TIMEOUT: 30
    # 卷挂载
    volumes:
      - ./init-scripts:/scripts:ro
    # 工作目录
    working_dir: /scripts
    # 用户权限
    user: "1000:1000"
    # 日志配置
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
  # 常规服务
  web:
    image: nginx:alpine
    # 依赖初始化容器
    depends_on:
      init-service:
        condition: service_completed_successfully
    # 健康检查
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

4.4 网络配置

4.4.1 网络类型

networks:
  # 桥接网络(默认)
  bridge-network:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: br0
  # 主机网络
  host-network:
    driver: host
  # 无网络
  none-network:
    driver: none
  # 覆盖网络(用于Swarm)
  overlay-network:
    driver: overlay
  # 自定义网络
  custom-network:
    driver: bridge
    enable_ipv6: true
    ipam:
      driver: default
      config:
        - subnet: "172.20.0.0/16"
          gateway: "172.20.0.1"
        - subnet: "2001:db8:abcd::/64"
          gateway: "2001:db8:abcd::101"

4.4.2 容器固定IP地址设置

services:
  web:
    image: nginx:alpine
    networks:
      app-net:
        ipv4_address: 172.20.0.10
        ipv6_address: 2001:db8:abcd::10
  db:
    image: postgres:13
    networks:
      app-net:
        ipv4_address: 172.20.0.20
  # 初始化容器也可以设置固定IP
  init-container:
    image: alpine:latest
    networks:
      app-net:
        ipv4_address: 172.20.0.100
    restart: "no"
networks:
  app-net:
    driver: bridge
    ipam:
      config:
        - subnet: "172.20.0.0/16"
        - subnet: "2001:db8:abcd::/64"

注意事项:

  • 必须使用自定义网络
  • IP地址必须在子网范围内
  • 避免IP地址冲突
  • 适用于需要静态IP的场景(如负载均衡配置)

4.4.3 容器固定MAC地址设置

services:
  database:
    image: postgres:13
    mac_address: "02:42:ac:11:00:02"
    networks:
      - app-network
  # 初始化容器MAC地址
  init-service:
    image: alpine:latest
    mac_address: "02:42:ac:11:00:ff"
    restart: "no"

MAC地址设置指南:

  1. 格式:"XX:XX:XX:XX:XX:XX"(字符串形式)
  2. 前三个字节通常为 02:42:ac
  3. 后三个字节自定义
  4. 确保网络内唯一性

完整网络配置示例:

version: '3.8'
services:
  # 初始化容器
  init-setup:
    image: alpine:latest
    container_name: init-setup
    command: >
      sh -c "
        echo '初始化网络设置...' &&
        # 可以在这里配置网络相关初始化
        echo '172.20.0.10 web-server' >> /etc/hosts &&
        echo '初始化完成'
      "
    networks:
      app-network:
        ipv4_address: 172.20.0.250
    restart: "no"
  web1:
    image: nginx:alpine
    container_name: web-server-1
    networks:
      app-network:
        ipv4_address: 172.20.0.10
    depends_on:
      init-setup:
        condition: service_completed_successfully
networks:
  app-network:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: "172.20.0.0/24"
          gateway: "172.20.0.1"

4.5 卷配置

4.5.1 卷(Volumes)类型与使用

定义卷:

volumes:
  db-data:  # 卷名称
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /path/to/data
  # 初始化数据卷
  init-data:
    external: true

使用卷:

services:
  # 初始化容器使用卷
  data-init:
    image: alpine:latest
    volumes:
      - init-data:/data
    command: >
      sh -c "
        echo '初始化数据...' &&
        echo 'Initial data' > /data/init.txt &&
        echo '数据初始化完成'
      "
    restart: "no"
  database:
    image: postgres:13
    volumes:
      - db-data:/var/lib/postgresql/data
      - init-data:/docker-entrypoint-initdb.d:ro

4.5.2 主机路径(Host Path)绑定

services:
  # 初始化容器使用主机路径
  config-init:
    image: alpine:latest
    volumes:
      # 主机绝对路径
      - /host/init-scripts:/scripts:ro
      # 相对路径(相对于docker-compose.yml)
      - ./init:/app/init:ro
      # 只读绑定初始化配置
      - ./config/init.conf:/etc/app/init.conf:ro
      # 初始化数据写入
      - ./data/init:/data:rw
    command: /scripts/init.sh
    restart: "no"
  app:
    image: myapp:latest
    volumes:
      # 共享初始化容器准备的数据
      - ./data/init:/app/data:ro

4.5.3 卷与主机路径的区别

特性 卷(Volume) 主机路径绑定(Bind Mount)
存储位置 Docker管理的位置(/var/lib/docker/volumes/ 主机文件系统任意位置
生命周期 独立于容器,需显式删除 与主机文件同生命周期
移植性 高,不依赖主机路径 低,依赖特定主机路径
性能 通常较好 依赖于主机文件系统
备份 Docker工具支持 需手动备份
多主机 支持卷驱动(如NFS) 需要共享文件系统
权限 Docker管理,UID/GUID一致 使用主机文件权限

卷(Volume)在初始化场景示例:

volumes:
  mysql-init:
    name: mysql-init-data
    labels:
      - "purpose=init"
services:
  mysql-init:
    image: mysql:8.0
    volumes:
      - mysql-init:/docker-entrypoint-initdb.d
    command: >
      sh -c "
        echo '准备初始化数据...' &&
        cp -r /init-data/* /docker-entrypoint-initdb.d/ &&
        echo '数据准备完成'
      "
    restart: "no"
  mysql:
    image: mysql:8.0
    volumes:
      - mysql-data:/var/lib/mysql
      - mysql-init:/docker-entrypoint-initdb.d:ro
    # 容器启动时会自动执行init目录下的SQL文件

主机路径绑定在初始化场景示例:

services:
  app-init:
    image: alpine:latest
    volumes:
      # 开发时挂载初始化脚本
      - ./init-scripts:/scripts:ro
      # 初始化输出目录
      - ./generated-config:/output
    command: >
      sh -c "
        echo '生成配置文件...' &&
        /scripts/generate-config.sh /output &&
        echo '配置生成完成'
      "
    restart: "no"
  app:
    image: myapp:latest
    volumes:
      # 使用初始化容器生成的配置
      - ./generated-config:/app/config:ro
      # 挂载源代码
      - ./src:/app/src
    depends_on:
      app-init:
        condition: service_completed_successfully

4.5.4 适用场景对比

使用卷(Volumes)的初始化场景:

  1. 数据库初始化数据

    volumes:
     - db-init-scripts:/docker-entrypoint-initdb.d
  2. 共享初始化配置

    volumes:
     - shared-init-data:/shared-init
  3. 初始化容器传递数据

    # 初始化容器写入数据
    init-container:
     volumes:
       - config-volume:/config
     command: generate-config.sh /config
    # 应用容器读取数据
    app:
     volumes:
       - config-volume:/app/config:ro
  4. 备份和恢复初始化数据

    # 备份初始化卷
    docker run --rm -v init-data:/data -v $(pwd):/backup \
     alpine tar czf /backup/init-backup.tar.gz /data

使用主机路径绑定(Bind Mounts)的初始化场景:

  1. 开发环境初始化脚本

    volumes:
     - ./init:/app/init
     - ./scripts/init.sh:/scripts/init.sh
  2. 配置文件生成

    volumes:
     - ./templates:/templates:ro
     - ./generated:/output
  3. 主机文件处理

    volumes:
     - /etc/localtime:/etc/localtime:ro
     - /etc/timezone:/etc/timezone:ro
  4. 初始化日志收集

    volumes:
     - ./logs/init:/var/log/init

初始化专用临时卷:

services:
  init-process:
    image: alpine:latest
    volumes:
      # 临时卷用于中间处理
      - type: volume
        source: temp-data
        target: /temp
        volume:
          nocopy: true
    command: >
      sh -c "
        echo '处理数据...' &&
        process-data.sh /temp/input /temp/output &&
        echo '处理完成'
      "
    restart: "no"
  app-process:
    image: app:latest
    volumes:
      - type: volume
        source: temp-data
        target: /processed-data
        volume:
          nocopy: true
    depends_on:
      - init-process
volumes:
  temp-data:
    driver: local
    driver_opts:
      type: tmpfs
      device: tmpfs

4.6 环境变量配置

services:
  # 初始化容器环境变量
  init-config:
    image: alpine:latest
    # 方式1:直接定义
    environment:
      INIT_MODE: full
      DATABASE_HOST: db
      DATABASE_PORT: 5432
      INIT_TIMEOUT: 300
    # 方式2:数组形式
    environment:
      - INIT_MODE=full
      - DATABASE_HOST=db
      - DATABASE_PORT=5432
    # 方式3:从文件加载
    env_file:
      - .env.init
      - .env.secret
    # 方式4:扩展环境变量
    environment:
      - DB_HOST=${DB_HOST:-localhost}
      - DB_PORT=${DB_PORT:-5432}
      - INIT_RETRIES=${INIT_RETRIES:-3}
    # 使用已存在的环境变量
    environment:
      - HOSTNAME
      - USER
    command: >
      sh -c "
        echo '初始化模式: \$INIT_MODE' &&
        echo '数据库主机: \$DATABASE_HOST' &&
        echo '超时时间: \$INIT_TIMEOUT秒' &&
        # 执行初始化逻辑
        if [ \"\$INIT_MODE\" = \"full\" ]; then
          echo '执行完整初始化...'
        else
          echo '执行快速初始化...'
        fi
      "
    restart: "no"
  app:
    image: myapp:latest
    environment:
      # 可以使用初始化容器设置的环境
      - CONFIG_PATH=/config
      - INIT_COMPLETE=true
    depends_on:
      init-config:
        condition: service_completed_successfully

.env.init 文件示例:

# 初始化环境变量配置文件
INIT_MODE=full
DATABASE_HOST=postgres
DATABASE_PORT=5432
DATABASE_NAME=mydb
INIT_TIMEOUT=300
SKIP_DATA_IMPORT=false
# 阶段控制
INIT_PHASE_1=true
INIT_PHASE_2=true
INIT_PHASE_3=false

五、常用命令

5.1 启动与停止

# 启动所有服务(包含初始化容器)
docker-compose up -d
# 启动并构建镜像
docker-compose up -d --build
# 启动指定服务(跳过初始化)
docker-compose up -d web db
# 只运行初始化容器
docker-compose up init-db
docker-compose up init-config
# 停止所有服务
docker-compose down
# 停止并删除卷
docker-compose down -v
# 停止但不删除资源
docker-compose stop
# 重启服务
docker-compose restart
# 重新运行初始化
docker-compose rm -f init-db
docker-compose up -d init-db
# 查看初始化状态
docker-compose ps init-db

5.2 构建与重建

# 构建镜像(包括初始化镜像)
docker-compose build
# 构建指定服务
docker-compose build web init-service
# 强制重建
docker-compose build --no-cache
# 构建初始化镜像
docker-compose build init-container
# 拉取镜像
docker-compose pull
# 拉取并启动
docker-compose up -d --pull always

5.3 查看状态

# 查看所有容器状态
docker-compose ps
# 详细状态
docker-compose ps -a
# 查看初始化容器状态
docker-compose ps | grep init
# 查看服务日志
docker-compose logs
# 查看初始化容器日志
docker-compose logs init-db
docker-compose logs init-config
# 跟踪初始化日志
docker-compose logs -f init-db
# 查看资源使用
docker-compose top
# 查看初始化容器退出代码
docker inspect $(docker-compose ps -q init-db) --format='{{.State.ExitCode}}'

5.4 日志管理

# 查看所有日志
docker-compose logs
# 查看并跟随日志
docker-compose logs -f
# 查看初始化日志
docker-compose logs init-service
# 查看最近N行
docker-compose logs --tail=100 init-container
# 带时间戳
docker-compose logs -t
# 过滤初始化日志
docker-compose logs init-db | grep -i "error\|complete\|success"
# 导出初始化日志
docker-compose logs init-container > init_logs.txt
# 清理日志
docker-compose logs --tail=0 -f

5.5 执行命令

# 在初始化容器执行命令(运行前)
docker-compose run --rm init-db sh
# 在运行中的容器执行命令
docker-compose exec web bash
# 在初始化容器执行诊断命令
docker-compose run --rm init-service env
docker-compose run --rm init-service ls -la /scripts
# 手动运行初始化脚本
docker-compose run --rm init-db /scripts/init.sh
# 测试初始化连接
docker-compose run --rm init-db ping db
docker-compose run --rm init-db nc -zv db 5432

5.6 扩展服务

# 扩展服务实例
docker-compose up -d --scale web=3
# 查看扩展状态
docker-compose ps
# 负载均衡示例
docker-compose up -d --scale web=5
# 注意:初始化容器通常不进行扩展
# 初始化容器应该只运行一个实例

六、实战示例

6.1 示例1:Web应用 + 数据库 + 初始化容器

项目结构:

myapp/
├── docker-compose.yml
├── init-scripts/
│   ├── init-db.sh
│   ├── init-redis.sh
│   └── wait-for-it.sh
├── web/
│   ├── Dockerfile
│   └── app.py
├── sql/
│   ├── schema.sql
│   └── seed.sql
└── .env

docker-compose.yml:

version: '3.8'
services:
  # 阶段1:等待基础设施
  wait-for-services:
    image: alpine:latest
    container_name: wait-for-infra
    command: >
      sh -c "
        echo '等待基础设施服务就绪...' &&
        # 等待PostgreSQL
        echo '等待PostgreSQL...' &&
        until nc -z postgres 5432; do
          echo 'PostgreSQL未就绪,等待2秒...'
          sleep 2
        done &&
        echo 'PostgreSQL就绪 ✓' &&
        # 等待Redis
        echo '等待Redis...' &&
        until nc -z redis 6379; do
          echo 'Redis未就绪,等待2秒...'
          sleep 2
        done &&
        echo 'Redis就绪 ✓' &&
        echo '所有基础设施就绪!'
      "
    networks:
      - app-network
    restart: "no"
    depends_on:
      - postgres
      - redis
  # 阶段2:数据库初始化
  init-database:
    image: postgres:13-alpine
    container_name: init-db
    command: >
      sh -c "
        echo '开始数据库初始化...' &&
        # 等待数据库完全就绪
        until pg_isready -h postgres -U postgres; do
          echo '等待数据库连接...'
          sleep 2
        done &&
        echo '创建应用数据库和用户...' &&
        psql -h postgres -U postgres -c '
          CREATE DATABASE IF NOT EXISTS appdb;
          CREATE USER IF NOT EXISTS appuser WITH PASSWORD '\''apppassword'\'';
          GRANT ALL PRIVILEGES ON DATABASE appdb TO appuser;
        ' || echo '数据库/用户可能已存在' &&
        echo '执行数据库脚本...' &&
        for sql_file in /sql/*.sql; do
          echo \"执行: \${sql_file}\" &&
          psql -h postgres -U postgres -d appdb -f \${sql_file} || true
        done &&
        echo '数据库初始化完成!'
      "
    volumes:
      - ./sql:/sql:ro
    environment:
      PGPASSWORD: postgres
    networks:
      - app-network
    restart: "no"
    depends_on:
      wait-for-services:
        condition: service_completed_successfully
  # 阶段3:Redis初始化
  init-redis:
    image: redis:6-alpine
    container_name: init-redis
    command: >
      sh -c "
        echo '开始Redis初始化...' &&
        # 等待Redis就绪
        until redis-cli -h redis ping | grep PONG; do
          echo '等待Redis连接...'
          sleep 1
        done &&
        echo '设置Redis初始数据...' &&
        redis-cli -h redis << 'EOF'
          HMSET app:config version \"1.0.0\"
          HMSET app:config environment \"production\"
          HMSET app:config initialized \"true\"
          HMSET app:config init_time \"\$(date '+%Y-%m-%d %H:%M:%S')\"
          # 设置缓存键
          SET cache:settings:max_users 1000
          SET cache:settings:session_timeout 3600
          # 初始化集合
          SADD app:default_roles admin user guest
        EOF &&
        echo 'Redis初始化完成!'
      "
    networks:
      - app-network
    restart: "no"
    depends_on:
      wait-for-services:
        condition: service_completed_successfully
  # 阶段4:应用初始化
  init-app:
    image: alpine:latest
    container_name: init-app
    command: >
      sh -c "
        echo '开始应用初始化...' &&
        # 等待数据库初始化完成
        echo '等待数据库初始化完成...' &&
        until docker inspect init-db --format='{{.State.Status}}' | grep -q exited; do
          sleep 2
        done &&
        # 检查数据库初始化是否成功
        DB_EXIT_CODE=\$(docker inspect init-db --format='{{.State.ExitCode}}')
        if [ \"\$DB_EXIT_CODE\" -ne 0 ]; then
          echo '数据库初始化失败!'
          exit 1
        fi &&
        echo '生成应用配置文件...' &&
        cat > /config/app.conf << EOF
        database:
          host: postgres
          port: 5432
          name: appdb
          user: appuser
          password: apppassword
        redis:
          host: redis
          port: 6379
        app:
          name: MyApplication
          version: 1.0.0
          environment: production
        EOF &&
        echo '创建必要的目录结构...' &&
        mkdir -p /data/uploads /data/logs /data/cache &&
        chmod 755 /data/uploads /data/logs /data/cache &&
        echo '应用初始化完成!'
      "
    volumes:
      - ./config:/config
      - ./data:/data
    networks:
      - app-network
    restart: "no"
    depends_on:
      init-database:
        condition: service_completed_successfully
      init-redis:
        condition: service_completed_successfully
  # 基础设施服务
  postgres:
    image: postgres:13-alpine
    container_name: app-postgres
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USER: postgres
      POSTGRES_DB: postgres
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./sql:/docker-entrypoint-initdb.d:ro
    ports:
      - "5432:5432"
    networks:
      - app-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 20
      start_period: 10s
    restart: unless-stopped
  redis:
    image: redis:6-alpine
    container_name: app-redis
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    ports:
      - "6379:6379"
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10
    restart: unless-stopped
  # 应用服务
  webapp:
    build: ./web
    container_name: app-web
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://appuser:apppassword@postgres:5432/appdb
      - REDIS_URL=redis://redis:6379/0
      - CONFIG_PATH=/app/config
    volumes:
      - ./config:/app/config:ro
      - ./data/uploads:/app/uploads
      - ./data/logs:/app/logs
      - ./data/cache:/app/cache
    networks:
      - app-network
    depends_on:
      init-app:
        condition: service_completed_successfully
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    restart: unless-stopped
networks:
  app-network:
    driver: bridge
    ipam:
      config:
        - subnet: "172.20.0.0/16"
          gateway: "172.20.0.1"
volumes:
  postgres-data:
    name: app-postgres-data
  redis-data:
    name: app-redis-data

操作命令:

# 1. 启动基础设施
docker-compose up -d postgres redis
# 2. 运行初始化序列
docker-compose up wait-for-services
docker-compose up init-database
docker-compose up init-redis
docker-compose up init-app
# 3. 启动应用
docker-compose up -d webapp
# 4. 查看初始化状态
docker-compose ps | grep init
# 5. 查看初始化日志
docker-compose logs init-database
docker-compose logs init-redis
docker-compose logs init-app
# 6. 一键启动(不推荐用于生产)
docker-compose up -d

6.2 示例2:多服务应用 + 复杂初始化

version: '3.8'
x-init-config: &init-config
  restart: "no"
  networks:
    - app-network
  logging:
    driver: "json-file"
    options:
      max-size: "10m"
      max-file: "3"
x-app-config: &app-config
  restart: unless-stopped
  networks:
    - app-network
services:
  # 阶段0:基础设施检查
  check-prerequisites:
    <<: *init-config
    image: alpine:latest
    container_name: check-prereq
    command: >
      sh -c "
        echo '检查系统前提条件...' &&
        # 检查必要的目录
        for dir in ./data ./config ./logs; do
          if [ ! -d \"\$dir\" ]; then
            echo \"创建目录: \$dir\" &&
            mkdir -p \"\$dir\" || {
              echo \"无法创建目录: \$dir\"
              exit 1
            }
          fi
        done &&
        # 检查权限
        for dir in ./data ./logs; do
          if [ ! -w \"\$dir\" ]; then
            echo \"目录不可写: \$dir\"
            exit 1
          fi
        done &&
        # 检查环境变量
        if [ -z \"\${REQUIRED_ENV}\" ]; then
          echo '警告: REQUIRED_ENV 未设置'
        fi &&
        echo '前提条件检查通过!'
      "
    volumes:
      - .:/app:ro
  # 阶段1:数据库初始化
  db-migration:
    <<: *init-config
    image: myapp-db-migrate:latest
    container_name: db-migrate
    build:
      context: ./db
      dockerfile: Dockerfile.migrate
    environment:
      DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres
      MIGRATION_DIR: /migrations
    volumes:
      - ./db/migrations:/migrations:ro
    command: >
      sh -c "
        echo '开始数据库迁移...' &&
        # 等待数据库
        until pg_isready -h postgres -U postgres; do
          echo '等待数据库...'
          sleep 2
        done &&
        # 运行迁移
        echo '运行迁移脚本...' &&
        for migration in \$(ls /migrations/*.sql | sort); do
          echo \"应用迁移: \$(basename \$migration)\" &&
          psql -h postgres -U postgres -f \$migration || {
            echo \"迁移失败: \$migration\"
            exit 1
          }
        done &&
        echo '数据库迁移完成!'
      "
    depends_on:
      - postgres
      - check-prerequisites
  # 阶段2:数据种子
  db-seed:
    <<: *init-config
    image: postgres:13-alpine
    container_name: db-seed
    environment:
      PGPASSWORD: postgres
    volumes:
      - ./db/seed:/seed:ro
    command: >
      sh -c "
        echo '开始数据种子...' &&
        # 等待迁移完成
        until docker inspect db-migrate --format='{{.State.Status}}' | grep -q exited; do
          sleep 2
        done &&
        MIGRATE_EXIT=\$(docker inspect db-migrate --format='{{.State.ExitCode}}')
        if [ \"\$MIGRATE_EXIT\" -ne 0 ]; then
          echo '数据库迁移失败,跳过种子'
          exit 1
        fi &&
        echo '导入种子数据...' &&
        for seed_file in /seed/*.sql; do
          echo \"导入: \$(basename \$seed_file)\" &&
          psql -h postgres -U postgres -d appdb -f \$seed_file || {
            echo \"种子导入失败: \$seed_file\"
            exit 1
          }
        done &&
        echo '数据种子完成!'
      "
    depends_on:
      - db-migration
  # 阶段3:缓存预热
  cache-warmup:
    <<: *init-config
    image: redis:6-alpine
    container_name: cache-warmup
    command: >
      sh -c "
        echo '开始缓存预热...' &&
        # 等待Redis
        until redis-cli -h redis ping | grep PONG; do
          sleep 1
        done &&
        # 预热数据
        echo '预热缓存数据...' &&
        redis-cli -h redis << 'EOF'
          # 设置配置
          HMSET app:cache:config max_size 1000000
          HMSET app:cache:config ttl 3600
          # 预热常用数据
          SET cache:homepage '<html>...</html>'
          SET cache:menu '[{\"id\":1,\"name\":\"Home\"}]'
          # 统计信息
          HMSET app:stats startups 1
          HMSET app:stats last_start \"\$(date)\"
        EOF &&
        echo '缓存预热完成!'
      "
    depends_on:
      - redis
      - db-seed
  # 阶段4:文件系统准备
  fs-prepare:
    <<: *init-config
    image: alpine:latest
    container_name: fs-prepare
    volumes:
      - ./data:/data
      - ./uploads:/uploads
      - ./static:/static
    command: >
      sh -c "
        echo '准备文件系统...' &&
        # 创建目录结构
        mkdir -p \
          /data/cache \
          /data/sessions \
          /data/temp \
          /uploads/images \
          /uploads/documents \
          /uploads/videos \
          /static/css \
          /static/js \
          /static/images &&
        # 设置权限
        chmod -R 755 /static &&
        chmod -R 775 /uploads &&
        chmod -R 777 /data/cache /data/temp &&
        # 复制默认文件
        if [ -d '/app/default-static' ]; then
          echo '复制默认静态文件...'
          cp -r /app/default-static/* /static/
        fi &&
        # 创建符号链接
        ln -sf /uploads /static/uploads || true &&
        echo '文件系统准备完成!'
      "
    depends_on:
      - cache-warmup
  # 阶段5:最终验证
  final-verification:
    <<: *init-config
    image: alpine:latest
    container_name: final-verify
    command: >
      sh -c "
        echo '执行最终验证...' &&
        # 验证服务连通性
        echo '验证数据库连接...' &&
        if ! pg_isready -h postgres -U postgres; then
          echo '数据库连接失败'
          exit 1
        fi &&
        echo '验证Redis连接...' &&
        if ! redis-cli -h redis ping | grep -q PONG; then
          echo 'Redis连接失败'
          exit 1
        fi &&
        # 验证文件系统
        echo '验证文件系统...' &&
        for dir in /data/cache /uploads /static; do
          if [ ! -d \"\$dir\" ]; then
            echo \"目录不存在: \$dir\"
            exit 1
          fi
        done &&
        # 验证数据完整性
        echo '验证数据完整性...' &&
        RECORD_COUNT=\$(psql -h postgres -U postgres -d appdb -t -c \
          \"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public'\" | tr -d '[:space:]')
        if [ \"\$RECORD_COUNT\" -eq 0 ]; then
          echo '数据库中没有表'
          exit 1
        fi &&
        echo '=======================================' &&
        echo '所有验证通过!应用可以启动。' &&
        echo '=======================================' &&
        # 创建完成标志
        touch /tmp/app.ready &&
        echo '初始化完成时间: \$(date)' > /tmp/init-complete.txt
      "
    volumes:
      - ./data:/data:ro
      - ./uploads:/uploads:ro
      - ./static:/static:ro
    depends_on:
      - fs-prepare
  # 基础设施服务
  postgres:
    <<: *app-config
    image: postgres:13-alpine
    container_name: app-postgres
    environment:
      POSTGRES_PASSWORD: postgres
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 20
  redis:
    <<: *app-config
    image: redis:6-alpine
    container_name: app-redis
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10
  # 应用服务
  api:
    <<: *app-config
    build: ./api
    container_name: app-api
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=postgres
      - REDIS_HOST=redis
      - APP_ENV=production
    volumes:
      - ./data:/app/data
      - ./uploads:/app/uploads
      - ./static:/app/static
    depends_on:
      final-verification:
        condition: service_completed_successfully
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
  web:
    <<: *app-config
    image: nginx:alpine
    container_name: app-web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./static:/usr/share/nginx/html:ro
      - ./uploads:/usr/share/nginx/html/uploads
    depends_on:
      - api
      - final-verification
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 30s
      timeout: 10s
      retries: 3
networks:
  app-network:
    driver: bridge
volumes:
  postgres-data:
  redis-data:

6.3 示例3:开发环境配置 + 初始化

version: '3.8'
services:
  # 开发数据库
  postgres-dev:
    image: postgres:13-alpine
    container_name: postgres-dev
    environment:
      POSTGRES_PASSWORD: devpassword
      POSTGRES_USER: devuser
      POSTGRES_DB: devdb
    ports:
      - "5432:5432"
    volumes:
      - postgres-dev-data:/var/lib/postgresql/data
      - ./dev-init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    networks:
      - dev-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U devuser"]
      interval: 5s
      timeout: 5s
      retries: 20
  # 开发环境初始化
  dev-setup:
    image: alpine:latest
    container_name: dev-setup
    command: >
      sh -c "
        echo '设置开发环境...' &&
        # 等待数据库
        until pg_isready -h postgres-dev -U devuser; do
          sleep 2
        done &&
        echo '安装开发依赖...' &&
        apk add --no-cache postgresql-client curl jq &&
        echo '设置测试数据...' &&
        psql -h postgres-dev -U devuser -d devdb << 'EOF'
          -- 创建测试表
          CREATE TABLE IF NOT EXISTS test_users (
            id SERIAL PRIMARY KEY,
            username VARCHAR(50) UNIQUE NOT NULL,
            email VARCHAR(100) UNIQUE NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
          );
          -- 插入测试数据
          INSERT INTO test_users (username, email) VALUES
            ('test1', 'test1@example.com'),
            ('test2', 'test2@example.com'),
            ('test3', 'test3@example.com')
          ON CONFLICT (username) DO NOTHING;
        EOF &&
        echo '创建开发配置文件...' &&
        mkdir -p /app/config &&
        cat > /app/config/development.yaml << 'EOF'
        database:
          host: postgres-dev
          port: 5432
          database: devdb
          username: devuser
          password: devpassword
        server:
          port: 3000
          debug: true
          hot_reload: true
        features:
          enable_debug_tools: true
          enable_test_routes: true
        EOF &&
        echo '开发环境设置完成!'
      "
    volumes:
      - ./config:/app/config
      - ./src:/app/src:ro
    networks:
      - dev-network
    depends_on:
      postgres-dev:
        condition: service_healthy
    restart: "no"
  # 开发服务器
  dev-server:
    build:
      context: .
      dockerfile: Dockerfile.dev
    container_name: dev-server
    ports:
      - "3000:3000"
      - "9229:9229"  # Node.js调试端口
    volumes:
      - ./src:/app/src
      - ./config:/app/config:ro
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - DEBUG=true
      - CHOKIDAR_USEPOLLING=true
    networks:
      - dev-network
    depends_on:
      dev-setup:
        condition: service_completed_successfully
    command: npm run dev
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
  # 开发工具
  adminer:
    image: adminer:latest
    container_name: dev-adminer
    ports:
      - "8080:8080"
    environment:
      ADMINER_DESIGN: dracula
      ADMINER_DEFAULT_SERVER: postgres-dev
    networks:
      - dev-network
    restart: unless-stopped
networks:
  dev-network:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: br-dev
volumes:
  postgres-dev-data:

6.4 示例4:生产环境配置 + 初始化容器

version: '3.8'
x-common: &common
  logging:
    driver: "json-file"
    options:
      max-size: "10m"
      max-file: "3"
x-init: &init-common
  <<: *common
  restart: "no"
  networks:
    - production-network
services:
  # 初始化序列
  # 1. 基础设施检查
  health-check:
    <<: *init-common
    image: alpine:latest
    container_name: health-check
    command: >
      sh -c "
        echo '执行健康检查...' &&
        # 检查必要的卷
        echo '检查数据卷...' &&
        for vol in postgres-data redis-data; do
          if docker volume inspect \$vol >/dev/null 2>&1; then
            echo \"卷存在: \$vol ✓\"
          else
            echo \"卷不存在: \$vol\"
          fi
        done &&
        # 检查网络
        echo '检查网络...' &&
        if docker network inspect production-network >/dev/null 2>&1; then
          echo '网络存在 ✓'
        else
          echo '网络不存在'
        fi &&
        # 检查端口
        echo '检查端口可用性...' &&
        for port in 80 443 5432 6379; do
          if netstat -tuln | grep -q \":\$port \"; then
            echo \"端口被占用: \$port\"
            exit 1
          fi
        done &&
        echo '所有健康检查通过!'
      "
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
  # 2. 数据库备份恢复(如果需要)
  db-restore:
    <<: *init-common
    image: postgres:13-alpine
    container_name: db-restore
    command: >
      sh -c "
        echo '检查数据库恢复...' &&
        # 等待数据库
        until pg_isready -h postgres -U postgres; do
          sleep 2
        done &&
        # 检查是否需要恢复
        if [ -f \"/backup/latest.dump\" ] && [ \"\${RESTORE_DB}\" = \"true\" ]; then
          echo '开始数据库恢复...' &&
          pg_restore -h postgres -U postgres -d postgres -c /backup/latest.dup || {
            echo '数据库恢复失败'
            exit 1
          } &&
          echo '数据库恢复完成!'
        else
          echo '跳过数据库恢复'
        fi
      "
    environment:
      PGPASSWORD: \${DB_PASSWORD}
      RESTORE_DB: \${RESTORE_DB:-false}
    volumes:
      - ./backups:/backup:ro
    depends_on:
      health-check:
        condition: service_completed_successfully
      postgres:
        condition: service_healthy
  # 3. 应用初始化
  app-init:
    <<: *init-common
    build:
      context: ./app
      dockerfile: Dockerfile.init
    container_name: app-init
    environment:
      - NODE_ENV=production
      - DATABASE_URL=\${DATABASE_URL}
      - SECRET_KEY=\${SECRET_KEY}
    volumes:
      - ./app/scripts:/scripts:ro
      - ./app/config:/config
    command: >
      sh -c "
        echo '开始应用初始化...' &&
        # 等待基础设施
        /scripts/wait-for.sh postgres:5432 &&
        /scripts/wait-for.sh redis:6379 &&
        # 运行初始化脚本
        echo '运行数据库迁移...' &&
        node /scripts/migrate.js &&
        echo '创建管理员用户...' &&
        node /scripts/create-admin.js \${ADMIN_EMAIL} \${ADMIN_PASSWORD} &&
        echo '生成静态文件...' &&
        node /scripts/generate-static.js &&
        echo '应用初始化完成!'
      "
    depends_on:
      db-restore:
        condition: service_completed_successfully
      redis:
        condition: service_healthy
  # 基础设施
  postgres:
    <<: *common
    image: postgres:13-alpine
    container_name: production-postgres
    environment:
      POSTGRES_PASSWORD: \${DB_PASSWORD}
      POSTGRES_USER: \${DB_USER}
      POSTGRES_DB: \${DB_NAME}
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./postgres/conf:/etc/postgresql:ro
    networks:
      - production-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U \${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
  redis:
    <<: *common
    image: redis:6-alpine
    container_name: production-redis
    command: redis-server --requirepass \${REDIS_PASSWORD} --appendonly yes
    volumes:
      - redis-data:/data
    networks:
      - production-network
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "\${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
  # 应用
  app:
    <<: *common
    image: \${APP_IMAGE}
    container_name: production-app
    ports:
      - "80:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=\${DATABASE_URL}
      - REDIS_URL=\${REDIS_URL}
    volumes:
      - ./app/config:/app/config:ro
      - ./logs:/app/logs
    networks:
      - production-network
    depends_on:
      app-init:
        condition: service_completed_successfully
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    restart: unless-stopped
networks:
  production-network:
    driver: bridge
    internal: false
volumes:
  postgres-data:
    driver: local
  redis-data:
    driver: local

七、高级特性

7.1 多环境配置

目录结构:

project/
├── docker-compose.yml
├── docker-compose.override.yml
├── docker-compose.prod.yml
├── docker-compose.init.yml
├── .env
└── scripts/
    ├── init-dev.sh
    └── init-prod.sh

基础配置(docker-compose.yml):

version: '3.8'
services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    environment:
      - APP_ENV=${APP_ENV:-development}
    networks:
      - app-network
networks:
  app-network:

初始化配置(docker-compose.init.yml):

services:
  # 通用初始化容器
  init-common:
    image: alpine:latest
    command: >
      sh -c "
        echo '通用初始化任务...' &&
        echo '环境: ${APP_ENV}' &&
        echo '初始化完成'
      "
    restart: "no"
    networks:
      - app-network
  # 数据库初始化
  init-db:
    extends:
      file: docker-compose.yml
      service: init-common
    container_name: init-db-${APP_ENV}
    environment:
      - DB_HOST=${DB_HOST}
      - DB_PORT=${DB_PORT}
    command: >
      sh -c "
        echo '数据库初始化: ${APP_ENV}' &&
        /scripts/init-db-${APP_ENV}.sh
      "
    volumes:
      - ./scripts:/scripts:ro
    depends_on:
      - db
  db:
    image: postgres:13
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
  web:
    depends_on:
      init-db:
        condition: service_completed_successfully

开发环境覆盖(docker-compose.override.yml):

services:
  init-db:
    environment:
      - INIT_MODE=development
      - SEED_DATA=true
    volumes:
      - ./dev-data:/seed-data:ro
  web:
    build: .
    volumes:
      - ./src:/app/src
    environment:
      - DEBUG=true
      - HOT_RELOAD=true

生产环境配置(docker-compose.prod.yml):

services:
  init-db:
    environment:
      - INIT_MODE=production
      - SEED_DATA=false
    volumes:
      - ./prod-data:/seed-data:ro
  web:
    image: registry.example.com/myapp:${TAG}
    restart: always
    environment:
      - DEBUG=false
      - LOG_LEVEL=info
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
  db:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G

使用不同配置:

# 开发环境(加载override和init配置)
docker-compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.init.yml up -d
# 生产环境
docker-compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.init.yml up -d
# 只运行初始化
docker-compose -f docker-compose.yml -f docker-compose.init.yml up init-db
# 自定义环境
APP_ENV=staging docker-compose -f docker-compose.yml -f docker-compose.init.yml up -d

7.2 扩展配置

version: '3.8'
# 定义扩展字段
x-init-config: &init-config
  restart: "no"
  networks:
    - app-network
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "3"
  healthcheck:
    test: ["CMD", "echo", "init-container-no-healthcheck"]
    interval: 5s
    timeout: 5s
    retries: 1
x-db-init: &db-init
  <<: *init-config
  image: postgres:13-alpine
  environment:
    PGPASSWORD: ${DB_PASSWORD}
  volumes:
    - ./sql:/sql:ro
x-app-init: &app-init
  <<: *init-config
  image: alpine:latest
  working_dir: /app
  user: "1000:1000"
services:
  # 数据库初始化
  init-db-schema:
    <<: *db-init
    container_name: init-db-schema
    command: >
      sh -c "
        until pg_isready -h db -U ${DB_USER}; do sleep 2; done &&
        for f in /sql/schema/*.sql; do
          echo \"执行: \$(basename \$f)\" &&
          psql -h db -U ${DB_USER} -d ${DB_NAME} -f \$f
        done
      "
    depends_on:
      db:
        condition: service_healthy
  init-db-data:
    <<: *db-init
    container_name: init-db-data
    command: >
      sh -c "
        until pg_isready -h db -U ${DB_USER}; do sleep 2; done &&
        for f in /sql/data/*.sql; do
          echo \"导入: \$(basename \$f)\" &&
          psql -h db -U ${DB_USER} -d ${DB_NAME} -f \$f
        done
      "
    depends_on:
      init-db-schema:
        condition: service_completed_successfully
  # 应用初始化
  init-app-config:
    <<: *app-init
    container_name: init-app-config
    volumes:
      - ./config:/config:ro
      - ./generated-config:/output
    command: >
      sh -c "
        echo '生成配置文件...' &&
        envsubst < /config/template.yaml > /output/config.yaml &&
        echo '配置生成完成'
      "
    depends_on:
      init-db-data:
        condition: service_completed_successfully
  init-app-setup:
    <<: *app-init
    container_name: init-app-setup
    volumes:
      - ./scripts:/scripts:ro
      - ./data:/data
    command: >
      sh -c "
        echo '设置应用数据...' &&
        /scripts/setup.sh /data &&
        echo '应用设置完成'
      "
    depends_on:
      init-app-config:
        condition: service_completed_successfully
  # 主服务
  db:
    image: postgres:13-alpine
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    healthcheck: ...
  app:
    image: myapp:latest
    depends_on:
      init-app-setup:
        condition: service_completed_successfully
    volumes:
      - ./generated-config:/app/config:ro
      - ./data:/app/data:ro

7.3 健康检查

version: '3.8'
services:
  # 初始化容器健康检查示例
  init-service:
    image: alpine:latest
    # 初始化容器通常不需要健康检查
    # 因为它们很快就会完成
    healthcheck:
      disable: true
    command: >
      sh -c "
        echo '开始初始化...' &&
        # 长时间运行的任务
        sleep 30 &&
        echo '初始化完成' &&
        # 创建完成标志
        touch /tmp/init.complete
      "
    # 通过卷共享完成状态
    volumes:
      - init-status:/status
  # 依赖服务的健康检查
  db:
    image: postgres:13
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 20
      start_period: 10s
  redis:
    image: redis:6-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 5s
  # 应用容器的健康检查
  app:
    image: myapp:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    depends_on:
      init-service:
        condition: service_completed_successfully
      db:
        condition: service_healthy
      redis:
        condition: service_healthy

7.4 资源限制

version: '3.8'
services:
  # 初始化容器资源限制
  cpu-intensive-init:
    image: myapp:latest
    # 容器资源限制
    deploy:
      resources:
        limits:
          cpus: '2.0'      # 最多2个CPU核心
          memory: 1G       # 最多1GB内存
          pids: 200        # 最多200个进程
        reservations:
          cpus: '0.5'      # 至少0.5个CPU核心
          memory: 256M     # 至少256MB内存
    # 或者使用旧格式
    cpus: '2.0'
    mem_limit: 1G
    mem_reservation: 256M
    cpu_shares: 512
    cpu_quota: 200000
    cpu_period: 100000
    cpuset: '0-3'
    # 设备限制
    devices:
      - "/dev/sda:/dev/xvda:rwm"
    # 内核参数
    sysctls:
      net.core.somaxconn: 1024
      net.ipv4.tcp_syncookies: 0
    # 共享内存
    shm_size: '512m'
    # 临时文件系统
    tmpfs:
      - /tmp:size=100m,mode=1777
    # 特权模式
    privileged: false
    # 内核功能
    cap_add:
      - SYS_PTRACE
    cap_drop:
      - SYS_ADMIN
    # 安全选项
    security_opt:
      - label=user:USER
      - label=role:ROLE
      - no-new-privileges:true
    command: "data-processing-script"
    restart: "no"
  # 轻量级初始化容器
  lightweight-init:
    image: alpine:latest
    deploy:
      resources:
        limits:
          cpus: '0.2'
          memory: 128M
    command: "echo '轻量初始化'"
    restart: "no"
  # 应用容器
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M
    depends_on:
      cpu-intensive-init:
        condition: service_completed_successfully
      lightweight-init:
        condition: service_completed_successfully

7.5 依赖管理

version: '3.8'
services:
  # 阶段1:基础设施
  postgres:
    image: postgres:13-alpine
    healthcheck: ...
  redis:
    image: redis:6-alpine
    healthcheck: ...
  # 阶段2:数据库初始化
  init-db:
    image: postgres:13-alpine
    command: "初始化命令"
    depends_on:
      # 简单依赖
      - postgres
      - redis
      # 带条件的依赖
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    restart: "no"
  # 阶段3:缓存初始化
  init-cache:
    image: redis:6-alpine
    command: "初始化命令"
    depends_on:
      # 依赖初始化容器
      init-db:
        condition: service_completed_successfully
      # 依赖基础设施
      redis:
        condition: service_healthy
    restart: "no"
  # 阶段4:应用初始化
  init-app:
    image: alpine:latest
    command: "初始化命令"
    depends_on:
      # 依赖前序初始化
      init-db:
        condition: service_completed_successfully
      init-cache:
        condition: service_completed_successfully
      # 依赖基础设施
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: "no"
  # 阶段5:应用服务
  web:
    build: .
    depends_on:
      # 依赖所有初始化完成
      init-db:
        condition: service_completed_successfully
      init-cache:
        condition: service_completed_successfully
      init-app:
        condition: service_completed_successfully
    # 链接到其他服务
    links:
      - postgres:database
      - redis:rediscache
    # 扩展依赖
    extends:
      file: common-services.yml
      service: webapp

八、最佳实践

8.1 文件组织

推荐的项目结构:

myapp/
├── docker-compose.yml          # 主配置文件
├── docker-compose.override.yml # 开发环境覆盖
├── docker-compose.prod.yml     # 生产环境配置
├── docker-compose.init.yml     # 初始化配置
├── .env                        # 环境变量
├── .env.example                # 环境变量示例
├── .dockerignore               # Docker忽略文件
├── scripts/
│   ├── init.sh                 # 初始化脚本
│   ├── wait-for-it.sh         # 等待脚本
│   ├── init-db.sh             # 数据库初始化
│   ├── init-redis.sh          # Redis初始化
│   └── init-app.sh            # 应用初始化
├── sql/
│   ├── schema/                # 数据库schema
│   │   ├── 001_init.sql
│   │   └── 002_tables.sql
│   └── data/                  # 初始数据
│       ├── seed_data.sql
│       └── test_data.sql
├── config/
│   ├── development/           # 开发配置
│   ├── production/            # 生产配置
│   └── templates/             # 配置模板
├── init-data/                 # 初始化数据
│   ├── uploads/              # 初始上传文件
│   ├── static/               # 静态文件
│   └── backups/              # 备份文件
├── app/                       # 应用代码
├── monitoring/                # 监控配置
└── logs/                      # 日志目录

.dockerignore 文件:

# 忽略文件
.git
.gitignore
*.log
*.pyc
__pycache__
node_modules
.DS_Store
.env
*.tmp
coverage/
dist/
build/
*.db
*.sqlite3
docker-compose.override.yml
docker-compose.init.yml

8.2 安全性考虑

 version: '3.8'
services:
  # 安全初始化容器示例
  secure-init:
    image: alpine:latest
    # 1. 非root用户运行
    user: "1000:1000"
    # 2. 只读根文件系统
    read_only: true
    # 3. 临时文件系统
    tmpfs:
      - /tmp
      - /run
      - /var/run
    # 4. 安全选项
    security_opt:
      - no-new-privileges:true
      - apparmor:docker-default
    # 5. 移除不需要的内核功能
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    # 6. 资源限制
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
          pids: 20
    # 7. 健康检查(通常禁用)
    healthcheck:
      disable: true
    # 8. 重启策略
    restart: "no"  # 初始化容器只运行一次
    # 9. 只读卷挂载
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./scripts:/scripts:ro
    # 10. 网络安全
    networks:
      internal-net:
        ipv4_address: 172.20.0.250
    # 11. 环境变量保护
    env_file:
      - .env.secret
    environment:
      - SECRET_KEY_FILE=/run/secrets/secret_key
    command: >
      sh -c "
        echo '安全初始化...' &&
        /scripts/secure-init.sh
      "
# 秘密管理
secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    external: true
# 内部网络
networks:
  internal-net:
    internal: true
    driver
发表评论
博客分类