首页 / Docker 入门教程 / Compose 进阶:环境变量与健康检查

Docker 入门教程

Compose 进阶:环境变量与健康检查

本教程共 25 篇 · 第 22 篇 · 更新于 2026-07-26 · 约 7 分钟阅读

DockerDocker 入门教程Docker Compose环境变量健康检查depends_onprofilescompose override

22. Compose 进阶:环境变量与健康检查

本节目标:学会用环境变量和 .env 文件管理配置,用 depends_on + healthcheck 解决「数据库还没起来应用就崩」的经典问题,用 profiles 和覆盖文件应对多环境。

两种环境变量写法

Compose 里给容器传环境变量,主要有 environmentenv_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 同级目录。它有两个用途:

  1. 给 Compose 自己用(比如 COMPOSE_PROJECT_NAME)。
  2. 在 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 各一份,互不干扰。

合并规则速记

覆盖文件合并时有几条规则,记一下免得踩坑:

  • 标量(字符串、数字):后者覆盖前者。
  • 列表(如 portsvolumes):后者追加,不是覆盖。
  • 字典(如 environment):按键合并,相同键后者覆盖。
  • commandentrypoint:后者整体覆盖。
# 基础
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)怎么用官方镜像快速容器化。