SSD Nodes Learn 8GB 内存 — 每年 $66
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-01

Docker Compose 中 .env、env_file 和 secrets 的区别

Compose 中的 .env 只用于插值,env_file 才会注入容器环境;本文用命令验证 environment 的优先级,并说明为何密码不应放在环境变量中。

人们称为 env 文件的 3 种机制

Docker Compose 有 3 种名称相似但彼此独立的机制。.env 文件会在 Compose 解析文件之前,替换 compose.yaml 内的 ${VARIABLE} 占位符。env_file: 属性会将键值对文件加载到容器的环境中。environment: 属性会直接在容器上设置变量,这些变量写在 compose 文件中。它们不能互换。如果其中两种机制设置了相同的键,最终值由文档规定的优先级顺序决定。

本指南会分别演示每种机制,使用可运行的命令验证优先级,然后介绍更重要的内容:任何可以运行 docker inspect 的人都能读取环境变量,因此密码不应放在环境变量中。如果您刚开始接触 compose 文件,请先阅读 VPS 上的 Docker Compose 基础知识,再回到这里了解配置。

.env 文件用于 compose 文件,而不是容器

创建一个目录,并在其中放置两个文件。

mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .env
services:
  demo:
    image: alpine:${ALPINE_TAG}
    command: printenv ALPINE_TAG

现在让 Compose 显示它实际解析的内容。

docker compose config

输出显示 image: alpine:3.20。占位符已消失,因为插值发生在解析时。Compose 会在项目目录(即存放 compose 文件的目录)中查找 .env,并替换其中找到的每个 ${NAME}

然后运行服务。

docker compose run --rm demo

printenv ALPINE_TAG 以状态码 1 退出,并且不输出任何内容。该变量在容器内不存在。这是最常见的误解:.env 配置的是 compose 文件,而不是进程。除非 compose 文件的某个部分引用了它,否则在 .env 文件中写入 POSTGRES_PASSWORD=hunter2 对数据库完全不起作用。

${NAME:-default} 会在变量未设置或为空时提供备用值。${NAME:?message} 会让 Compose 拒绝启动并输出指定消息。对于没有安全默认值的配置,这是正确的选择。

env_file 将变量加载到容器中

env_file: 属性指定一个或多个文件,这些文件的内容会成为容器环境变量。

printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.env
services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
docker compose run --rm demo

此命令会输出 from_env_file。文件格式是普通的 KEY=value 行,每行一个变量;以 # 开头的内容表示注释。它不是 shell 文件。多数情况下,引号会作为值的一部分保留,不需要使用 export 前缀。不要在 = 符号两侧添加空格,否则 KEY = value 会生成一个名为 KEY 的变量,并在其值开头包含一个空格。

如果 env_file 路径不存在,就会报错,Compose 也会停止。若文件确实可能不存在,请将其标记为可选:

    env_file:
      - path: ./app.env
        required: false

environment 内联设置变量

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    environment:
      GREETING: from_environment

支持两种语法:上面的映射形式,以及使用 - GREETING=from_environment 的列表形式。两者的行为完全相同。列表形式还有一个额外用法:不带值的裸键会从运行 docker compose 的 shell 中透传该变量。

    environment:
      - GREETING
GREETING=from_my_shell docker compose run --rm demo

该命令会输出 from_my_shell。如果在 shell 中未设置 GREETING 就运行它,Compose 不会设置任何值,也不会发出警告。需要注意这种静默的透传失败,因为使用空密码变量启动的服务通常仍会成功启动,但实际上完全未受保护。

哪个优先级更高

Docker 记录了优先级顺序,按从高到低排列:命令行中的 docker compose run -e,然后是其值从 shell 或 env 文件插值得到的 environmentenv_file,接着是 compose 文件中的普通 environment,然后是 env_file,最后是构建到镜像中的 ENV 指令。

日常使用时可以简单记住:environment: 的优先级高于 env_file:,而命令行中的 -e 的优先级高于这两者。可以在一个文件中验证这一点。

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
    environment:
      GREETING: from_environment
docker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETING

第一个命令输出 from_environment,说明 environment: 覆盖了 app.env 中的值。第二个命令输出 from_cli。compose 文件中的任何设置都无法覆盖命令行设置。

当容器的行为表明配置似乎没有生效时,不要猜测。docker compose config 会输出完全解析后的文件,docker compose config --environment 会输出 Compose 正在使用的插值变量。大多数“我的 env 文件被忽略了”的问题,最终都是因为同一个值在两个不同层级被设置了两次。

环境变量为何会泄露

environment: 中设置密码后,密码会存储在容器的配置文件中。docker 组中的任何用户都可以查看该文件。

docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'

输出中包含明文形式的 "DB_PASSWORD=hunter2"。另外还有 3 个路径会暴露相同的值。docker compose config 会将其打印到终端,因此它可能被复制并粘贴到支持论坛中。容器中的任何进程都可以读取 /proc/1/environ,并且每个子进程都会继承该变量。应用程序的崩溃处理程序还经常将整个环境转储到日志或错误报告中。

属于 docker 组实际上等同于在主机上拥有 root 权限,因此不能将其视为可靠的权限边界。有关 VPS 上的最小权限用户账户 的指南解释了为什么在任何共享服务器上都应限制该组的成员资格。

Compose secrets 将值保存在文件中

Compose 支持基于文件的 secrets。值会以文件形式挂载到容器中,而不是注入环境变量。

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
secrets:
  db_password:
    file: ./db_password.txt

secret 会挂载到容器内的 /run/secrets/db_password。斜杠后的名称来自顶层 secrets: 块中的 secret 名称。

_FILE 后缀是 Docker Official Images 使用的约定,包括 postgresmysqlmariadb。这些 entrypoint 脚本会检查 VARNAME_FILE,读取该文件并使用其内容。这不是 Docker 功能,因此只有实现了该约定的镜像才能使用。不要默认 SOMETHING_FILE 会生效,应先查阅镜像文档。不支持该约定的应用通常可以在启动时自行读取该文件,或者传递文件路径,再由您自己的 entrypoint 处理。

从运行中的容器内部验证:

docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORD

第一个命令会输出密码。第二个命令不会输出任何内容,因为该值从未进入环境变量。这正是该方式的作用:此容器中的 docker inspect 只显示无敏感信息的路径。

保护主机上的源文件,因为 secret 的私密性取决于其背后的文件:

chmod 600 db_password.txt

VPS 上的实用折中方案

许多自托管镜像不支持 _FILE 变量,因此环境变量是唯一的注入方式。对于由单个管理员维护的 VPS,实际目标是避免这些值存放在项目目录中任何人都可读的文件里,并将它们排除在 git 之外。

sudo install -o root -g root -m 600 /dev/null /etc/myapp/app.env
sudo nano /etc/myapp/app.env
    env_file:
      - /etc/myapp/app.env

install -m 600 创建文件时就设置好权限,因此不存在所有用户都能读取文件的时间窗口。文件由 root 所有,因此本机上的非 root 用户无法读取它。不过,任何能够运行 docker 的人仍然可以从容器中读取该值。将 *.env.env 添加到 .gitignore,并提交一个仅包含键名且值为空的 app.env.example。已提交的密码就应视为已轮换的密码。

轮换值意味着重启服务。容器进程启动时只读取一次环境变量,因此编辑文件不会立即生效,直到运行 docker compose up -d --force-recreate db在 VPS 上通过 HTTPS 运行 n8n 指南也采用相同模式,其中加密密钥存放在 compose 文件之外。

按环境拆分配置

Compose 默认从项目目录读取 .env。使用 --env-file 将其指向其他位置。

docker compose --env-file .env.staging config

系统会按顺序读取多个文件,后读取的文件会覆盖先读取的文件。将非机密默认值保存在已提交的文件中,将机密信息保存在不会离开服务器的文件中。env_file: 也遵循相同规则;对于重复的键,最后列出的文件优先。

FAQ

为什么容器内的 .env 文件会被忽略?

它并没有被忽略。.env 文件只会替换 compose 文件中的 ${NAME} 占位符。它不会在容器内设置变量。要将值传入容器,请引用它:environment: { KEY: "${NAME}" },或者改用 env_file: ./that-file.env

environment 会覆盖 env_file,还是反过来?

environment: 的优先级更高。Docker 的文档规定,environment 属性的优先级高于 env_file 属性,而这两者的优先级都低于命令行上的 docker compose run -e。如果同一个键在两个位置都设置了,env_file 中的值会被静默忽略。

如何查看 Compose 将使用的最终值?

运行 docker compose config,输出应用所有插值后的完整 compose 文件。对于已经运行的容器,docker inspect <container> --format '{{json .Config.Env}}' 会准确显示其进程接收到的内容。

Compose secret 会加密吗?

不会。基于文件的 secret 会以普通文件的形式挂载到容器中的 /run/secrets/<name>,源文件也会以未加密形式保存在主机磁盘上。它的优势在于作用域,而不是加密:该值不会出现在容器环境、docker inspect 输出或打印环境变量的崩溃转储中。

可以在 env 文件中使用引号和空格吗?

使用 KEY=value with spaces,并省略引号。Compose 会将该行其余全部内容作为值,因此引号通常会成为值中的字面字符。不要在 = 两侧添加空格,否则键名会包含尾随空格,导致无法匹配。