容器化部署已成标配,而 Docker Compose 则是本地开发、测试环境和小型生产部署的事实标准。但很多团队的 Compose 文件只是一个能跑起来的”大一统”配置——开发、测试、生产共用同一份 docker-compose.yml,导致环境差异带来的坑层出不穷。本文将演示如何用 Docker Compose 实现dev/test/prod 三环境分离,既保持配置的可维护性,又确保各环境的行为严格隔离。
为什么需要多环境配置分离?
先看一个典型痛点:开发环境里数据库用内存型 SQLite 就够了,线上必须用 PostgreSQL;测试环境需要开启详细日志方便排查,线上却要求日志级别调低、输出到外部集中收集系统。如果所有环境共用同一份 Compose 文件,只能通过臃肿的 if/else 式注释和覆盖配置来区分,随着环境增多,文件会变得难以维护。
| 环境 | 数据库 | 日志级别 | 资源限制 | 调试工具 |
|---|---|---|---|---|
| 开发(dev) | SQLite / 本地 Postgres | DEBUG | 宽松(本地机器够用) | Hot-reload、inspect |
| 测试(test) | PostgreSQL(同版本) | INFO | 接近生产 80% | 集成测试框架 |
| 生产(prod) | PostgreSQL(HA 集群) | WARN | 严格限流 | 无,禁止调试入口 |
可以看出三个环境的差异非常显著——把这些差异塞进一个文件,结果必然是混乱。正确的做法是分层配置 + 按需合并。
方案一:独立 Compose 文件 + include 引入
Docker Compose V2 引入了 include 指令,允许主配置文件动态加载其他 compose 文件,这是实现环境分离最现代也最清晰的方式。
2.1 项目目录结构
docker-compose/
├── base/ # 公共基础配置(所有环境共用)
│ └── docker-compose.base.yml
├── dev/ # 开发环境覆盖配置
│ └── docker-compose.dev.yml
├── test/ # 测试环境覆盖配置
│ └── docker-compose.test.yml
├── prod/ # 生产环境覆盖配置
│ └── docker-compose.prod.yml
└── docker-compose.yml # 主入口(按需 include)
2.2 base 公共配置
# base/docker-compose.base.yml
services:
app:
build:
context: ../
dockerfile: Dockerfile
image: myapp:${TAG:-latest}
restart: unless-stopped
networks:
- appnet
depends_on:
- db
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${DB_USER:-appuser}
POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
POSTGRES_DB: ${DB_NAME:-myapp}
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- appnet
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
networks:
- appnet
volumes:
pgdata:
networks:
appnet:
driver: bridge
2.3 dev 开发环境覆盖
# dev/docker-compose.dev.yml
include:
- ../base/docker-compose.base.yml
services:
app:
build:
context: ../
dockerfile: Dockerfile.dev # 开发专用 Dockerfile(含调试工具)
environment:
NODE_ENV: development
LOG_LEVEL: debug
DEBUG: 'app:*'
volumes:
- ../src:/app/src # 源码热重载挂载
- /app/node_modules # 覆盖 node_modules 避免宿主机污染
ports:
- "3000:3000" # 业务端口
- "9229:9229" # Node.js inspect 调试端口
command: ["npm", "run", "dev"] # 启动开发服务器
db:
ports:
- "5432:5432" # 允许本地直连数据库
redis:
ports:
- "6379:6379"
2.4 prod 生产环境覆盖
# prod/docker-compose.prod.yml
include:
- ../base/docker-compose.base.yml
services:
app:
image: myapp:${VERSION:-stable} # 使用预构建的稳定镜像
environment:
NODE_ENV: production
LOG_LEVEL: warn
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
restart: always
ports:
- "8080:8080" # 仅暴露生产端口
# 生产环境禁止调试端口
# command: 不覆盖,使用 Dockerfile 的 ENTRYPOINT
db:
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} # 从 secrets 或 .env 注入
volumes:
- pg_prod_data:/var/lib/postgresql/data # 独立持久卷
volumes:
pg_prod_data: # 生产数据卷,与开发环境完全隔离
方案二:基于 .env 的环境变量隔离
除了分文件,也可以用环境文件(.env)配合 Compose 的 env_file 指令实现差异。这种方式适合差异较小的场景,比如只调整资源限制或环境变量:
# 各自目录下的 .env 文件
dev/.env:
NODE_ENV=development
DB_HOST=db-dev
LOG_LEVEL=debug
APP_PORT=3000
DEBUG_MODE=true
test/.env:
NODE_ENV=test
DB_HOST=db-test
LOG_LEVEL=info
APP_PORT=3000
DEBUG_MODE=false
prod/.env:
NODE_ENV=production
DB_HOST=db-prod
LOG_LEVEL=warn
APP_PORT=8080
DEBUG_MODE=false
# 敏感信息通过 Docker secrets 或 CI/CD 注入,不写在 .env 中
方案对比:两种策略如何选择
| 维度 | 独立文件 + include | .env 环境变量隔离 |
|---|---|---|
| 配置差异程度 | 差异大(服务数量、网络、卷都不一样) | 差异小(主要是环境变量和资源限制) |
| 文件可维护性 | ⭐⭐⭐ 各自职责清晰 | ⭐⭐ 单文件越来越臃肿 |
| 新环境扩展成本 | ⭐⭐⭐ 新增一个目录即可 | ⭐⭐ 需要修改主文件 |
| CI/CD 集成 | ⭐⭐⭐ 每个环境独立构建/部署 | ⭐⭐ 需要传递不同 .env |
| 适用项目规模 | 中大型项目、多环境严格隔离 | 小型项目、环境差异有限的场景 |
生产环境安全加固要点
生产环境的 Compose 配置除了功能正确,还需要额外的安全考量。以下是经过实战验证的安全清单:
6.1 禁止暴露调试端口
# ❌ 错误:生产环境暴露了调试端口
services:
app:
ports:
- "8080:8080"
- "9229:9229" # 绝不应在生产环境暴露
# ✅ 正确:仅暴露业务端口
services:
app:
ports:
- "8080:8080"
6.2 使用 Docker Secrets 管理敏感信息
# 不使用明文环境变量传递密码
# ❌ 错误做法
services:
db:
environment:
POSTGRES_PASSWORD: supersecretpassword123 # 明文出现在 compose 文件里!
# ✅ 正确做法:使用 Docker Secrets
services:
db:
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt # 文件本身应从 git 仓库中排除(.gitignore)
6.3 资源限制与 OOM 防护
services:
app:
image: myapp:stable
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
# 确保 OOM 时自动重启,但设置重试次数限制避免雪崩
restart: on-failure:5
一键切换环境的实用命令
# 启动开发环境(含热重载 + 调试端口)
docker compose --profile dev up -d
# 或者显式指定
docker compose -f docker-compose.yml -f dev/docker-compose.dev.yml up -d
# 启动测试环境
docker compose -f docker-compose.yml -f test/docker-compose.test.yml up -d
# 启动生产环境
docker compose -f docker-compose.yml -f prod/docker-compose.prod.yml up -d
# 查看所有可用 profile
docker compose config --profiles
总结
Docker Compose 多环境配置的核心理念是「公共配置下沉,环境差异上提」。通过 include 机制将基础服务配置和环境的差异化配置彻底解耦,既能保证各环境行为严格隔离,又能在公共层统一维护基础设施(网络、卷、健康检查等),大幅降低运维复杂度。
如果你的项目还在用一份 docker-compose.yml 走天下,不妨参考本文的结构做一次重构。关于容器编排的更多实战内容,可以参考之前的 Docker 网络模式详解 和 K8s 入门教程——Compose 是 K8s 的前置基础,理解了网络模式和多环境配置,迁移到 Kubernetes 会顺畅得多。更多技术干货,欢迎关注 FSData!🔧




