跳至正文

Docker Compose 的 .env 文件不生效?排查了两小时,问题出在优先级顺序上

上周五下午五点半,我想着改个环境变量就下班——结果硬是搞到七点多才走。问题很简单:我把 docker-compose.yml 旁边的 .env 文件里的 DB_PORT 从 3306 改成了 3307,然后 docker compose up -d,结果容器起来后还是连的 3306。

说出来你可能不信,就这个破事儿,我排查了快两个小时。记录一下踩坑过程,下次别再犯。

复现问题

先看我的目录结构:

project/
├── docker-compose.yml
├── .env
└── app/
    └── .env

docker-compose.yml 长这样:

version: '3.8'
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
    ports:
      - "${DB_PORT}:3306"
  app:
    build: ./app
    environment:
      DB_PORT: ${DB_PORT}
      DB_PASSWORD: ${DB_PASSWORD}

.env 文件内容:

DB_PORT=3307
DB_PASSWORD=MySecret123

看起来没问题对吧?改完 DB_PORT=3307,执行:

docker compose down && docker compose up -d

然后用 docker compose ps 一看,端口映射还是 0.0.0.0:3306->3306/tcp。我人都傻了。

Docker Compose 变量优先级

排查过程

第一反应:是不是缓存?

docker compose down -v  # 加 -v 清 volume
docker compose up -d

没用,还是 3306。

第二反应:是不是没读 .env?

docker compose config 看看实际解析出来的配置:

docker compose config | grep DB_PORT

输出:

DB_PORT: "3306"

好家伙,确实是 3306,根本没读我改的 .env

第三反应:检查文件编码和换行符

file .env
# .env: ASCII text

不是编码问题。

关键突破:app/ 目录下还有一个 .env

我突然想起来,app/ 目录下的 app/.env 文件是之前写的,里面有一行:

DB_PORT=3306

docker compose config 的文档里写了这么一段话(很多人没注意):

When you set the same environment variable in multiple sources, Docker Compose uses priority: Shell env > –env-file > project .env > service working dir .env

更坑的是:docker composebuild 阶段会自动读取 build context 目录下的 .env 文件,而且这个优先级在某些情况下高于项目根目录的 .env

验证一下:

# 在 app/ 目录放一个 .env
echo "DB_PORT=3306" > app/.env

# 项目根 .env 写 DB_PORT=3307
echo "DB_PORT=3307" > .env

# compose config 看看
docker compose config | grep DB_PORT
# 输出: DB_PORT: "3306"  ← 取的是 app/.env 的值!

.env 文件冲突

解决方案

有三种修法,按推荐度排序:

方案一(推荐):用 env_file 明确指定

services:
  app:
    build: ./app
    env_file:
      - .env    # 明确指定读项目根目录的 .env

方案二:删掉子目录的 .env

如果 app/.env 不是必须的,直接删掉。简单粗暴但有效。

方案三:用 –env-file 参数强制指定

docker compose --env-file .env up -d

我最终用了方案一,因为 app/.env 是给本地开发用的(npm run dev 会读它),不能删。

三种解决方案

踩坑总结

  1. docker compose config 是排查神器。变量解析有问题,第一时间用它看实际值,别对着 compose 文件和 .env 文件肉眼 Debug。
  2. build context 里的 .env 会干扰变量解析。这个官方文档写得很隐晦,在 Compose Build Specification 那一节才提到。如果你的 compose 文件里既有 build 又有根目录 .env,要特别注意优先级。
  3. 项目根 .env 和 env_file 不是一回事。根 .env 是 compose 自动读的默认文件,用于变量替换(${VAR} 语法)。env_file 是把整个文件内容注入容器的环境变量。两者语义不同,优先级也不同。
  4. 不同 Compose 版本行为有差异。Compose v1(docker-compose)和 v2(docker compose)在这个细节上的处理不完全一致,实测用 v2 遇到这个问题概率更高。
  5. 排查时注意区分「变量替换」和「环境变量注入」${DB_PORT} 在 compose 文件解析阶段替换是一次,容器内的 $DB_PORT 是另一次。两个阶段可能读到不同的值——别搞混了。

折腾完这两个小时,我在想一个问题:Docker Compose 的 .env 优先级逻辑,算不算过度设计了?一个 .env 能解决的事,非要搞多层覆盖,最后坑的都是没仔细看文档的人。

你说是不是?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注