Compose 进阶:环境变量与健康检查
本教程共 25 篇 · 第 22 篇 · 更新于 2026-07-26 · 约 7 分钟阅读
22. Compose 进阶:环境变量与健康检查
本节目标:学会用环境变量和 .env 文件管理配置,用 depends_on + healthcheck 解决「数据库还没起来应用就崩」的经典问题,用 profiles 和覆盖文件应对多环境。
两种环境变量写法
Compose 里给容器传环境变量,主要有 environment 和 env_file 两条路。
environment:直接写在文件里
最直接的方式,键值对写在服务下面:
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: todos
MYSQL_USER: app
MYSQL_PASSWORD: apppass
也可以用列表语法:
environment:
- MYSQL_ROOT_PASSWORD=secret
- MYSQL_DATABASE=todos
两种等价,键值对写法更易读,列表写法适合从别处复制。
Warning密码、密钥这类敏感信息别硬编码进 compose.yaml,更别提交到 Git。下面讲的
env_file和变量插值就是解决这个问题的。
env_file:从外部文件加载
把敏感配置放到独立文件里,Compose 读取时注入:
先建一个 .env.mysql 文件:
MYSQL_ROOT_PASSWORD=secret
MYSQL_DATABASE=todos
MYSQL_USER=app
MYSQL_PASSWORD=apppass
然后在 compose.yaml 里引用:
services:
mysql:
image: mysql:8.0
env_file:
- .env.mysql
Tip
.env.mysql这种文件记得加进.gitignore。可以提交一个.env.mysql.example当模板,团队成员自己复制改名填值。
.env 文件与变量插值
Compose 还有个特殊的 .env 文件(无前缀,就叫 .env),放在 compose.yaml 同级目录。它有两个用途:
- 给 Compose 自己用(比如
COMPOSE_PROJECT_NAME)。 - 在 compose.yaml 里做变量插值。
来看插值怎么用。先写 .env:
# .env
APP_PORT=3000
DB_PASSWORD=secret
IMAGE_TAG=1.4
然后在 compose.yaml 里用 ${变量名} 引用:
services:
app:
image: my-app:${IMAGE_TAG}
ports:
- ${APP_PORT}:3000
environment:
DB_PASSWORD: ${DB_PASSWORD}
跑 docker compose up 时,Compose 会先把 ${...} 替换成 .env 里的值,再传给容器。
Note变量插值是「解析时替换」,发生在 Compose 读文件的阶段;
environment是「运行时传给容器」。两者层次不同,别混了。插值改变的是 Compose 文件本身,environment改变的是容器内进程看到的环境。
也可以从 shell 环境变量插值,优先级高于 .env 文件:
export IMAGE_TAG=1.5
docker compose up -d
这样不修改文件就能临时换版本,CI/CD 里常用。
depends_on 的局限
「应用启动时数据库还没就绪」是多容器编排的经典坑。depends_on 能控制启动顺序:
services:
app:
image: my-app
depends_on:
- mysql
mysql:
image: mysql:8.0
这样 Compose 会先起 mysql,再起 app。
但有个大坑:depends_on 只等容器「启动」,不等「就绪」。MySQL 容器跑起来了,但可能还在做初始化,这时候应用连上去必然失败。
Warning光靠
depends_on解决不了就绪问题。要么应用里加重试逻辑(推荐),要么配合下面的healthcheck+condition。
healthcheck:让容器自报健康
健康检查(healthcheck)让容器周期性地自检,Docker 据此判断它是「活的」还是「僵了」。
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-psecret"]
interval: 10s # 每 10 秒查一次
timeout: 5s # 超过 5 秒算失败
retries: 5 # 连续失败 5 次算不健康
start_period: 30s # 启动后 30 秒内失败不计入
test 是核心,常见写法:
["CMD", "curl", "-f", "http://localhost"]:跑命令,退出码 0 算健康。["CMD-SHELL", "curl -f http://localhost || exit 1"]:用 shell 解释,能写复杂逻辑。["NONE"]:禁用基础镜像自带的健康检查。
start_period 很有用,给应用一段「热身时间」,避免启动慢的服务被误判。
depends_on + condition:等就绪
把健康检查和 depends_on 组合起来,才能真正等到数据库就绪:
services:
app:
image: my-app
depends_on:
mysql:
condition: service_healthy
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-psecret"]
interval: 5s
timeout: 3s
retries: 10
condition: service_healthy 表示「等 mysql 健康检查通过再起 app」。还有几个取值:
service_started:等容器启动(默认行为,不等就绪)。service_healthy:等健康检查通过。service_completed_successfully:等容器成功退出(适合一次性初始化任务)。
Tip即便配了健康检查,应用代码里也建议保留重试逻辑。生产环境网络抖动、数据库重启都可能让连接断掉,依赖关系解决不了所有问题。
profiles:多环境分组
一个项目可能有好几套服务:开发用 SQLite,测试用 MySQL,压测还要加 Redis。全堆在一个 compose.yaml 里,每次 up 全起来很浪费。
profiles 给服务打标签,按需启动:
services:
app:
image: my-app
profiles: ["dev", "test"] # 默认不在 dev/test 时不起
mysql:
image: mysql:8.0
profiles: ["test"]
redis:
image: redis:alpine
profiles: ["benchmark"]
启动时指定 profile:
# 只启动属于 test profile 的服务(app + mysql)
docker compose --profile test up -d
# 启动 benchmark 压测
docker compose --profile benchmark up -d
不带 profile 的服务默认总会启动,带 profile 的只在匹配时启动。
Note没标
profiles的服务属于「默认集合」,任何up都会带上它们。把核心服务留默认,把环境特定的服务打标签,是常见用法。
覆盖文件:多环境配置
另一个管理多环境的路子是覆盖文件。Compose 默认会读两个文件:
compose.yaml(基础配置)compose.override.yaml(覆盖配置,存在才读)
基础文件放通用配置,override 放本地开发差异。比如:
compose.yaml:
services:
app:
image: my-app:${IMAGE_TAG}
ports:
- 3000:3000
compose.override.yaml(开发用,挂源码热重载):
services:
app:
build: .
volumes:
- ./:/app
command: npm run dev
environment:
NODE_ENV: development
跑 docker compose up 时,两个文件会自动合并。开发时挂代码、跑 dev 模式,提交时把 override 留在本地不进库。
Tip把
compose.override.yaml加进.gitignore,每个人本地自定义。团队共享一个compose.override.yaml.example作参考。
要显式指定文件,用 -f:
# 用生产配置
docker compose -f compose.yaml -f compose.prod.yaml up -d
多个文件按顺序合并,后面的覆盖前面的。生产、测试、CI 各一份,互不干扰。
合并规则速记
覆盖文件合并时有几条规则,记一下免得踩坑:
- 标量(字符串、数字):后者覆盖前者。
- 列表(如
ports、volumes):后者追加,不是覆盖。 - 字典(如
environment):按键合并,相同键后者覆盖。 command、entrypoint:后者整体覆盖。
# 基础
services:
app:
ports: ["3000:3000"]
environment:
A: 1
B: 2
# 覆盖
services:
app:
ports: ["8080:80"]
environment:
B: 20
C: 3
# 合并结果
services:
app:
ports: ["3000:3000", "8080:80"] # 列表追加
environment:
A: 1
B: 20 # 同键覆盖
C: 3 # 新增
Warning列表是追加不是替换这点最容易记错。想在 override 里替换 ports,得用
!reset或直接改基础文件。
小结
这一章的几个机制让 Compose 从「能跑多容器」升级到「聪明地跑多容器」:
env_file管敏感配置,.env做变量插值。depends_on+condition: service_healthy真正解决启动顺序。profiles按需启服务,避免环境噪音。- 覆盖文件分离开发与生产配置。
掌握这些,本地编排基本够用了。下一章我们换个角度,看几个常用服务(Nginx、MySQL、Redis)怎么用官方镜像快速容器化。