IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。
在前面的文章中,我们通过环境变量(第 13 篇)实现了同一份 docker-compose.yml 在不同环境中的切换。但你可能已经注意到一个问题:开发环境需要 Bind Mount 热重载、debug 日志级别、额外暴露的调试端口;生产环境需要资源限制、副本数、日志轮转。这些差异如果全靠环境变量来切换,YAML 会变得臃肿且难以维护。
今天我们来学习 Compose 的多文件组合机制------把通用配置、开发环境特化、生产环境特化拆成多个文件,按需叠加。这不仅让配置更清晰,也为你理解 Kubernetes 的 Kustomize 和 Helm values 文件铺设了思维基础。
一、多文件组合的核心原理
Docker Compose 默认会自动查找当前目录下的 docker-compose.yml 和 docker-compose.override.yml。如果两个文件都存在,Compose 会将它们合并 ------override 文件中的配置会覆盖或追加到基础文件中。这里的"合并"遵循特定的规则:对于列表类型的配置(如 ports、volumes、environment 列表格式),Compose 会合并双方的条目,而不是用 override 完全替换基础文件中的列表。但对于单值类型的配置(如 image、restart、container_name),override 文件会直接覆盖基础文件中的同名项。
你也可以通过 -f 参数指定任意数量的 Compose 文件,Compose 会按照从左到右的顺序合并,右边的文件覆盖左边的同名配置。
bash
# 基础文件 + 开发覆盖(默认行为)
docker compose up -d
# 手动指定多个文件(右边的优先级更高)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
1.1 为什么要拆分配置?
单一文件的问题:当所有环境配置塞在一个 YAML 里,你会发现自己在修改生产配置时不小心改动了开发环境的端口号;或者在调试一个 Bug 时,必须通读整份几百行的文件才能找到对应的环境变量。多文件拆分正是为了解决这种"配置膨胀"和"环境耦合"。
拆分后的收益:
-
减少重复:服务定义、网络、卷等公共部分只写一次(基础文件),环境特有的改动只写差异(覆盖文件)。
-
降低风险 :开发环境的覆盖文件可以加入
.gitignore,避免个人调试配置被提交到仓库影响其他团队成员。 -
清晰结构:一眼就能看出某个环境做了哪些特殊配置。
这种"基础配置 + 环境差异"的模式,在 Kubernetes 生态中以 Kustomize(基础 + overlay)和 Helm(values.yaml + values-prod.yaml)的形式得到延续。
二、实战:Flask + Redis 多环境配置拆分
我们以 Flask + Redis 计数器应用为例,将配置拆成三个文件:
2.1 基础配置文件
docker-compose.yml 只包含所有环境共用的配置:
bash
services:
redis:
image: redis:alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks:
- app-net
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
flask-app:
image: flask-redis-counter:${TAG:-2.0}
restart: unless-stopped
environment:
- FLASK_ENV=${FLASK_ENV:-production}
- REDIS_HOST=redis
- LOG_LEVEL=${LOG_LEVEL:-info}
volumes:
- flask-logs:/app/logs
networks:
- app-net
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s
volumes:
redis-data:
flask-logs:
networks:
app-net:
driver: bridge
注意这里去掉了 ports 配置,因为开发和生产需要的端口映射可能不同------基础文件不定义,留给覆盖文件各自声明。
2.2 开发环境覆盖文件
docker-compose.override.yml(Compose 自动加载):
bash
services:
flask-app:
# 开发环境用 debug 模式
environment:
- FLASK_ENV=development
- LOG_LEVEL=debug
# 暴露端口
ports:
- "5000:5000"
# Bind Mount 实现热重载
volumes:
- .:/app
- flask-logs:/app/logs
当你执行 docker compose up -d 时,Compose 自动合并 docker-compose.yml + docker-compose.override.yml,最终效果相当于把 override 中的内容"叠"在了基础文件之上。
2.3 生产环境覆盖文件
docker-compose.prod.yml(通过 -f 显式指定):
bash
services:
flask-app:
# 生产环境用 warn 级别日志
environment:
- LOG_LEVEL=warn
ports:
- "80:5000"
# 生产环境关闭 Bind Mount(不写 volumes 就是使用基础文件中的定义)
# 注意:这里不重新声明 volumes,Compose 会使用基础文件中的 flask-logs 挂载
redis:
# 生产环境增加内存限制
command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
# 可以在 deploy 中声明资源限制(需要 Swarm 模式才生效)
# deploy:
# resources:
# limits:
# memory: 256M
三、启动与验证
3.1 开发环境启动
bash
# 默认自动加载 override
docker compose up -d
此时 Compose 合并了 docker-compose.yml + docker-compose.override.yml。flask-app 暴露了 5000 端口,开启了 Bind Mount 热重载,日志级别为 debug。
查看最终配置:
bash
docker compose config | grep -A5 "flask-app:" | head -20
你会看到两个文件合并后的完整配置。
验证:
bash
curl http://localhost:5000/health
# {"status":"ok"}
docker compose exec flask-app env | grep LOG_LEVEL
# LOG_LEVEL=debug
3.2 生产环境启动
bash
# 停止开发环境
docker compose down
# 手动指定生产文件(不自动加载 override)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
此时 Compose 合并了基础文件 + 生产覆盖文件。flask-app 暴露了 80 端口,日志级别为 warn,没有 Bind Mount。
bash
curl http://localhost:80/health
# {"status":"ok"}
docker compose exec flask-app env | grep LOG_LEVEL
# LOG_LEVEL=warn
重要 :使用
-f显式指定文件时,Compose 不会 自动加载docker-compose.override.yml。只有在你希望叠加 override 时才需要显式列出它。
四、多文件使用进阶技巧
4.1 查看合并后的最终配置
这是最常用的调试命令,能让你看到 Compose 实际使用的配置:
bash
# 查看默认合并(含 override)
docker compose config
# 查看指定文件合并
docker compose -f docker-compose.yml -f docker-compose.prod.yml config
# 只查看某个服务的合并结果
docker compose config --services
4.2 多个 override 文件堆叠
复杂项目中,你可以按功能拆分覆盖文件:
bash
docker-compose.yml # 基础
docker-compose.override.yml # 本地开发
docker-compose.debug.yml # 调试工具(如 debugpy)
docker-compose.admin.yml # 管理工具(如 phpMyAdmin)
启动时按需组合:
bash
# 开发 + 调试
docker compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.debug.yml up -d
# 开发 + 管理工具
docker compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.admin.yml up -d
4.3 个人本地配置(不提交到 Git)
每个开发者可能有自己的本地偏好(比如不同端口、额外的环境变量)。你可以创建一个 docker-compose.local.yml,加入 .gitignore,然后通过别名或脚本加载:
bash
# 加入 .gitignore
echo "docker-compose.local.yml" >> .gitignore
# 个人覆盖文件
cat > docker-compose.local.yml << 'EOF'
services:
flask-app:
ports:
- "9999:5000"
EOF
# 启动时叠加
docker compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.local.yml up -d
这样每个人都能有自己的本地配置,互不干扰,也不会污染共享仓库。
4.4 使用 extends 继承(不推荐,已弃用)
你可能会在一些旧教程中看到 extends 关键字,它允许一个 Compose 文件继承另一个 Compose 文件中的服务定义。但从 Compose v3 开始,extends 已被标记为弃用,Docker 推荐使用多文件 -f 合并的方式替代。如果你在维护遗留项目时遇到 extends,建议逐步迁移到多文件覆盖模式。
五、从 Compose 到 K8s 的概念映射
多文件组合的思路在 Kubernetes 生态中有两个重要的对应物:
Helm 的 values 文件 :helm install myapp ./chart -f values.yaml -f values-prod.yaml 将多个 values 文件叠加,后面的覆盖前面的。这与 Compose 的多文件合并逻辑完全一致------基础模板(Chart)定义了结构,values 文件提供了环境差异。
Kustomize 的 base + overlay :Kustomize 通过 base/ 定义公共资源,overlays/dev/ 和 overlays/prod/ 定义各环境的 patch(增量修改)。这更接近于 Compose 多文件的思想------把"是什么"和"哪里不同"分离。
六、命令速查表
七、本篇总结
-
多文件合并机制 :Compose 默认加载
docker-compose.yml+docker-compose.override.yml,按顺序合并,右边覆盖左边。 -
配置拆分原则:基础文件保留通用定义,覆盖文件按环境(开发/生产)或按功能(调试/管理工具)拆分差异配置。
-
开发与生产分离:开发覆盖加 Bind Mount、debug 日志、自定义端口;生产覆盖加资源限制、warn 日志、标准端口。
-
演进视角:Compose 的多文件模式对应 Helm 的多 values 文件叠加和 Kustomize 的 base + overlay,是"基础 + 差异"这一配置管理思想的统一体现。
下一篇------第 18 篇:从 Docker Compose 到 Kubernetes 的思考,我们将系统地对比 Compose 与 K8s 的核心概念,把你已经熟悉的 Compose 术语一一映射到 K8s 的对象模型,为进入本系列最核心的 Kubernetes 阶段做好准备。
想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !