目录

Docker 环境变量教程:配置 .env 与容器环境

同一个镜像在开发、测试和生产环境中可能需要连接不同的数据库、使用不同的设置。最常见的做法是把这些设置放在环境变量中,运行容器时再传入。

使用 -e 设置

$ docker run --rm -e NODE_ENV=production -e PORT=8080 alpine:3.23 sh -c "echo \$NODE_ENV \$PORT"
production 8080

$ export API_KEY=abc123 && docker run --rm -e API_KEY alpine:3.23 sh -c "echo API_KEY=\$API_KEY"
API_KEY=abc123
  • -e 名称=值 直接设置变量。
  • 只写 -e 名称(不写值)会沿用主机上当前同名的环境变量。

使用 --env-file 读取文件

变量较多时,写在命令中很难管理。可以把它们放进文件:

$ cat app.env
# app.env
NODE_ENV=production
PORT=8080
GREETING="hello world"
DATABASE_URL=postgres://app:secret@db:5432/app

$ docker run --rm --env-file app.env alpine:3.23 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=e311730363f7
NODE_ENV=production
PORT=8080
GREETING="hello world"
DATABASE_URL=postgres://app:secret@db:5432/app
HOME=/root

注意 GREETING 这一行:文件中写的是 "hello world",容器中取得的值包含引号。docker run --env-file 的规则是:

  • 每行一个 KEY=VALUE,以 # 开头的行是注释。
  • 值不要加引号,引号会被当作值的一部分。
  • 不支持 $OTHER_VAR 变量展开。

(下面会看到,Compose 读取同一个文件时会去掉引号,两者的行为不同。)

在 Dockerfile 中设置默认值

ENV NODE_ENV=production \
    PORT=3000

ENV 设置的是默认值,docker run -e 可以覆盖它。适合放置“不太会变,也不是机密”的设置。

优先级

环境变量的优先级:docker run -e 或 compose environment 最高,其次是 env-file,然后是 Dockerfile ENV;.env 用于替换 compose.yaml 中的变量
环境变量的优先级,以及 .env 的不同用途

从高到低:

  1. docker run -e
  2. docker run --env-file
  3. Dockerfile 中的 ENV
  4. 基础镜像中的 ENV

环境变量能被看到吗?

环境变量很方便,但并不安全。任何能运行 docker inspect 的人都能看到:

$ docker run -d --name envdemo -e SECRET_TOKEN=abc123 alpine:3.23 sleep 60
431168d97dc8187ec79e420638155b2f53fd78a9d82f8a6d13eb6bd9aec09bcb

$ docker inspect --format '{{json .Config.Env}}' envdemo
["SECRET_TOKEN=abc123","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]

$ docker rm -f envdemo
envdemo

此外,程序出错时可能会把环境变量打印到日志中,子进程也会继承它们。因此,密码和 API 密钥请参考下面“处理机密信息”一节中的建议。

Docker Compose 中的环境变量

Compose 中有两种容易混淆的“环境变量”,下面用一个示例说明。compose.yaml:

services:
  app:
    image: alpine:3.23
    command: ["sh", "-c", "env | sort | grep -E 'APP_|DATABASE|NODE_ENV|PORT|GREETING'"]
    environment:
      APP_VERSION: ${APP_VERSION}
      DATABASE_PASSWORD: ${DB_PASSWORD}
    env_file:
      - app.env

1. 传给容器的变量:environment 与 env_file

environment 直接写在 compose.yaml 中;env_file 从文件读取(这里沿用上面的 app.env)。

2. 用于替换 compose.yaml 内容的变量:.env

Compose 会自动读取项目文件夹中的 .env 文件,用来替换 compose.yaml 中的 ${...}。使用 docker compose config 可以查看替换后的最终配置:

$ cat .env
APP_VERSION=1.4.2
DB_PASSWORD=change-me

$ docker compose config app
name: ch15
services:
  app:
    command:
      - sh
      - -c
      - env | sort | grep -E 'APP_|DATABASE|NODE_ENV|PORT|GREETING'
    environment:
      APP_VERSION: 1.4.2
      DATABASE_PASSWORD: change-me
      DATABASE_URL: postgres://app:secret@db:5432/app
      GREETING: hello world
      NODE_ENV: production
      PORT: "8080"
    image: alpine:3.23
    networks:
      default: null
networks:
  default:
    name: ch15_default

$ docker compose run --rm app
[+] run 1/1
 ✔ Network ch15_default Created                                                                 0.0s
Container ch15-app-run-dfbcf95b7271 Creating
Container ch15-app-run-dfbcf95b7271 Created
APP_VERSION=1.4.2
DATABASE_PASSWORD=change-me
DATABASE_URL=postgres://app:secret@db:5432/app
GREETING=hello world
NODE_ENV=production
PORT=8080
  • ${APP_VERSION}、${DB_PASSWORD} 被替换成了 .env 中的值。
  • Compose 读取 app.env 中的 GREETING="hello world" 后会去掉引号,这与 docker run --env-file 不同。
  • PORT: "8080" 加上了引号,表示它是字符串(环境变量一律是字符串)。

.env 本身不会自动传入容器,它只用于替换 compose.yaml。要将变量传入容器,必须在 environment 中引用(如 APP_VERSION: ${APP_VERSION}),或者使用 env_file: .env。

默认值与必填检查

写法含义
${VAR}替换为 VAR 的值
${VAR:-默认值}未设置或为空时使用默认值
${VAR:?错误消息}未设置或为空时直接报错

${VAR:?} 很适合用于密码这类“必须设置”的变量。忘记设置时会立即停止,而不是使用空密码启动:

services:
  db:
    image: postgres:18
    environment:
      POSTGRES_PASSWORD: ${MISSING_PASSWORD:?请在 .env 中设置 MISSING_PASSWORD}
$ docker compose -f compose.required.yaml config
error while interpolating services.db.environment.POSTGRES_PASSWORD: required variable MISSING_PASSWORD is missing a value: 请在 .env 中设置 MISSING_PASSWORD

处理机密信息

  • 不要把密码写进 Dockerfile 的 ENV 或 ARG,它们会留在镜像中(ARG 的实测见 Dockerfile 指令大全)。
  • 不要将 .env 提交到 Git。在 .gitignore 中加入 .env,另外提供一个不含真实值的 .env.example。
  • 在 .dockerignore 中加入 .env,避免它被 COPY . . 打包进镜像。

通过文件传递机密信息(Compose secrets)

许多官方镜像支持以 _FILE 结尾的变量,从文件中读取密码:

services:
  db:
    image: postgres:18
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt
$ docker compose up -d db
[+] up 1/1
 ✔ Container ch15-db-1 Started                                                                  0.3s

$ docker compose exec db sh -c "ls -l /run/secrets; cat /run/secrets/db_password; echo; env | grep POSTGRES"
total 4
-rw-rw-r-- 1 1001 1001 16 Oct 12 14:25 db_password
s3cret-from-file
POSTGRES_PASSWORD_FILE=/run/secrets/db_password

$ docker compose down
[+] down 2/2
 ✔ Container ch15-db-1  Removed                                                                 0.1s
 ✔ Network ch15_default Removed                                                                 0.1s

机密内容以文件形式出现在容器的 /run/secrets/db_password 中,环境变量里只有文件路径 POSTGRES_PASSWORD_FILE,docker inspect 看不到密码本身。

构建时需要的机密信息

例如,安装私有包需要 token,请使用 BuildKit 的 secret 挂载:

# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
docker build --secret id=npm_token,env=NPM_TOKEN -t myapp .

token 只在这条 RUN 命令执行期间存在,不会留在任何层中。

重点回顾

  • 优先级:-e/environment > --env-file/env_file > Dockerfile ENV。
  • .env 用于替换 compose.yaml 中的 ${VAR},不会自动传入容器。
  • docker run --env-file 不会去掉值中的引号,Compose 会。
  • 通过文件(secrets)传递机密信息,不要把它们写进镜像。

动手练习

练习 1:镜像中有 ENV MODE=prod,使用 docker run -e MODE=dev 运行时,容器中的 MODE 是什么?

答案

dev。-e 的优先级高于 Dockerfile 的 ENV。

练习 2:compose.yaml 中写了 POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required},但 .env 中没有 DB_PASSWORD。运行 docker compose up 会怎样?

答案

Compose 会直接报错并停止,显示 DB_PASSWORD is required,不会使用空密码创建数据库。

常见问题

修改 .env 后,容器的环境变量没有变化?

环境变量在创建容器时确定。修改后需要重建容器:Compose 使用 docker compose up -d,restart 不会应用更改。

用 docker inspect 能看到我的密码吗?

能。通过 -e 传入的值都会出现在 docker inspect 中,敏感数据请改用文件(secrets)方式传递。

${VAR:-默认值} 和 ${VAR:?消息} 有什么区别?

前者在未设置时使用默认值;后者在未设置时直接报错并停止,适用于必填密码,避免值变成空字符串。

更新于 2026年10月14日