Docker Compose环境变量为什么没生效

改了 .env,容器却还在使用旧值,通常不是文件没有保存,而是改错了变量来源,或者只重启了旧容器。排查时要分清三步:Compose 读取什么值、服务配置把什么值交给容器、运行中的容器实际拿到了什么。

Compose环境变量配置流程:插值来源、服务环境配置与新建容器,服务env_file另行注入
配置流程示意。插值来源和容器环境文件承担不同任务。

先分清两种“环境文件”

位置 作用
Shell、.env、--env-file 为 Compose 文件中的 ${变量} 提供插值来源;不会把文件全部内容自动注入容器。
服务下的 environment 明确设置容器环境变量,可以使用插值结果,也可以直接写固定值。
服务下的 env_file 读取要传入容器的变量文件;相对路径按 Compose 文件所在目录解析。

Shell 的同名变量优先于插值文件。显式指定 --env-file 可避免误用默认 .env;本文使用绝对路径固定文件位置。容器侧则是 environment 覆盖服务 env_file,包括显式空值;没有被这些配置覆盖的变量,才可能保留镜像自带的 ENV。Shell 不会自动覆盖写死在 environment 中的值。

用无秘密的小例子看清变量去向

以下是独立演示,/srv/compose-demo 为示例目录,env-demo 为示例项目名,不要覆盖现有业务文件。先在宿主机创建该目录,再分别保存三个文件。镜像首次运行可能需要下载,镜像内含本例使用的 sleep、printenv 程序。

.env:

APP_MODE=staging

container.env:

LOG_LEVEL=info

compose.yaml:

services:
  app:
    image: busybox:1.37
    command: ["sleep", "3600"]
    env_file:
      - ./container.env
    environment:
      APP_MODE: "${APP_MODE:-development}"

在宿主机执行 printenv APP_MODE,只查看这个非敏感变量;无输出且退出码为 1 表示未设置。若显示其他值,它会优先参与本例插值。确认该宿主机变量不需要传入本次演示后,可在当前终端执行 unset APP_MODE 再继续。

第一步:只校验配置,不打印全部变量

在宿主机执行:

docker compose --project-directory /srv/compose-demo -p env-demo \
  -f /srv/compose-demo/compose.yaml \
  --env-file /srv/compose-demo/.env config --quiet

无错误退出只表示配置校验通过,并不能证明运行中的容器已更新。不要为了找一个变量,把完整 config、env 或容器环境列表粘贴到工单里,其中可能包含密码和令牌。

第二步:创建或定点重建,再看容器实际值

下面两条均在宿主机执行;最后的 printenv 在演示容器内运行:

docker compose --project-directory /srv/compose-demo -p env-demo \
  -f /srv/compose-demo/compose.yaml \
  --env-file /srv/compose-demo/.env up -d --no-deps --force-recreate app
docker compose --project-directory /srv/compose-demo -p env-demo \
  -f /srv/compose-demo/compose.yaml \
  --env-file /srv/compose-demo/.env exec -T app printenv APP_MODE LOG_LEVEL

当宿主机没有覆盖值时,预期依次得到 staging 和 info:前者经 environment 注入,后者来自服务 env_file。这里只验证已知的非敏感变量。实际业务镜像未必提供 printenv 或 shell,应使用应用已有的单项诊断方式,不要因命令缺失就断言变量没有注入。

如果仅在 .env 新增 OTHER_FLAG=on,服务配置既没有引用它,也没有把该文件声明为服务 env_file,这个变量不会自动进入容器。文件名不能决定用途:同一文件可以被两个入口分别读取,但每个入口的作用仍需分开判断。

仍然不对时,按这个顺序检查

  1. 值被覆盖:先查宿主机是否导出了同名变量,再查服务 environment 是否覆盖 env_file;不要凭 .env 的文件内容猜运行值。
  2. 空值与未设置混淆:${APP_MODE:-development} 在未设置或空字符串时都取默认值;${APP_MODE-development} 只在未设置时取默认值。直接写 APP_MODE: "" 会向容器传入空字符串。
  3. 只执行了 restart:docker compose restart 重启现有容器,不加载新环境配置。修改后要让 Compose 创建新容器,随后再次定点检查。
  4. 操作了另一个项目:业务环境应使用原项目名、原项目目录及全部原有 -f 文件,不能把演示项目名和路径直接套上去。

单服务重建会中断该服务;--no-deps 用于避免同时启动其依赖。通常会继续挂载原有数据卷,但容器可写层会随旧容器移除。操作前检查挂载和回退方案,不要额外加删除卷参数。

延伸阅读:容器重建后的数据保留方法;Docker应用更新失败,怎样退回原来的版本。