怎么使用 Docker Compose profiles 功能管理测试和生产环境配置

文章导读
在维护同一个 Compose 项目时,测试环境往往要多跑几个服务,生产环境为了干净又要少跑几个服务。遇到这种需求,先不用急着把 compose 文件拆成 docker-compose.test.yml 和 docker-compose.prod.yml。拆文件虽然直观,但公共配置一变就要同步两处,时间长了很容易出现测试和生产行为不一致的问题。Docker Compose profiles 提供了另
📋 目录
  1. 先别急着拆文件,看两个环境共享多少配置
  2. 给服务打上 profile 标记,比维护多份文件更直接
  3. 注意 depends_on 不会自动激活 profile 依赖
  4. 改完配置后,用 config 和 ps 验证实际解析结果
  5. 最容易踩的坑:忘了带参数和 COMPOSE_PROFILES 残留
  6. 一个实际项目的 profile 划分示例
A A

在维护同一个 Compose 项目时,测试环境往往要多跑几个服务,生产环境为了干净又要少跑几个服务。遇到这种需求,先不用急着把 compose 文件拆成 docker-compose.test.yml 和 docker-compose.prod.yml。拆文件虽然直观,但公共配置一变就要同步两处,时间长了很容易出现测试和生产行为不一致的问题。Docker Compose profiles 提供了另一种思路:在同一个 docker-compose.yml 里,通过给服务打标记,控制哪些服务默认启动、哪些服务按需启动。

先别急着拆文件,看两个环境共享多少配置

当同一个 Compose 项目中同时存在测试和生产环境所需的服务,但两者启动的服务集合差异较大时,使用 profiles 比维护多份 Compose 文件更简洁。判断依据是:如果两个环境共享大部分配置,只是需要按需启动一部分服务,比如测试环境需要 mock 服务,生产环境需要日志采集服务,那么 profiles 可以避免复制粘贴公共配置。如果环境间配置差异很大,例如网络模式、存储卷完全不同,则拆分为多个 Compose 文件更合适。

这个判断很重要。profiles 解决的是“服务集合不同”的问题,不是“配置细节不同”的问题。如果数据库地址、端口映射、镜像 tag 都完全不一样,硬把全部服务塞进一个文件,反而要用大量环境变量和 profile 条件覆盖,阅读和维护成本都会上升。先确认哪些服务是两边都要启动的,哪些是只有某个环境需要的,再决定要不要用 profiles。

给服务打上 profile 标记,比维护多份文件更直接

在 docker-compose.yml 中为每个服务添加 profiles 字段,即可控制该服务是否被默认启动。例如,设置 profiles: ["test"] 后,该服务仅在执行 docker compose --profile test up 时启动,普通 docker compose up 不会包含它。可以在一个服务上指定多个 profile 名,也可以让一个 profile 包含多个服务。启动时使用 -p 或 --profile 参数传入多个 profile 值,例如 docker compose --profile test --profile monitoring up。

实际配置里通常这样写:

  mock:
    image: mock-server:latest
    profiles: ["test"]

这样测试环境启动命令是 docker compose --profile test up -d,生产环境直接 docker compose up -d,两边都不需要额外覆盖环境变量。注意,profiles 列表写在服务定义上,可以写多个值:profiles: ["test", "debug"]。同一个 profile 也可以挂多个服务,只要在各自服务上写相同名字即可。

怎么使用 Docker Compose profiles 功能管理测试和生产环境配置

注意 depends_on 不会自动激活 profile 依赖

使用 profiles 时有一个容易忽视的边界条件:如果服务 A 离开了 profile,而服务 B 在默认启动集合中且通过 depends_on 依赖服务 A,那么 docker compose up 会因为找不到服务 A 而报错。这个依赖不会自动激活服务 A 的 profile。正确的做法是确保被依赖的服务要么也在同一个 profile 中,要么让依赖关系也通过 profile 显式声明,否则就需要在启动命令中同时指定两个 profile。

举个例子,web 服务默认启动,mock 服务只打了 test profile。如果 web 的 depends_on 里写了 mock,执行 docker compose up 时会直接报 no service named "mock"。即使 mock 和 web 都在默认启动集合之外,只要 web 没有 profile 而 mock 有,这个问题就会发生。解决方式是调整依赖关系:让 mock 不作为默认服务的依赖,或者把 web 也放进 test profile。如果确实需要 web 依赖 mock,但生产环境不能启动 mock,那就得检查应用代码是否允许 mock 缺失,或者把 web 拆成两个 profile 下的不同启动方式。

改完配置后,用 config 和 ps 验证实际解析结果

配置改完,不要直接 up,先看解析结果。要检查当前环境实际启动了哪些服务,可以运行 docker compose config --services 查看所有定义的服务,但更直接的方法是运行 docker compose ps 查看运行中的服务实例。如果想了解某个 profile 会解析出哪些服务,可以执行 docker compose --profile test config --services,这样会列出在该 profile 下会被创建的服务列表,从而验证配置是否符合预期。注意,docker compose config 默认不显示未激活 profile 的服务。

建议养成的习惯是:改完 profiles 后先跑两条命令。第一条是 docker compose config --services 看默认集合有没有多出来服务;第二条是针对目标 profile 跑 docker compose --profile test config --services,确认测试环境需要的东西都在。如果发现预期外服务,优先检查是不是某个服务上的 profiles 写漏了,或者环境变量里是不是有 COMPOSE_PROFILES。

怎么使用 Docker Compose profiles 功能管理测试和生产环境配置

最容易踩的坑:忘了带参数和 COMPOSE_PROFILES 残留

一个常见坑是把资源占用型服务放入 profiles 后,忘记在 CI 或本地开发命令中带上 --profile 参数,导致服务静默缺失。比如有人把 mock 服务加上了 profiles: ["test"],但 CI 脚本里用的还是原来的 docker compose up,结果集成测试跑了一半才发现 mock 没起来。排查时看 docker compose ps 能立刻发现,但更讨厌的是它不报错,服务少了一个,所有依赖它的请求都超时。

另一个坑是 profiles 不会覆盖环境变量中的 COMPOSE_PROFILES,如果 shell 中设置了 COMPOSE_PROFILES,docker compose 会优先使用它,甚至可能意外启动不需要的服务。这种问题在本地开发环境尤其隐蔽:你上午为了调试加了 export COMPOSE_PROFILES="debug",下午忘了取消,结果 docker compose up 把 debug 服务也拉起来了。排查问题时可以执行 env | grep COMPOSE_PROFILES 确认是否有残留变量。如果 CI 环境里有设置,也会导致 profile 行为和预期不一致,建议在 CI 脚本开头显式 unset COMPOSE_PROFILES,或者明确写入目标 profile 名。

一个实际项目的 profile 划分示例

假设一个项目包含 web、api、mock、db 四个服务,测试环境希望运行 web、api、mock、db,而生产环境只运行 web、api、db。可以在 mock 服务上标记 profiles: ["test"],然后在测试环境启动时执行 docker compose --profile test up -d,生产环境执行 docker compose up -d。如果需要在生产环境临时调试,可以增加一个 debug 服务并设置 profiles: ["debug"],用 docker compose --profile debug up 临时引入。

这样划分后,生产环境的默认启动集合是干净且稳定的,测试环境通过追加 profile 获得 mock 服务,debug 工具只在需要时出现。后续如果需要新增一个只给测试用的数据库迁移工具,同样打上 test profile 即可,不会影响生产环境。但需要留意的是,如果 mock 服务内部还要分不同测试场景,可以再拆成 profiles: ["test", "e2e"],启动时按需叠加。profile 名最好在项目文档里写清楚,否则后面接手的人会在启动命令里漏掉参数,这就是上面说的静默缺失问题。

最后强调一个边界:profiles 只是“启动时包含或不包含”的开关,它不能替代环境变量或配置覆盖。生产环境需要禁用的功能,不能只靠不启动对应服务来保证安全,还要确认代码不会在服务缺失时自动降级到不安全的逻辑。用好 profiles 的关键是提前梳理服务依赖和启动集合,而不是临时给某个服务加个标记就完事。