Docker Compose 配置了 env_file 仍提示变量未设置
Docker Compose 声明 env_file 后仍提示变量未设置,可能是 environment 的插值空值覆盖了文件内容。区分容器环境与 CLI 插值,按用途调整配置,并在重建服务前检查最终模型。
密码明明写在 .env_mysql 中,服务也声明了 env_file,执行 Compose 时却出现:
The "MYSQL_ROOT_PASSWORD" variable is not set. Defaulting to a blank string.
随后新建的 MySQL 容器可能因为没有初始化密码而退出。这个警告发生在 Compose 读取配置时,不能据此认定 MySQL 拒绝了一个已经传入的密码。
一种触发写法是同时指定文件,又在 environment 中引用同名变量:
services:
db:
image: mysql:8.4
env_file:
- ./.env_mysql
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
这段配置用于说明故障,完整部署还需保留数据挂载等配置。下文使用当前 docker compose 插件的命令;旧版 docker-compose、docker stack deploy 或面板封装的启动流程,需要分别确认支持的参数和变量来源。
文件被读取,变量仍可能被空值覆盖
这份配置的含义是:env_file 为容器提供环境变量;environment 中的同名值会覆盖它,即使覆盖值为空。
${MYSQL_ROOT_PASSWORD} 要先由运行 Compose 的进程解析。它的来源是宿主 shell、CLI 指定的环境文件或默认 .env,不会因为服务声明了 .env_mysql 就自动从那里取值。找不到变量且没有默认值时,Compose 发出警告并替换为空字符串。
在这段配置中,Compose 可以先从 .env_mysql 读到密码,再用 environment 中解析出的空值覆盖它。只检查文件存在,或把文件改名为另一种 .env_*,都没有处理这个覆盖关系。
在启动容器前确认配置
先确认实际执行的是哪套 Compose:
docker compose version
docker compose -f ./compose.yaml config -q
compose.yaml 要替换成真实文件名。如果部署使用多个 -f、不同工作目录或 --project-directory,检查时沿用同一组参数,避免检查到另一套项目配置。服务的 env_file 相对路径从 Compose 文件所在目录解析;CLI 的 --env-file 相对路径则从执行命令的当前目录解析。
config -q只校验配置,不打印展开后的完整内容,也不会创建或启动服务。不过普通 ${VAR} 缺失时可能只是警告,校验成功不等于变量有值;下面的必填写法可以把这种情况变成错误。
需要检查真实展开值时,只在本地受控终端查看。普通 config 和 config --environment 可能输出密码,不要把整份结果贴进工单、聊天或 CI 日志。
变量只供容器使用时,保留 env_file
如果这些变量只是给 MySQL 容器使用,没有参与镜像名、端口或挂载路径的计算,可以删除 environment 中重复的同名条目。下面是新项目的最小配置示例:
services:
db:
image: mysql:8.4
env_file:
- ./.env_mysql
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
对应的 .env_mysql:
MYSQL_ROOT_PASSWORD=replace-with-a-long-random-root-password
MYSQL_DATABASE=appdb
MYSQL_USER=app
MYSQL_PASSWORD=replace-with-a-different-long-random-app-password
两个密码是占位值,不能用于正式部署。真实环境文件应限制读取权限并排除 Git。上述例子没有发布数据库端口;已有项目不要照搬整份文件覆盖配置,只调整相关变量,保留原服务名、数据卷、网络和项目名称。
删除的是同名覆盖项,不必删掉 environment 中其他有意设置的变量。规范允许单个 env_file 使用字符串,也允许列表;把字符串改成列表本身不会解决插值来源问题。
YAML 需要引用变量时,显式传入环境文件
如果变量确实要在 YAML 中展开,就让 CLI 读取这份文件。下面用明确映射代替服务的 env_file,让这些变量都从 CLI 指定的文件进入配置:
services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD is required}
MYSQL_DATABASE: ${MYSQL_DATABASE:?MYSQL_DATABASE is required}
MYSQL_USER: ${MYSQL_USER:?MYSQL_USER is required}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:?MYSQL_PASSWORD is required}
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
继续使用上一份 .env_mysql,先校验:
docker compose --env-file ./.env_mysql -f ./compose.yaml config -q
${VAR:?提示} 在变量未设置或为空时终止解析。这里要求四个变量都有值,是为了检查这份应用账号示例,不表示所有 MySQL 部署都必须设置这四项。.env 中的值也不会自动成为全部容器的环境;需要通过 environment 明确传入。
宿主 shell 中导出的同名变量优先于 --env-file。如果显式指定文件后值仍不对,在 Bash 或 Zsh 中可用子 shell 暂时排除这些变量再校验:
(
unset MYSQL_ROOT_PASSWORD MYSQL_DATABASE MYSQL_USER MYSQL_PASSWORD
docker compose --env-file ./.env_mysql -f ./compose.yaml config -q
)
unset 只作用于括号里的进程,不会清除当前终端的变量。如果检查因此恢复正常,说明宿主环境参与了覆盖;实际启动时也要明确采用哪份来源,不能只给检查命令加限制。
把文件改成默认 .env 也可为插值提供值,但项目中已有 .env 时不要直接替换。它可能还含有镜像标签、项目名等配置。显式 --env-file 更容易核对这次启动究竟使用哪份文件;选定后,校验和启动都应保留这个参数。
不打印密码地检查最终模型
对于上面的显式映射方案,可以让配置 JSON 直接进入 Python,只报告所需变量是否非空。以下命令适用于装有 Python 3 的 Bash 或 Zsh 环境:
(
set -o pipefail
docker compose --env-file ./.env_mysql -f ./compose.yaml config --format json |
python3 -c '
import json, sys
values = json.load(sys.stdin)["services"]["db"]["environment"]
keys = ("MYSQL_ROOT_PASSWORD", "MYSQL_DATABASE", "MYSQL_USER", "MYSQL_PASSWORD")
missing = [key for key in keys if not values.get(key)]
print("Missing or empty: " + ", ".join(missing) if missing else "Required variables are non-empty")
sys.exit(1 if missing else 0)
'
)
这一步检查的是准备交给容器的模型,仍未启动数据库。它不输出变量值;结果为非空也只说明值存在,不能证明密码与现有数据库账号一致。使用第一种 env_file 方案时,按实际启动方式移除 --env-file;服务名不同则修改 Python 中的 db。
如果警告中的名字是密码的一部分,例如密码里有 $,应继续检查环境文件的引号规则。普通环境文件会解析未加引号及双引号中的插值,单引号值按字面量处理。不要统一给所有美元符号加倍:YAML 中的 $$ 用于保留美元符号,不会让 environment 字段自动在容器里再次取值。
配置通过后再更新服务
修改容器环境需要重建容器。docker compose restart不会应用环境变量的改动。先保存原配置和环境文件,确认当前项目及数据挂载,安排数据库服务的维护窗口。
若采用显式插值方案,更新指定服务的命令为:
docker compose --env-file ./.env_mysql -f ./compose.yaml up -d --no-deps db
它可能停止并重建 db,不要把它当成只读检查。第一种方案则按原启动参数执行,不需要额外指定 CLI 环境文件。up 重建时会保留已挂载的数据卷;这不代替备份,也不表示换服务名、项目名或挂载路径后仍会连接原数据。
对于已经初始化的 MySQL 数据目录,官方镜像的初始化环境变量不会修改已有账号密码。如果 Compose 警告消失、变量也不为空,但应用仍报 Access denied,转向已有数据卷与账号检查,不要删除卷来让初始化重新发生。
更新后用应用的实际账号建立新连接,确认数据库身份、业务数据和权限都符合预期。需要回退时,恢复之前可用的配置与环境文件,用原项目参数更新对应服务;如果另外修改了数据库账号,凭据与应用配置也要一起恢复。整个排查不需要 down -v,也不需要开启空密码登录。


