强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 6 章 · 环境变量:.env 文件、environment 与 env_file

第 6 章 · 环境变量管理

6.1 环境变量的重要性

环境变量是容器化应用中配置外部化的核心手段,遵循 12-Factor App 方法论。

为什么用环境变量?

原则说明
配置与代码分离同一份镜像,不同环境用不同配置
无状态容器可随时替换,配置不绑定容器
安全敏感信息不硬编码在代码或镜像中
灵活性运行时覆盖,无需重新构建

6.2 环境变量的四种来源

Compose 中环境变量有四个来源,按优先级从高到低排列:

优先级:高 → 低

┌─────────────────────────────────┐
│  1. docker compose run -e       │  命令行注入
│     或 shell 环境变量            │
├─────────────────────────────────┤
│  2. environment: 指令            │  compose.yaml 中直接指定
├─────────────────────────────────┤
│  3. env_file: 指令               │  从文件加载
├─────────────────────────────────┤
│  4. Dockerfile ENV              │  镜像构建时设置
└─────────────────────────────────┘

⚠️ 注意:高优先级的值会覆盖低优先级的值。理解这个顺序对调试配置问题至关重要。


6.3 environment 指令

compose.yaml 中直接定义环境变量。

两种语法

services:
  app:
    image: myapp:latest

    # 语法一:映射格式(推荐,更清晰)
    environment:
      NODE_ENV: production
      DATABASE_URL: postgresql://user:pass@db:5432/mydb
      LOG_LEVEL: info
      CACHE_TTL: "3600"        # 数字值建议加引号

    # 语法二:列表格式
    # environment:
    #   - NODE_ENV=production
    #   - DATABASE_URL=postgresql://user:pass@db:5432/mydb
    #   - LOG_LEVEL=info

空值处理

services:
  app:
    environment:
      # 显式设置为空字符串
      DEBUG: ""

      # 从宿主机继承(仅映射格式支持)
      HOME:        # 继承宿主机的 $HOME
      USER:        # 继承宿主机的 $USER
      PATH: /usr/local/bin:/usr/bin:/bin  # 可覆盖

💡 映射格式中,如果值为空,Compose 会从宿主机环境中继承同名变量。列表格式中 KEY= 会设置为空字符串,KEY(无等号)才会继承。

动态值插值

services:
  app:
    environment:
      # 引用 .env 文件或 shell 中的变量
      DATABASE_URL: postgresql://${DB_USER}:${DB_PASS}@db:5432/${DB_NAME}

      # 带默认值
      LOG_LEVEL: ${LOG_LEVEL:-info}
      PORT: ${PORT:-3000}

      # 仅在变量存在时使用(否则为空)
      EXTRA_CONFIG: ${EXTRA_CONFIG:-}

6.4 .env 文件

.env 文件用于定义 Compose 文件本身使用的变量(注意不是容器内的环境变量)。

默认行为

Compose 自动加载项目根目录下的 .env 文件(如果存在)。

# 项目结构
myproject/
├── .env              # ← Compose 自动加载
├── compose.yaml
└── ...

.env 文件语法

# .env — 注意:这不是 shell 脚本,但语法类似

# 注释行
# 空行会被忽略

# 基本赋值
DB_USER=admin
DB_PASS=s3cretP@ss
DB_NAME=myapp

# 值包含空格时必须加引号
APP_TITLE=My Application

# 单引号(不解释变量)
GREETING='Hello $USER'

# 双引号(解释变量)
FULL_GREETING="Hello $USER"

# 可用于端口、版本等
POSTGRES_VERSION=16
WEB_PORT=8080

在 compose.yaml 中使用 .env 变量

# .env
# DB_USER=admin
# DB_PASS=s3cretP@ss
# DB_NAME=myapp

# compose.yaml
services:
  db:
    image: postgres:${POSTGRES_VERSION:-16}-alpine
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASS}
      POSTGRES_DB: ${DB_NAME}

  web:
    image: myapp:latest
    ports:
      - "${WEB_PORT:-8080}:3000"
    environment:
      DATABASE_URL: postgresql://${DB_USER}:${DB_PASS}@db:5432/${DB_NAME}

.env 与 environment 的区别

维度.envenvironment:
用途Compose 文件中的变量替换容器内的环境变量
加载时机Compose 读取 YAML 时容器启动时
谁使用Compose CLI容器内进程
示例${WEB_PORT}8080NODE_ENV: production
# 关键区别演示
services:
  app:
    ports:
      - "${WEB_PORT}:3000"      # ← .env 变量,由 Compose 解析
    environment:
      PORT: "3000"               # ← 容器环境变量,传入容器
      DB_HOST: db                # ← 容器环境变量

6.5 env_file 指令

env_file 将文件中的变量直接注入为容器环境变量,适合管理大量变量。

基本用法

services:
  app:
    image: myapp:latest
    env_file:
      - app.env                # 相对路径(相对于 compose.yaml)
      - common.env
# app.env
NODE_ENV=production
LOG_LEVEL=info
DATABASE_URL=postgresql://user:pass@db:5432/mydb
API_KEY=sk-abc123xyz

多 env_file 与合并

services:
  app:
    env_file:
      - common.env             # 通用配置(先加载)
      - app.env                # 应用配置(后加载,会覆盖同名变量)
      - secrets.env            # 敏感配置(最后加载)

加载顺序:文件按列表顺序加载,后面的文件会覆盖前面同名的变量。

env_file 高级选项

services:
  app:
    env_file:
      - path: app.env         # 长语法
        required: true         # 必须存在,否则报错(Compose V2.24+)

      - path: optional.env
        required: false        # 可选,不存在不报错

env_file 语法注意事项

# app.env

# ✅ 正确
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp

# ✅ 值包含空格
APP_NAME=My Application

# ✅ 引号(引号会成为值的一部分 ⚠️)
DB_PASS="s3cret"     # 容器内值为 "s3cret"(含引号!)
DB_PASS2='s3cret'    # 同上

# ❌ 不支持变量引用
HOME_DIR=$HOME       # 字面值 "$HOME",不会展开

# ❌ 不支持 export
export DB_HOST=...   # 会将 "export DB_HOST" 作为变量名

⚠️ 易错点env_file 中的引号不会被去除DB_PASS="s3cret" 传入容器的值是 "s3cret"(包含双引号),而不是 s3cret。大多数情况下不应使用引号。


6.6 优先级实验

通过以下实验理解优先级:

# compose.yaml
services:
  demo:
    image: alpine:3.20
    command: ["sh", "-c", "echo VAR=$VAR && sleep 3600"]
    env_file:
      - demo.env
    environment:
      VAR: "from-environment"
# demo.env
VAR=from-env-file
# 实验 1:使用 environment 值
docker compose up
# 输出: VAR=from-environment

# 实验 2:通过命令行注入(更高优先级)
VAR=from-shell docker compose up
# 输出: VAR=from-shell

# 实验 3:run -e 注入
docker compose run -e VAR=from-run-flag demo
# 输出: VAR=from-run-flag

优先级汇总表

来源优先级示例
docker compose run -e🥇 最高docker compose run -e K=V app
Shell 环境变量🥈K=V docker compose up
environment: 指令🥉environment: {K: V}
env_file: 指令4env_file: app.env
Dockerfile ENV5(最低)ENV K=V

6.7 变量替换(Interpolation)

Compose 文件中可以使用 ${VAR}$VAR 语法引用变量。

语法大全

services:
  app:
    environment:
      # 基本引用
      DB_HOST: ${DB_HOST}

      # 带默认值(变量未设置时使用)
      DB_PORT: ${DB_PORT:-5432}

      # 带默认值(变量为空或未设置时使用)
      DB_NAME: ${DB_NAME:-myapp}

      # 错误提示(变量未设置时报错)
      DB_PASS: ${DB_PASS:?数据库密码必须设置}

      # 仅在变量为空或未设置时使用默认值
      LOG_LEVEL: ${LOG_LEVEL-default}

      # 仅在变量未设置时使用默认值(空字符串时不触发)
      CACHE_TTL: ${CACHE_TTL-3600}

替换操作符对比

操作符变量未设置变量为空字符串 ""变量有值
${VAR}变量值
${VAR:-default}defaultdefault变量值
${VAR-default}default变量值
${VAR:?error}报错退出报错退出变量值
${VAR?error}报错退出变量值

💡 建议:大多数场景使用 ${VAR:-default} 最安全——未设置和空值都用默认值。

关闭变量替换

如果 compose.yaml 中包含 $ 字符但不想被替换(如 shell 脚本),使用 $$ 转义:

services:
  app:
    entrypoint: /bin/sh -c
    command:
      - |
        echo "Literal dollar: $$HOME"
        # 不会被 Compose 替换,传入容器后由 shell 解释

6.8 多环境管理模式

模式一:多个 .env 文件

project/
├── .env                  # 默认(开发)
├── .env.staging          # 预发布
├── .env.production       # 生产
├── compose.yaml
└── compose.prod.yaml
# 开发环境
docker compose up

# 生产环境
docker compose -f compose.yaml -f compose.prod.yaml --env-file .env.production up -d

模式二:env_file 分层

services:
  app:
    image: myapp:latest
    env_file:
      - env/common.env          # 通用配置
      - env/${APP_ENV:-dev}.env # 环境特定配置
# env/common.env
LOG_FORMAT=json
APP_NAME=myapp

# env/dev.env
LOG_LEVEL=debug
DATABASE_URL=postgresql://dev:dev@localhost:5432/myapp_dev

# env/prod.env
LOG_LEVEL=warn
DATABASE_URL=postgresql://prod:prod@db:5432/myapp

模式三:变量组合

# compose.yaml — 通用基础配置
services:
  app:
    image: ${DOCKER_REGISTRY:-docker.io}/${DOCKER_REPO:-myorg}/myapp:${APP_VERSION:-latest}
    ports:
      - "${APP_PORT:-8080}:3000"
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      LOG_LEVEL: ${LOG_LEVEL:-debug}
      DATABASE_URL: ${DATABASE_URL}
      REDIS_URL: ${REDIS_URL:-redis://cache:6379}

6.9 敏感信息处理

❌ 不推荐:直接写在 compose.yaml

# 不要这样做!compose.yaml 会进入版本控制
services:
  db:
    environment:
      POSTGRES_PASSWORD: mysecretpassword    # ❌ 密码明文

⚠️ 可接受:使用 .env 文件

# compose.yaml
services:
  db:
    environment:
      POSTGRES_PASSWORD: ${DB_PASS}
# .env(加入 .gitignore)
DB_PASS=mysecretpassword
# .gitignore
.env
.env.*
!.env.example
# .env.example(提交到版本控制,作为模板)
DB_USER=
DB_PASS=
DB_NAME=myapp

✅ 推荐:Docker Secrets(Swarm 模式)

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

secrets:
  db_password:
    file: ./secrets/db_password.txt

💡 详见第 10 章「敏感信息管理」。


6.10 容器内读取环境变量

应用代码中读取

# Python
import os
db_url = os.environ.get("DATABASE_URL", "sqlite:///default.db")
log_level = os.getenv("LOG_LEVEL", "info")
// Node.js
const dbUrl = process.env.DATABASE_URL || "sqlite:///default.db";
const port = parseInt(process.env.PORT || "3000");
// Go
import "os"
dbURL := os.Getenv("DATABASE_URL")
if dbURL == "" {
    dbURL = "sqlite:///default.db"
}

运行时查看环境变量

# 查看容器的所有环境变量
docker compose exec app env

# 查看特定变量
docker compose exec app printenv DATABASE_URL

# 在 entrypoint 脚本中调试
docker compose exec app sh -c 'echo $DATABASE_URL'

环境变量与配置文件的配合

services:
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.template:/etc/nginx/templates/default.conf.template:ro
    environment:
      BACKEND_HOST: app
      BACKEND_PORT: "3000"
      SERVER_NAME: myapp.example.com
# nginx.template — Nginx 官方镜像支持 envsubst 自动替换
server {
    listen 80;
    server_name ${SERVER_NAME};

    location / {
        proxy_pass http://${BACKEND_HOST}:${BACKEND_PORT};
    }
}

💡 Nginx 官方镜像启动时会自动将 /etc/nginx/templates/ 下的模板文件中的 ${} 变量替换为环境变量值。


6.11 常见问题排查

问题原因解决方案
变量值为空变量名拼写错误docker compose config 检查解析结果
值包含引号env_file 中加了引号去除引号
变量未生效优先级被覆盖检查 environment > env_file > Dockerfile
.env 未加载文件名错误或不在项目根目录确认文件名和位置
? 操作符报错变量未设置且用了 :?提供变量或改用 :-
特殊字符问题$# 等字符被解释使用 $$ 转义或单引号

调试命令

# 查看完整的解析后配置(含变量替换结果)
docker compose config

# 查看特定服务的环境变量
docker compose config --format json | jq '.services.app.environment'

# 验证 .env 加载
docker compose config | grep "VAR_NAME"

6.12 小结

概念说明
environment:在 compose.yaml 中定义容器环境变量
env_file:从文件批量加载容器环境变量
.envCompose 文件变量替换用,非容器环境变量
优先级-e > shell > environment > env_file > Dockerfile
变量替换${VAR:-default} 语法,在 compose.yaml 中使用
安全实践.env 加入 .gitignore,提供 .env.example 模板

扩展阅读


上一章:第 5 章 · 数据卷 ← | 下一章:第 7 章 · 依赖与健康检查 →