改了 .env,重启 Docker 容器后,网站仍然使用旧的 API Key、数据库地址或运行模式?先确认两件事:这个文件有没有进入当前服务的配置,以及容器有没有按新配置重新创建。
.env、命令行 --env-file、服务里的 env_file 看起来很像,用途却不同。本文按“Compose 读取 → 容器创建 → 应用使用”的顺序排查,适用于 VPS 上的 Compose 项目。下面用 app 代表应用服务名,实际操作要替换;验证示例只使用非敏感变量,不需要把真实密钥打印出来。
| 现象 | 优先检查 | 下一步 |
|---|---|---|
| 出现 variable is not set 警告 | 插值文件没加载、变量名拼错 | 显式指定文件,检查 ${VAR} |
| config 展开就是旧值 | Shell 变量、硬编码、覆盖文件 | 找出覆盖来源 |
| config 是新值,容器仍是旧值 | 只执行了 restart、操作错项目 | 确认项目后重建目标服务 |
| 容器变量已更新,页面没变化 | 应用配置缓存、构建时变量 | 按应用文档更新或重新构建 |
| 改数据库密码后认证失败 | 初始化密码与现有账号不同步 | 执行正式的密码轮换流程 |
Docker 的 restart 文档明确说明:修改 Compose 配置后,restart 不会把新的环境变量更新进已有容器。先看配置结果,再决定是否重建,能少走很多弯路。
假设项目有一个 .env:
APP_MODE=production
只有当 Compose 文件引用这个变量时,它才会参与对应位置的替换:
services:
app:
image: alpine:3.22
command: ["sleep", "3600"]
environment:
APP_MODE: "${APP_MODE:?请设置 APP_MODE}"
这个示例没有业务数据、没有开放端口,仅用于观察配置。镜像版本用于演示,不代表你的应用需要换成 Alpine。
如果文件放在别处,可用命令行 --env-file /实际路径/.env.production 指定插值来源。单独写一个 .env 文件,并不会让其中所有变量自动进入每个容器。Docker 插值文档说明了这个文件的作用与查找规则。
另一种写法是给服务明确列出文件:
services:
app:
image: alpine:3.22
command: ["sleep", "3600"]
env_file:
- ./app.env
把 APP_MODE=production 放进 app.env,就会成为 app 的容器环境变量。这里的相对路径以 Compose 文件位置为基准,不要与执行命令时的工作目录混淆。服务环境变量文档给出了这类配置。
如果另一个字段仍写着 ${APP_MODE},不能指望服务级 env_file 顺带给那个字段提供插值来源。项目插值与服务环境注入是两步,排障时分开看。
先进入真实部署目录,确认文件名。下面假设目录为 /opt/myapp,文件为 compose.yaml 和 .env.production:
cd /opt/myapp
pwd
ls -la compose.yaml .env.production
docker compose -f /opt/myapp/compose.yaml --env-file /opt/myapp/.env.production config --quiet
路径必须换成实际值。config --quiet 检查配置能否解析,不打印展开后的全部值。需要定位某个无敏感信息的变量时,再在自己的终端运行不带 --quiet 的 config,检查对应服务;输出可能含有密码和连接串,不要原样贴进公开工单。
如果部署脚本用了多个 -f、固定的 -p 项目名或额外的 --project-directory,排障命令也必须保持一致。不要为了排查随手换一个 -p,否则可能创建另一套容器。1Panel、Portainer 等面板还可能保存独立的环境变量或 Stack 配置,修改本地文件不等于更新面板里的部署来源。
先检查当前 Shell 是否已经设置同名变量。插值阶段,Shell 中的变量优先于插值文件,因此 --env-file 不是“强制覆盖一切”的开关。例如,在只有测试值的项目里:
APP_MODE=from-shell docker compose --env-file .env config
即使 .env 写着 APP_MODE=production,示例中的 ${APP_MODE} 仍会得到 from-shell。正式项目如果确认不该继承当前 Shell 的值,可以在当前会话 unset APP_MODE 后重新检查;变量名和影响范围要先核对。
另一个常见原因是 environment 覆盖了服务级 env_file:
services:
app:
image: alpine:3.22
command: ["sleep", "3600"]
env_file:
- ./app.env
environment:
APP_MODE: development
这里 app.env 再怎么修改 APP_MODE,environment 里的显式值仍会覆盖它。甚至显式空值也可能覆盖文件中的值,不要只搜索“旧字符串”,还要检查同名键。Docker 环境变量优先级说明区分了这些来源。
还要留意额外 Compose 文件是否重写了该服务。config 展示的是这次命令解析出的结果,只有命令参数与实际部署一致,它才有诊断价值。
确认配置、数据挂载和回滚方案后,在维护窗口执行。下面沿用相同文件参数,只处理 app:
docker compose -f /opt/myapp/compose.yaml --env-file /opt/myapp/.env.production up -d --no-deps app
docker compose -f /opt/myapp/compose.yaml --env-file /opt/myapp/.env.production ps app
Docker up 文档说明,服务配置变化后,up 会停止并重新创建相应容器,同时保留挂载卷。重建会短暂中断这个单实例服务,写在容器可写层、没有挂载出去的数据也不能当作持久数据保留。--no-deps 适合依赖服务已经正常运行、此次只更新应用的情况;需要联动更新时应单独安排。
如果确认模型正确、但还需要强制重建,可以使用:
docker compose -f /opt/myapp/compose.yaml --env-file /opt/myapp/.env.production up -d --no-deps --force-recreate app
它不会替你修复变量覆盖,也不会自动完成镜像重新构建。不需要先执行 down,更不要把 down -v 或删 Volume 当成环境变量修复步骤。
检查示例变量时,可在具备 printenv 的镜像中运行:
docker compose -f /opt/myapp/compose.yaml --env-file /opt/myapp/.env.production exec app printenv APP_MODE
只对非敏感字段这样做。极简镜像可能没有这个程序,可以改用应用自己的诊断接口;不要为确认 Key 是否更新而导出全部容器环境。随后测试一条真实业务请求,容器处于 running 也不代表配置已被应用采用。
包含美元符号的值要检查插值和引号。Compose 的 .env 中,单引号值按字面处理;不要直接把 Bash 脚本语法当成所有 env 文件的通用语法。docker run --env-file 与 Compose 的处理也有区别。先用不含秘密的 DEMO_VALUE 验证,确认不会被替换后,再处理真实配置。部分版本的 config 输出会保留转义后的 $$,不能仅凭这份序列化输出就断定容器最终收到两个美元符号。
如果想让命令里的变量留到容器启动后由 Shell 展开,需要在 Compose 中用 $$:
services:
app:
image: alpine:3.22
environment:
APP_MODE: production
command: ["sh", "-c", "printf '%s\\n' \"$$APP_MODE\"; sleep 3600"]
Compose 插值规范说明,$$ 可以避免 Compose 提前插值。这个例子显式调用了 sh -c;仅仅使用数组命令,并不会自动获得 Shell 展开能力。镜像也必须确实包含这个 Shell。
此时应转向应用配置:Laravel 等程序可能缓存了配置;部分前端变量在构建时就写进静态资源;某些服务首次启动后把设置保存到数据库。按对应项目文档处理缓存、构建或设置存储,继续重建容器未必有帮助。
数据库尤其要分清:初始化环境变量不是已存在账号的密码管理接口。例如已有数据卷的数据库,不能指望只改一个初始化密码变量就轮换账号密码。先通过该数据库支持的方式变更凭据,再同步应用配置;保留备份和回滚入口,不要删数据卷来“让初始化重跑”。
修复完成的标准是:同一部署命令的 config 解析正确、目标容器采用新配置、业务请求成功,且依赖服务与持久数据都正常。本文使用 Docker Compose 2.17.3 完成了 4 个 YAML 示例的解析,并验证 Shell 覆盖、必填空值拒绝和字面美元符号的配置输出。默认插值结果为 production,Shell 覆盖后为 from-shell,显式 environment 覆盖 env_file 后为 development。当前验证环境未运行 Docker 守护进程,没有执行容器重建或生产 VPS 业务验收。
你下一步可能需要:
