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 主机。
|
|
建立独立目录 compose-env-lab,在其中新建以下三个文件。
不要直接把实验服务追加到生产项目中。
|
|
compose.yaml:
|
|
.env:
|
|
app.env:
|
|
本例没有端口映射、数据卷或特权权限。 镜像需要从你的 Docker 主机能够访问的仓库拉取;实验结束前保持文件不变,便于对照。
先在这个目录执行:
|
|
config -q 成功只表示配置校验通过。
config --environment 显示用于插值的变量;普通 config 展示合并、解析后的服务模型。
它们都不能证明容器已经创建,更不能证明应用读取到了值。
这些输出可能包含密码,只在可信终端查看,分享前删去敏感值。 具体选项见 docker compose config。
启动后只读取需要核对的变量
|
|
如果启动 Compose 的终端没有同名变量覆盖,预期依次看到:
|
|
APP_MODE 来自 .env 的插值结果,覆盖了 app.env 中的同名项。
APP_COLOR 通过服务的 env_file 注入。
APP_LABEL 是必填项,缺失或为空时会在配置解析阶段报错。
再单独查询没有被声明传入容器的变量:
|
|
预期没有输出,命令返回非零状态。
这是实验的正常结果,说明 .env 并不是全量容器环境注入文件。
.env 的值为什么被终端覆盖
Compose 解析 ${APP_MODE} 时,启动它的 Shell 中若已有同名变量,会优先使用 Shell 的值。
所以“文件写的是 production,解析出来是 staging”时,先检查终端环境。
在 Bash 中可以这样查看和清除实验变量:
|
|
在 PowerShell 中对应为:
|
|
清除当前终端变量前,确认它不是其他任务需要的设置。 IDE 任务、CI 作业和系统服务继承的环境,也可能与手动打开的终端不同。
为了减少工作目录造成的歧义,部署命令可以显式固定配置和输入文件:
|
|
把 myapp 换成现有项目名,不要在生产部署时随意改名。
项目名参与容器、网络和默认卷名称的生成,换名可能得到另一套资源。
--env-file 的相对路径以执行命令时的工作目录为基准;服务 env_file 的常规相对路径则以 Compose 文件位置为基准。
多个 Compose 文件、--project-directory、COMPOSE_FILE 会增加路径关系的复杂度,排错时先用明确路径收敛范围。
同时指定多个插值输入文件时,后面的文件可以覆盖前面的同名项:
|
|
Shell 中已有的同名变量仍可能优先。
默认 .env 的发现细节应结合当前 CLI 版本核对,见 Compose 插值与文件加载规则。
容器里的优先级要单独检查
插值输入确定后,还要看服务最终怎么设置容器变量。 在常见的显式赋值场景中,可以按下面的覆盖关系理解:
| 场景 | 对结果的影响 |
|---|---|
docker compose run -e APP_MODE=debug |
覆盖这次临时容器的对应变量 |
服务 environment 中设置 APP_MODE |
覆盖服务 env_file 的同名值 |
服务列出多个 env_file |
后面的文件覆盖前面的同名值 |
镜像 Dockerfile 中的 ENV |
未被运行时同名配置覆盖时才保留 |
这里的 environment 值可能本身来自 Shell 或 .env 插值。
不要把“值从哪里解析出来”和“通过哪个服务字段传进容器”混成一个步骤。
只写变量名而不给值的透传写法还有未解析时移除变量的语义,排错时优先使用明确赋值。
可以在实验中验证临时覆盖:
|
|
预期输出 debug。
这创建的是一次性容器,不会修改之前 up -d 启动的 probe 容器。
也不要把 run -e 当成 up 的通用参数。
完整边界和透传案例见 官方环境变量优先级说明。
改完文件后,为什么 restart 仍然是旧值
容器的环境变量在创建容器时确定。
docker compose restart 重启已有容器,不会根据修改后的配置重新创建容器环境。
把实验 .env 中的 APP_MODE 改成 staging 后,可以依次对照:
|
|
若容器最初使用 production,重启后仍应是该值。
此时配置文件和正在运行的容器处于不同状态。
确认解析结果正确后应用变更:
|
|
Compose 通常会检测配置变化并重建受影响服务。 确有必要强制重建时,使用:
|
|
重建会中断服务,应按业务维护窗口执行。 数据库等依赖服务是否需要联动,应由实际架构决定,不要习惯性重建整个项目。
挂载的数据卷会按配置继续使用,但容器可写层中的临时修改可能丢失。 操作前应确认业务数据确实存放在持久化位置。 官方行为说明见 restart 与 up。
美元符、空值和布尔值的常见陷阱
让变量在容器内展开
Compose 文件中的 $VAR 通常会先被 Compose 处理。
如果希望由容器里的 Shell 读取变量,可使用 $$,并显式调用 Shell:
|
|
这是一个单独的短命服务示例,执行后打印值并退出。
$$ 阻止 Compose 在宿主机阶段展开该变量;sh -c 负责在容器内展开。
仅把变量放进不经过 Shell 的参数数组,不会自动获得 Shell 展开行为。
语法依据见 Compose Interpolation。
空字符串和“没有设置”含义不同
${VAR:-default} 在变量不存在或为空时都使用默认值。
${VAR-default} 只在变量不存在时使用默认值。
${VAR:?message} 适合必须非空的输入,可以避免空地址、空路径一路传到运行阶段。
不要用空字符串临时屏蔽生产配置而不记录原因。
服务中的显式空值可能覆盖原本有效的 env_file 设置。
YAML 中类似布尔值的内容建议写成字符串:
|
|
这只能保证传入的是字符串。
应用把 "false" 解释为关闭还是非空即真,仍取决于它自己的配置解析方式。
配置正确、容器也正确,应用为什么仍不变
先看应用是否支持这个变量名,以及是否存在配置文件、命令行参数等更高优先级来源。
APP_URL、BASE_URL、PUBLIC_URL 不属于通用协议,不能跨项目随意替换。
再看读取时机:某些前端配置在构建镜像时写入静态文件,运行时设置环境变量不会重写已有前端产物。
需要构建参数的项目,应按项目文档配置 build.args 或专用启动脚本。
最后核对是否读到了正确服务和实例。 同一项目的 worker、web、scheduler 可能是三个容器,只更新其中一个不会改变另外两个。
| 观察结果 | 下一步 |
|---|---|
config 仍是旧值 |
查输入文件路径、Shell 覆盖和多文件合并 |
config 正确,exec printenv 是旧值 |
查项目名、Docker context 和容器重建 |
| 容器值正确,应用行为仍旧 | 查变量名、应用优先级、读取时机和实际访问实例 |
exec 失败,容器未运行 |
先看 ps -a 和对应服务日志 |
在实验中可以直接执行:
|
|
业务镜像可能没有 printenv 或 Shell。
这种情况下使用镜像提供的诊断命令,或由管理员查看容器配置并脱敏;不要为了排错在生产镜像里临时安装一堆工具。
密码、回退和实验清理
不要把真实密码写进示例仓库,也不要完整粘贴 config、inspect 或全部 env 输出。
.env 是普通文本文件,不提供加密能力。
可提交不含凭据的 .env.example,真实部署文件按项目要求限制读取权限并排除出版本控制。
应用支持文件读取凭据时,可以考虑 Compose secrets。
它将授权的 secret 作为容器中的文件提供,常见位置为 /run/secrets/<name>。
_FILE 变量只是一部分镜像采用的约定,不是所有应用自动支持的功能。
本地来源文件仍需妥善保护,见 Compose secrets 官方说明。
生产配置变更前,保存上一版配置和环境输入,并记录使用的项目名、镜像版本及命令参数。
回退时恢复那些文件,用同一组参数检查 config -q,再执行 up -d,最后验证应用行为。
配置回退不能替代数据库备份,也不保证能够逆转应用已经完成的数据迁移。
实验结束后,在 compose-env-lab 目录执行:
|
|
本例没有声明数据卷,无需增加 -v。
不要把这条实验命令改成全局清理操作;空间维护另见 Docker 磁盘空间清理指南。
排错结束时,应分别确认:解析后的配置符合预期、运行容器获得了新值、应用实际行为已经变化。
这三项对应三个不同层次,逐层确认才能避免反复修改 .env 却找错原因。