第17篇 Docker Compose 进阶实战:多 Compose 文件与环境覆盖

0 阅读7分钟

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。

在前面的文章中,我们通过环境变量(第 13 篇)实现了同一份 docker-compose.yml 在不同环境中的切换。但你可能已经注意到一个问题:开发环境需要 Bind Mount 热重载、debug 日志级别、额外暴露的调试端口;生产环境需要资源限制、副本数、日志轮转。这些差异如果全靠环境变量来切换,YAML 会变得臃肿且难以维护。

今天我们来学习 Compose 的多文件组合机制——把通用配置、开发环境特化、生产环境特化拆成多个文件,按需叠加。这不仅让配置更清晰,也为你理解 Kubernetes 的 Kustomize 和 Helm values 文件铺设了思维基础。

一、多文件组合的核心原理

Docker Compose 默认会自动查找当前目录下的 docker-compose.ymldocker-compose.override.yml。如果两个文件都存在,Compose 会将它们合并——override 文件中的配置会覆盖或追加到基础文件中。这里的“合并”遵循特定的规则:对于列表类型的配置(如 portsvolumesenvironment 列表格式),Compose 会合并双方的条目,而不是用 override 完全替换基础文件中的列表。但对于单值类型的配置(如 imagerestartcontainer_name),override 文件会直接覆盖基础文件中的同名项。

你也可以通过 -f 参数指定任意数量的 Compose 文件,Compose 会按照从左到右的顺序合并,右边的文件覆盖左边的同名配置

# 基础文件 + 开发覆盖(默认行为)
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 只包含所有环境共用的配置:

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 自动加载):

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 显式指定):

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 开发环境启动

# 默认自动加载 override
docker compose up -d

此时 Compose 合并了 docker-compose.yml + docker-compose.override.ymlflask-app 暴露了 5000 端口,开启了 Bind Mount 热重载,日志级别为 debug。

查看最终配置:

docker compose config | grep -A5 "flask-app:" | head -20

你会看到两个文件合并后的完整配置。

验证:

curl http://localhost:5000/health
# {"status":"ok"}

docker compose exec flask-app env | grep LOG_LEVEL
# LOG_LEVEL=debug

3.2 生产环境启动

# 停止开发环境
docker compose down

# 手动指定生产文件(不自动加载 override)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

此时 Compose 合并了基础文件 + 生产覆盖文件。flask-app 暴露了 80 端口,日志级别为 warn,没有 Bind Mount。

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 实际使用的配置:

# 查看默认合并(含 override)
docker compose config

# 查看指定文件合并
docker compose -f docker-compose.yml -f docker-compose.prod.yml config

# 只查看某个服务的合并结果
docker compose config --services

4.2 多个 override 文件堆叠

复杂项目中,你可以按功能拆分覆盖文件:

docker-compose.yml               # 基础
docker-compose.override.yml       # 本地开发
docker-compose.debug.yml          # 调试工具(如 debugpy)
docker-compose.admin.yml          # 管理工具(如 phpMyAdmin)

启动时按需组合:

# 开发 + 调试
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,然后通过别名或脚本加载:

# 加入 .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 思维 !