第 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" # 但注意,这会追加而不是替换
⚠️ 列表合并陷阱:
ports、volumes、env_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 章 · 敏感信息 →