Docker Compose 环境变量不生效怎么办:.env、env_file、优先级与重建排查

Docker Compose 修改 .env 后不生效?用可复现示例区分变量插值和容器环境,检查 env_file、Shell 覆盖、美元符转义及 restart 与重建的区别,附 PowerShell 排错命令。

Docker Compose 环境变量不生效,先分清两件事:.env 和 --env-file 为 Compose 解析配置提供变量,服务里的 environment 与 env_file 才负责把变量传进容器。修改配置后,restart 不会更新已有容器的环境,应检查解析结果,再用 up -d 应用变更。

复制部署教程时,最常见的情况是:.env 明明写了值,容器里却没有;改了值又重启,程序仍然使用旧地址。 这通常需要沿着“输入文件 → Compose 配置 → 容器环境 → 应用读取”逐层排查。

本文适用于 Compose V2 和 Linux 容器,包括 Docker Desktop 的 Linux 容器模式。 文档核对日期为 2026 年 11 月 4 日;命令依据官方文档整理,未在本文环境中运行 Docker 容器。

如果你正在维护 Joplin Server、Syncthing 等自托管服务,可以先用下面的独立示例理解覆盖规则,再检查业务配置。

四种写法分别把变量送到哪里

写法 使用位置 主要作用
.env Compose 项目约定的输入文件 为 ${VAR} 等插值提供值
--env-file ./deploy.env Compose 命令行选项 显式指定插值输入文件
env_file: ./app.env 某个服务的配置 把文件中的变量注入该服务容器
environment: 某个服务的配置 直接设置、引用或覆盖容器变量

同一个文件可以同时承担两种用途,但需要显式声明。 仅仅把 .env 放在目录里,不会把其中所有变量自动送入所有容器。

例如 .env 中存在 APP_MODE=production,但服务没有任何环境变量声明,容器通常不会因此获得 APP_MODE。 服务写了 environment: APP_MODE: "${APP_MODE}",这条传递链才连接起来。

官方说明见 设置容器环境变量。

用一个没有业务数据的项目观察覆盖结果

先确认 CLI 和实际连接的 Docker 主机。

1
2
3
docker compose version
docker context show
docker info

建立独立目录 compose-env-lab,在其中新建以下三个文件。 不要直接把实验服务追加到生产项目中。

1
2
3
4
compose-env-lab/
├── compose.yaml
├── .env
└── app.env

compose.yaml:

1
2
3
4
5
6
7
8
9
services:
  probe:
    image: alpine:3.22
    env_file:
      - ./app.env
    environment:
      APP_MODE: "${APP_MODE:-development}"
      APP_LABEL: "${APP_LABEL:?APP_LABEL is required}"
    command: ["sleep", "3600"]

.env:

1
2
3
APP_MODE=production
APP_LABEL=compose-env-lab
ONLY_FOR_COMPOSE=not-in-container

app.env:

1
2
APP_MODE=from-env-file
APP_COLOR=blue

本例没有端口映射、数据卷或特权权限。 镜像需要从你的 Docker 主机能够访问的仓库拉取;实验结束前保持文件不变,便于对照。

先在这个目录执行:

1
2
3
docker compose -p compose-env-lab config -q
docker compose -p compose-env-lab config --environment
docker compose -p compose-env-lab config

config -q 成功只表示配置校验通过。 config --environment 显示用于插值的变量;普通 config 展示合并、解析后的服务模型。 它们都不能证明容器已经创建,更不能证明应用读取到了值。

这些输出可能包含密码,只在可信终端查看,分享前删去敏感值。 具体选项见 docker compose config。

启动后只读取需要核对的变量

1
2
3
docker compose -p compose-env-lab up -d
docker compose -p compose-env-lab ps
docker compose -p compose-env-lab exec probe printenv APP_MODE APP_LABEL APP_COLOR

如果启动 Compose 的终端没有同名变量覆盖,预期依次看到:

1
2
3
production
compose-env-lab
blue

APP_MODE 来自 .env 的插值结果,覆盖了 app.env 中的同名项。 APP_COLOR 通过服务的 env_file 注入。 APP_LABEL 是必填项,缺失或为空时会在配置解析阶段报错。

再单独查询没有被声明传入容器的变量:

1
docker compose -p compose-env-lab exec probe printenv ONLY_FOR_COMPOSE

预期没有输出,命令返回非零状态。 这是实验的正常结果,说明 .env 并不是全量容器环境注入文件。

.env 的值为什么被终端覆盖

Compose 解析 ${APP_MODE} 时,启动它的 Shell 中若已有同名变量,会优先使用 Shell 的值。 所以“文件写的是 production,解析出来是 staging”时,先检查终端环境。

在 Bash 中可以这样查看和清除实验变量:

1
2
3
printenv APP_MODE
unset APP_MODE
docker compose -p compose-env-lab config --environment

在 PowerShell 中对应为:

1
2
3
Get-Item Env:APP_MODE -ErrorAction SilentlyContinue
Remove-Item Env:APP_MODE -ErrorAction SilentlyContinue
docker compose -p compose-env-lab config --environment

清除当前终端变量前,确认它不是其他任务需要的设置。 IDE 任务、CI 作业和系统服务继承的环境,也可能与手动打开的终端不同。

为了减少工作目录造成的歧义,部署命令可以显式固定配置和输入文件:

1
2
docker compose -p myapp -f ./compose.yaml --env-file ./deploy.env config -q
docker compose -p myapp -f ./compose.yaml --env-file ./deploy.env config

把 myapp 换成现有项目名,不要在生产部署时随意改名。 项目名参与容器、网络和默认卷名称的生成,换名可能得到另一套资源。

--env-file 的相对路径以执行命令时的工作目录为基准;服务 env_file 的常规相对路径则以 Compose 文件位置为基准。 多个 Compose 文件、--project-directory、COMPOSE_FILE 会增加路径关系的复杂度,排错时先用明确路径收敛范围。

同时指定多个插值输入文件时,后面的文件可以覆盖前面的同名项:

1
docker compose --env-file .env --env-file .env.prod config

Shell 中已有的同名变量仍可能优先。 默认 .env 的发现细节应结合当前 CLI 版本核对,见 Compose 插值与文件加载规则。

容器里的优先级要单独检查

插值输入确定后,还要看服务最终怎么设置容器变量。 在常见的显式赋值场景中,可以按下面的覆盖关系理解:

场景 对结果的影响
docker compose run -e APP_MODE=debug 覆盖这次临时容器的对应变量
服务 environment 中设置 APP_MODE 覆盖服务 env_file 的同名值
服务列出多个 env_file 后面的文件覆盖前面的同名值
镜像 Dockerfile 中的 ENV 未被运行时同名配置覆盖时才保留

这里的 environment 值可能本身来自 Shell 或 .env 插值。 不要把“值从哪里解析出来”和“通过哪个服务字段传进容器”混成一个步骤。 只写变量名而不给值的透传写法还有未解析时移除变量的语义,排错时优先使用明确赋值。

可以在实验中验证临时覆盖:

1
docker compose -p compose-env-lab run --rm -e APP_MODE=debug probe printenv APP_MODE

预期输出 debug。 这创建的是一次性容器,不会修改之前 up -d 启动的 probe 容器。 也不要把 run -e 当成 up 的通用参数。

完整边界和透传案例见 官方环境变量优先级说明。

改完文件后,为什么 restart 仍然是旧值

容器的环境变量在创建容器时确定。 docker compose restart 重启已有容器,不会根据修改后的配置重新创建容器环境。

把实验 .env 中的 APP_MODE 改成 staging 后,可以依次对照:

1
2
3
docker compose -p compose-env-lab config
docker compose -p compose-env-lab restart probe
docker compose -p compose-env-lab exec probe printenv APP_MODE

若容器最初使用 production,重启后仍应是该值。 此时配置文件和正在运行的容器处于不同状态。

确认解析结果正确后应用变更:

1
2
docker compose -p compose-env-lab up -d probe
docker compose -p compose-env-lab exec probe printenv APP_MODE

Compose 通常会检测配置变化并重建受影响服务。 确有必要强制重建时,使用:

1
docker compose -p compose-env-lab up -d --force-recreate probe

重建会中断服务,应按业务维护窗口执行。 数据库等依赖服务是否需要联动,应由实际架构决定,不要习惯性重建整个项目。

挂载的数据卷会按配置继续使用,但容器可写层中的临时修改可能丢失。 操作前应确认业务数据确实存放在持久化位置。 官方行为说明见 restart 与 up。

美元符、空值和布尔值的常见陷阱

让变量在容器内展开

Compose 文件中的 $VAR 通常会先被 Compose 处理。 如果希望由容器里的 Shell 读取变量,可使用 $$,并显式调用 Shell:

1
2
3
4
5
6
services:
  probe:
    image: alpine:3.22
    environment:
      APP_MODE: "production"
    command: ["sh", "-c", "printf '%s\\n' \"$$APP_MODE\""]

这是一个单独的短命服务示例,执行后打印值并退出。 $$ 阻止 Compose 在宿主机阶段展开该变量;sh -c 负责在容器内展开。 仅把变量放进不经过 Shell 的参数数组,不会自动获得 Shell 展开行为。

语法依据见 Compose Interpolation。

空字符串和“没有设置”含义不同

${VAR:-default} 在变量不存在或为空时都使用默认值。 ${VAR-default} 只在变量不存在时使用默认值。 ${VAR:?message} 适合必须非空的输入,可以避免空地址、空路径一路传到运行阶段。

不要用空字符串临时屏蔽生产配置而不记录原因。 服务中的显式空值可能覆盖原本有效的 env_file 设置。

YAML 中类似布尔值的内容建议写成字符串:

1
2
3
environment:
  FEATURE_ENABLED: "false"
  WORKER_COUNT: "2"

这只能保证传入的是字符串。 应用把 "false" 解释为关闭还是非空即真,仍取决于它自己的配置解析方式。

配置正确、容器也正确,应用为什么仍不变

先看应用是否支持这个变量名,以及是否存在配置文件、命令行参数等更高优先级来源。 APP_URL、BASE_URL、PUBLIC_URL 不属于通用协议,不能跨项目随意替换。

再看读取时机:某些前端配置在构建镜像时写入静态文件,运行时设置环境变量不会重写已有前端产物。 需要构建参数的项目,应按项目文档配置 build.args 或专用启动脚本。

最后核对是否读到了正确服务和实例。 同一项目的 worker、web、scheduler 可能是三个容器,只更新其中一个不会改变另外两个。

观察结果 下一步
config 仍是旧值 查输入文件路径、Shell 覆盖和多文件合并
config 正确,exec printenv 是旧值 查项目名、Docker context 和容器重建
容器值正确,应用行为仍旧 查变量名、应用优先级、读取时机和实际访问实例
exec 失败,容器未运行 先看 ps -a 和对应服务日志

在实验中可以直接执行:

1
2
docker compose -p compose-env-lab ps -a
docker compose -p compose-env-lab logs --tail 100 probe

业务镜像可能没有 printenv 或 Shell。 这种情况下使用镜像提供的诊断命令,或由管理员查看容器配置并脱敏;不要为了排错在生产镜像里临时安装一堆工具。

密码、回退和实验清理

不要把真实密码写进示例仓库,也不要完整粘贴 config、inspect 或全部 env 输出。 .env 是普通文本文件,不提供加密能力。 可提交不含凭据的 .env.example,真实部署文件按项目要求限制读取权限并排除出版本控制。

应用支持文件读取凭据时,可以考虑 Compose secrets。 它将授权的 secret 作为容器中的文件提供,常见位置为 /run/secrets/<name>。 _FILE 变量只是一部分镜像采用的约定,不是所有应用自动支持的功能。 本地来源文件仍需妥善保护,见 Compose secrets 官方说明。

生产配置变更前,保存上一版配置和环境输入,并记录使用的项目名、镜像版本及命令参数。 回退时恢复那些文件,用同一组参数检查 config -q,再执行 up -d,最后验证应用行为。 配置回退不能替代数据库备份,也不保证能够逆转应用已经完成的数据迁移。

实验结束后,在 compose-env-lab 目录执行:

1
docker compose -p compose-env-lab down

本例没有声明数据卷,无需增加 -v。 不要把这条实验命令改成全局清理操作;空间维护另见 Docker 磁盘空间清理指南。

排错结束时,应分别确认:解析后的配置符合预期、运行容器获得了新值、应用实际行为已经变化。 这三项对应三个不同层次,逐层确认才能避免反复修改 .env 却找错原因。