强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 9 章 · 多环境管理:override 文件、profiles 与变量替换

第 9 章 · 多环境管理

9.1 为什么需要多环境管理?

一个典型的应用至少需要以下环境:

环境用途特点
开发 (Development)本地开发热重载、调试端口、详细日志
测试 (Testing)自动化测试独立数据、一次性使用
预发布 (Staging)上线前验证尽量模拟生产
生产 (Production)正式服务高可用、安全、性能优化

核心挑战:如何共享基础配置,同时满足不同环境的差异需求?


9.2 多文件覆盖模式

基本原理

Compose 支持多个 -f 参数,后面的文件会合并或覆盖前面的配置。

docker compose -f compose.yaml -f compose.prod.yaml up -d

基础文件 + 环境覆盖

# compose.yaml — 基础配置(开发环境默认)
services:
  app:
    build:
      context: ./app
      target: development
    ports:
      - "3000:3000"
      - "9229:9229"    # Node.js 调试端口
    volumes:
      - ./app/src:/app/src
    environment:
      NODE_ENV: development
      LOG_LEVEL: debug
    restart: "no"

  db:
    image: postgres:16-alpine
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: devpass

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:
# compose.prod.yaml — 生产覆盖
services:
  app:
    build:
      target: production
    ports:
      - "8080:3000"
    volumes: []          # 清除开发挂载
    environment:
      NODE_ENV: production
      LOG_LEVEL: warn
      DATABASE_URL: ${DATABASE_URL}
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      retries: 3

  db:
    ports: []            # 不暴露数据库端口
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      retries: 5

  redis:
    ports: []            # 不暴露 Redis 端口
    restart: unless-stopped

使用方式

# 开发环境(使用默认 compose.yaml)
docker compose up

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

# 等效简化写法(Compose V2 支持)
COMPOSE_FILE=compose.yaml:compose.prod.yaml docker compose up -d

9.3 合并规则

深度合并 vs 替换

字段类型合并行为
标量值(string, number)直接替换
列表(ports, volumes, env_file)追加(不是替换)
映射(environment, labels)深度合并
services深度合并(按服务名)
# compose.yaml
services:
  app:
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: development
      DEBUG: "true"
# compose.prod.yaml
services:
  app:
    ports:
      - "8080:3000"     # 追加,不会替换 3000:3000
    environment:
      NODE_ENV: production   # 覆盖
      DEBUG: "false"         # 覆盖
      # DEBUG 仍然存在(因为是合并,不是替换整个 environment)

清除列表

替换而非追加列表,需要使用空列表或显式清除:

# compose.prod.yaml
services:
  app:
    volumes: []           # 清除所有 volumes
    ports:
      - "8080:3000"       # 但注意,这会追加而不是替换

⚠️ 列表合并陷阱portsvolumesenv_file 等列表字段会追加而非替换。要完全替换,需要在基础文件中使用空列表,或使用 !override 标记(Compose V2.24+)。

使用 !override(V2.24+)

# compose.prod.yaml
services:
  app:
    volumes: !override    # 强制替换,不追加
      - appdata:/app/data

9.4 Profiles(配置档)

Profiles 是 Compose V2 引入的强大特性,允许按需启动服务

基本用法

services:
  # 基础服务(无 profile,始终启动)
  app:
    image: myapp:latest
    ports:
      - "8080:3000"

  db:
    image: postgres:16-alpine

  # 仅开发时使用
  debug-tools:
    image: busybox:latest
    command: sleep infinity
    profiles:
      - dev

  # 仅测试时使用
  test:
    image: myapp:latest
    command: npm test
    profiles:
      - test

  # 仅监控时使用
  prometheus:
    image: prom/prometheus:latest
    profiles:
      - monitoring

  grafana:
    image: grafana/grafana:latest
    profiles:
      - monitoring

  # 开发 + 测试都用
  mailhog:
    image: mailhog/mailhog:latest
    profiles:
      - dev
      - test

使用 Profiles

# 仅启动无 profile 的服务(app, db)
docker compose up -d

# 启动基础服务 + dev profile 服务
docker compose --profile dev up -d

# 启动多个 profiles
docker compose --profile dev --profile monitoring up -d

# 启动所有服务
docker compose --profile dev --profile test --profile monitoring up -d

# 使用环境变量
COMPOSE_PROFILES=dev,monitoring docker compose up -d

Profiles 设计模式

┌──────────────────────────────────────────────────────┐
│              服务分层                                  │
│                                                      │
│  ┌─────────────────────────────────┐                 │
│  │  核心服务(无 profile)          │                 │
│  │  app / db / redis / nginx       │  ← 始终启动     │
│  └─────────────────────────────────┘                 │
│                                                      │
│  ┌──────────────┐  ┌───────────────┐                 │
│  │ dev profile  │  │ test profile  │                 │
│  │ debug-tools  │  │ test-runner   │                 │
│  │ mailhog      │  │ mailhog       │                 │
│  │ adminer      │  │ mockserver    │                 │
│  └──────────────┘  └───────────────┘                 │
│                                                      │
│  ┌─────────────────────────────────┐                 │
│  │ monitoring profile              │                 │
│  │ prometheus / grafana / cadvisor │                 │
│  └─────────────────────────────────┘                 │
└──────────────────────────────────────────────────────┘

9.5 include 指令(V2.20+)

include 允许将 Compose 配置拆分为多个文件,实现模块化管理。

基本用法

# compose.yaml — 主文件
include:
  - path: ./services/database.yaml
  - path: ./services/cache.yaml
  - path: ./services/app.yaml

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
# services/database.yaml
services:
  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}

volumes:
  pgdata:
# services/cache.yaml
services:
  redis:
    image: redis:7-alpine
# services/app.yaml
services:
  api:
    build: ./api
    environment:
      DATABASE_URL: ${DATABASE_URL}

include 高级选项

include:
  # 基本路径
  - path: ./services/db.yaml

  # 多路径合并(共享相同项目名和网络)
  - path:
      - ./services/web.yaml
      - ./services/api.yaml
    project_directory: .    # 指定项目根目录

  # 从远程 URL 加载
  - path: https://raw.githubusercontent.com/example/compose/main/services/monitoring.yaml

  # 从 Git 仓库加载
  - path: https://github.com/example/compose.git
    # 支持 #branch 或 #tag

💡 include vs 多文件覆盖include 用于组合不同模块(各文件独立声明服务),而 -f 多文件用于覆盖同一服务的不同环境配置。


9.6 变量替换进阶

从系统环境变量获取

export APP_ENV=production
export DB_PASSWORD=supersecret

docker compose up -d
services:
  app:
    environment:
      NODE_ENV: ${APP_ENV}
      DB_PASSWORD: ${DB_PASSWORD}

.env 分层加载

services:
  app:
    env_file:
      - env/common.env
      - env/${APP_ENV:-dev}.env

动态变量生成

services:
  app:
    image: myapp:latest
    environment:
      # 使用 shell 命令生成(通过 .env)
      # 在 .env 中写:HOSTNAME_VALUE=$(hostname)
      # 注意:.env 不支持命令替换,需要在 shell 中 export
# 方式:在启动前设置环境变量
export INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
docker compose up -d

使用 envsubst 预处理

# 模板文件处理
envsubst < compose.template.yaml > compose.yaml
docker compose up -d

9.7 完整的多环境项目结构

myapp/
├── compose.yaml                # 基础配置(开发默认)
├── compose.override.yaml       # 自动覆盖(开发额外配置)
├── compose.prod.yaml           # 生产配置
├── compose.test.yaml           # 测试配置
├── compose.monitoring.yaml     # 监控组件
├── .env                        # 默认环境变量
├── .env.development            # 开发环境变量
├── .env.production             # 生产环境变量
├── .env.example                # 变量模板
├── services/
│   ├── app.yaml
│   ├── db.yaml
│   └── cache.yaml
└── app/
    ├── Dockerfile
    └── ...

compose.yaml(基础)

include:
  - path: ./services/db.yaml
  - path: ./services/cache.yaml

services:
  app:
    build:
      context: ./app
      target: base
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy

  nginx:
    image: nginx:alpine
    depends_on:
      - app

compose.override.yaml(开发环境自动加载)

# 文件名为 compose.override.yaml 时会自动加载,无需 -f 指定

services:
  app:
    build:
      target: development
    ports:
      - "3000:3000"
      - "9229:9229"
    volumes:
      - ./app/src:/app/src
    environment:
      NODE_ENV: development
      LOG_LEVEL: debug

  db:
    ports:
      - "5432:5432"

  redis:
    ports:
      - "6379:6379"

  # 仅开发环境的服务
  adminer:
    image: adminer:latest
    ports:
      - "8081:8080"
    profiles:
      - dev

compose.prod.yaml(生产环境)

services:
  app:
    build:
      target: production
    ports:
      - "8080:3000"
    volumes: !override
      - appdata:/app/data
    environment:
      NODE_ENV: production
      LOG_LEVEL: warn
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      retries: 3

  db:
    ports: !override []
    restart: unless-stopped

  redis:
    ports: !override []
    restart: unless-stopped

  nginx:
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/prod.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certs:/etc/nginx/certs:ro
    restart: unless-stopped

volumes:
  appdata:

使用方式

# 开发环境(自动加载 compose.yaml + compose.override.yaml)
docker compose up

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

# 测试环境
docker compose -f compose.yaml -f compose.test.yaml up --abort-on-container-exit

# 使用 Makefile 简化

Makefile 封装

# Makefile
.PHONY: dev prod test clean

dev:
	docker compose up

dev-d:
	docker compose up -d

prod:
	docker compose -f compose.yaml -f compose.prod.yaml up -d

test:
	docker compose -f compose.yaml -f compose.test.yaml up \
		--abort-on-container-exit --exit-code-from test

logs:
	docker compose logs -f

clean:
	docker compose down -v --rmi local

9.8 动态配置生成

使用 Docker Config 模板

services:
  nginx:
    image: nginx:alpine
    configs:
      - source: nginx_conf
        target: /etc/nginx/nginx.conf

configs:
  nginx_conf:
    content: |
      server {
        listen 80;
        server_name ${SERVER_NAME};
        location / {
          proxy_pass http://app:3000;
        }
      }

⚠️ configs.content 中的 ${} 不会被 Compose 自动替换。需要使用外部工具(如 envsubst)预处理。


9.9 CI/CD 中的多环境管理

GitHub Actions 示例

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main, staging]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set environment
        run: |
          if [ "${{ github.ref }}" = "refs/heads/main" ]; then
            echo "APP_ENV=production" >> $GITHUB_ENV
          else
            echo "APP_ENV=staging" >> $GITHUB_ENV
          fi

      - name: Deploy
        run: |
          docker compose \
            -f compose.yaml \
            -f compose.${APP_ENV}.yaml \
            --env-file .env.${APP_ENV} \
            up -d --build

9.10 小结

概念说明
多文件覆盖-f base.yaml -f overlay.yaml,后面的覆盖前面的
合并规则标量替换,列表追加,映射深度合并
!override强制替换列表字段(V2.24+)
Profiles按需启动服务,--profile dev
include模块化拆分 Compose 配置
.env 分层env/${APP_ENV}.env 按环境加载变量
override 文件compose.override.yaml 自动加载

扩展阅读


上一章:第 8 章 · 构建 ← | 下一章:第 10 章 · 敏感信息 →