Docker Compose 提供多种方式将环境变量注入容器:environment 指令、env_file 指令、根级 .env 文件和构建时 ARG 值。每种方式行为不同,具有不同的优先级规则、安全影响和使用场景。本指南详细说明何时使用哪种方式,并提供可直接复制的 docker-compose.yml 示例。
快速答案:env_file 用于密钥,environment 用于配置
如果你只需要一个快速经验法则:
- environment: 使用 environment 设置非敏感的、服务特定的配置,这些配置你希望在 Compose 文件中可见(端口号、日志级别、功能开关)。
- env_file: 使用 env_file 管理密钥和分组变量,将它们排除在版本控制之外(API 密钥、数据库密码、第三方凭证)。
- .env: 使用根级 .env 文件仅用于 Compose 文件插值(镜像标签、端口映射、副本数量)。
- ARG: 使用 ARG 设置构建时的值,这些值不应在运行时存在(构建工具版本、依赖标志)。
# Quick reference — same service using all methods:
services:
api:
build:
context: .
args:
NODE_VERSION: 20 # ARG: build-time only
image: myapp:${TAG:-latest} # .env: Compose interpolation
env_file:
- .env.secrets # env_file: secrets (gitignored)
environment:
NODE_ENV: production # environment: inline config
LOG_LEVEL: info
PORT: "3000"environment:内联和映射语法
environment 指令直接在 docker-compose.yml 中定义键值对。这些值在版本控制中可见,并覆盖 env_file 中的值。
列表语法(数组)
使用数组中的 KEY=VALUE 字符串。在教程和快速设置中最常见:
services:
api:
image: node:20-alpine
environment:
- NODE_ENV=production
- DB_HOST=postgres
- DB_PORT=5432
- LOG_LEVEL=info
- CACHE_TTL=3600映射语法(字典)
使用 YAML 键值映射。对大型配置更清晰,支持 YAML 锚点:
# Map syntax — cleaner for large configs
services:
api:
image: node:20-alpine
environment:
NODE_ENV: production
DB_HOST: postgres
DB_PORT: "5432" # Quotes needed for numeric strings
LOG_LEVEL: info
CACHE_TTL: "3600"
# YAML anchors work with map syntax
x-common-env: &common-env
NODE_ENV: production
LOG_LEVEL: info
TZ: UTC
worker:
image: node:20-alpine
environment:
<<: *common-env # Merge common variables
QUEUE_NAME: tasks
CONCURRENCY: "4"覆盖行为
environment 中的值始终覆盖 env_file 中的相同键。这是设计使然,允许你在文件中设置默认值并内联覆盖特定值:
# .env.shared (loaded via env_file)
DB_HOST=shared-database
DB_PORT=5432
DB_NAME=myapp
LOG_LEVEL=warn
# docker-compose.yml
services:
api:
image: node:20-alpine
env_file:
- .env.shared # DB_HOST=shared-database, LOG_LEVEL=warn
environment:
DB_HOST: custom-db # OVERRIDES env_file! DB_HOST=custom-db
LOG_LEVEL: debug # OVERRIDES env_file! LOG_LEVEL=debug
# DB_PORT and DB_NAME keep their env_file values
# Final result inside container:
# DB_HOST=custom-db (from environment, overrode env_file)
# DB_PORT=5432 (from env_file, not overridden)
# DB_NAME=myapp (from env_file, not overridden)
# LOG_LEVEL=debug (from environment, overrode env_file)Shell 透传
列出不带值的变量名,将主机 shell 变量传递到容器中:
# On your host machine:
export API_KEY=sk-abc123
export AWS_REGION=us-east-1
# docker-compose.yml
services:
api:
image: node:20-alpine
environment:
- API_KEY # Passes host $API_KEY into container
- AWS_REGION # Passes host $AWS_REGION into container
- CI # Empty string if $CI is not set on host
- DEBUG # Empty string if $DEBUG is not set on hostenv_file:外部文件加载
env_file 指令从一个或多个外部文件加载变量到容器环境中。与 environment 不同,这些文件可以被 gitignore,从而将密钥排除在版本控制之外。
.env 文件格式规则
# .env.api — env_file format rules
# Lines starting with # are comments
# Blank lines are ignored
# Basic KEY=VALUE (no spaces around =)
DB_HOST=postgres
DB_PORT=5432
# Values with spaces — use quotes
APP_NAME="My API Service"
GREETING='Hello World'
# No variable substitution! This is LITERAL:
# BASE_URL=${DOMAIN} # WRONG — will be literal "${DOMAIN}"
BASE_URL=https://api.example.com
# Multi-line values (double quotes only)
RSA_KEY="-----BEGIN RSA KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA KEY-----"
# export prefix is stripped (compatible with shell source)
export SECRET_KEY=mysecretkey
# Empty values
EMPTY_VAR=
ALSO_EMPTY=''多文件和合并顺序
你可以指定多个文件。后面的文件会覆盖前面文件中的相同键:
# .env.defaults
DB_HOST=localhost
DB_PORT=5432
LOG_LEVEL=info
# .env.overrides
DB_HOST=production-db # Overrides .env.defaults
LOG_LEVEL=error # Overrides .env.defaults
# docker-compose.yml
services:
api:
image: node:20-alpine
env_file:
- .env.defaults # Loaded first
- .env.overrides # Loaded second, overrides duplicates
# Final result:
# DB_HOST=production-db (from .env.overrides)
# DB_PORT=5432 (from .env.defaults, not overridden)
# LOG_LEVEL=error (from .env.overrides)路径解析
env_file 中的文件路径相对于 docker-compose.yml 文件的位置,而非当前工作目录。这是一个常见的混淆源:
# Project structure:
my-project/
docker-compose.yml # Compose file lives here
.env.api # Same directory — OK
config/
.env.db # Subdirectory — relative to compose file
../shared/
.env.common # Parent directory — also relative
# docker-compose.yml
services:
api:
env_file:
- .env.api # ./my-project/.env.api
- config/.env.db # ./my-project/config/.env.db
- ../shared/.env.common # ./shared/.env.common
# WRONG: Paths are NOT relative to where you run docker compose from!
# If you run "docker compose up" from a different directory,
# paths are still relative to the docker-compose.yml location.可选 env_file(Compose v2.24+)
默认情况下,缺失的 env_file 会导致错误。使用 required 字段使其可选:
# docker-compose.yml
services:
api:
image: node:20-alpine
env_file:
- .env.shared # Required — error if missing
- path: .env.local
required: false # Optional — no error if missing
- path: .env.secrets
required: false # Optional — useful for dev
# Use case: .env.local exists only on developer machines
# for personal overrides, never committed to git..env 文件(根级):Docker Compose 自动加载
Docker Compose 自动读取与 docker-compose.yml 同目录(或项目目录)下名为 .env 的文件。这个文件有一个经常被误解的特殊用途。
.env 实际做什么
- YES: .env 文件为 docker-compose.yml 内部的 ${VARIABLE} 替换提供值。
- NO: 它不会自动将变量注入容器。
- TIP: 要将 .env 的值传入容器,你必须同时使用 environment 或 env_file。
.env 如何工作(以及不如何工作)
# .env (root level, auto-loaded)
TAG=3.2.1
EXTERNAL_PORT=8080
REPLICAS=3
DB_PASSWORD=supersecret
# docker-compose.yml
services:
api:
image: myapp:${TAG} # WORKS: Interpolated to myapp:3.2.1
ports:
- "${EXTERNAL_PORT}:3000" # WORKS: Interpolated to 8080:3000
deploy:
replicas: ${REPLICAS} # WORKS: Interpolated to 3
environment:
- DB_PASSWORD # DOES NOT WORK the way you think!
# This passes the HOST $DB_PASSWORD, not the .env value.
# If host has no $DB_PASSWORD, the container gets an empty string.
# To pass .env values into the container, do one of these:
- DB_PASSWORD=${DB_PASSWORD} # Option 1: Explicit interpolation
# OR:
# env_file:
# - .env # Option 2: Load entire .env file使用 --env-file 覆盖
你可以让 Compose 指向不同的插值文件:
# Default: reads .env from project directory
docker compose up
# Use a different file for Compose interpolation:
docker compose --env-file .env.staging up
# Multiple env files (Compose v2.24+):
docker compose --env-file .env --env-file .env.local up
# This affects ONLY ${} interpolation in docker-compose.yml.
# It does NOT affect env_file directives or container variables.对比表:env_file vs environment vs .env vs ARG
以下是四种方法的并排对比:
| 特性 | environment | env_file | .env(根级) | ARG(构建) |
|---|---|---|---|---|
| 用途 | 直接设置容器环境变量 | 从文件加载环境变量 | Compose 文件插值 | 构建时变量 |
| 作用域 | 运行时(容器) | 运行时(容器) | 仅 Compose 文件 | 仅构建时(除非传递给 ENV) |
| 在 docker-compose.yml 中? | 是 | 否 | 否 | 是 |
| 可被 gitignore? | 否 | 是 | 是 | 否 |
| 适合存密钥? | 否 | 部分 | 否 | 否 |
| 优先级 | 最高 | 中 | 最低(仅插值) | 仅构建时 |
| 支持 ${} 语法? | 仅在值中 | 文件内部不支持 | 不适用 | 不适用 |
变量替换:${VAR:-default}、${VAR:?error}
Docker Compose 支持 shell 风格的变量替换,具有强大的默认值和错误处理语法。这些在 docker-compose.yml 的值中使用 .env 或主机 shell 的变量。
| 语法 | 结果 | 示例 |
|---|---|---|
| ${VAR} | VAR 的值,未设置则为空字符串 | image: myapp:${TAG} |
| ${VAR:-default} | VAR 的值,未设置/为空则使用 "default" | image: myapp:${TAG:-latest} |
| ${VAR-default} | VAR 的值,未设置则使用 "default"(允许空值) | LOG_LEVEL: ${LOG:-} |
| ${VAR:?error msg} | VAR 的值,未设置/为空则退出并报错 | DB_HOST: ${DB_HOST:?Must set DB_HOST} |
| ${VAR?error msg} | VAR 的值,未设置则退出并报错(允许空值) | API_KEY: ${API_KEY?Missing API_KEY} |
| ${VAR:+replacement} | 如果 VAR 已设置且非空则为 "replacement",否则为空 | DEBUG: ${DEBUG:+true} |
带默认值和验证的实际示例
# .env
TAG=3.2.1
# DB_HOST is intentionally NOT set
# docker-compose.yml
services:
api:
image: myapp:${TAG:-latest} # -> myapp:3.2.1
environment:
DB_HOST: ${DB_HOST:?Error: DB_HOST is required} # -> exits with error!
DB_PORT: ${DB_PORT:-5432} # -> 5432 (default)
LOG_LEVEL: ${LOG_LEVEL:-info} # -> info (default)
DEBUG: ${DEBUG:+true} # -> empty (DEBUG not set)
REPLICAS: ${REPLICAS:-1} # -> 1 (default)
db:
image: postgres:${PG_VERSION:-16} # -> postgres:16 (default)
environment:
POSTGRES_DB: ${DB_NAME:-myapp} # -> myapp (default)
POSTGRES_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD} # -> exits with error!优先级顺序:environment > env_file > .env > Dockerfile ENV
当同一变量在多个位置定义时,Docker Compose 应用严格的优先级顺序。理解这一点可以避免数小时的调试。
从最高到最低优先级:
- 1. docker compose run -e KEY=VALUE(CLI 覆盖)
- 2. 主机 shell 环境变量
- 3. docker-compose.yml 中的 environment 指令
- 4. env_file 指令(后面的文件覆盖前面的文件)
- 5. .env 文件(仅用于 Compose 插值)
- 6. Dockerfile ENV 指令
优先级演示
# Dockerfile
ENV DB_HOST=dockerfile-db # Priority 6 (lowest)
# .env (root)
DB_HOST=dotenv-db # Priority 5 (Compose interpolation only)
# .env.file (loaded via env_file)
DB_HOST=envfile-db # Priority 4
# docker-compose.yml
services:
api:
build: .
env_file:
- .env.file # DB_HOST=envfile-db
environment:
DB_HOST: compose-db # Priority 3 — WINS over env_file
# On host shell:
export DB_HOST=shell-db # Priority 2 — WINS over environment
# CLI override:
docker compose run -e DB_HOST=cli-db api # Priority 1 — WINS over everything
# Result with all defined:
# docker compose run -e -> DB_HOST=cli-db
# Without CLI override -> DB_HOST=shell-db
# Without shell export -> DB_HOST=compose-db
# Without environment: -> DB_HOST=envfile-db
# Without env_file: -> DB_HOST=dockerfile-db实际设置:开发、预发布、生产
这是一个完整的、经过生产测试的多环境设置,使用了上述所有技术。
项目结构
my-project/
docker-compose.yml # Base config (shared across environments)
docker-compose.override.yml # Dev overrides (auto-loaded)
docker-compose.staging.yml # Staging overrides
docker-compose.prod.yml # Production overrides
.env # Dev defaults (Compose interpolation)
.env.staging # Staging defaults
.env.prod # Production defaults
.env.secrets # Secrets (gitignored)
.env.secrets.example # Template for secrets (committed)
.gitignore # Ignores .env.secrets, .env.local, etc.基础 docker-compose.yml
# docker-compose.yml — shared base config
services:
api:
build:
context: .
args:
NODE_VERSION: ${NODE_VERSION:-20}
image: myapp:${TAG:-latest}
ports:
- "${API_PORT:-3000}:3000"
env_file:
- path: .env.secrets
required: false
environment:
NODE_ENV: ${NODE_ENV:-development}
DB_HOST: ${DB_HOST:-postgres}
DB_PORT: ${DB_PORT:-5432}
DB_NAME: ${DB_NAME:-myapp}
REDIS_URL: redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
postgres:
image: postgres:${PG_VERSION:-16}
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${DB_NAME:-myapp}
POSTGRES_USER: ${DB_USER:-myapp}
env_file:
- path: .env.secrets
required: false
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-myapp}"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
volumes:
pgdata:
redisdata:开发环境:docker-compose.override.yml
此文件在开发环境中由 docker compose up 自动加载:
# docker-compose.override.yml — auto-loaded in dev
services:
api:
build:
target: development
volumes:
- .:/app # Hot reload
- /app/node_modules
environment:
NODE_ENV: development
LOG_LEVEL: debug
DEBUG: "true"
ports:
- "9229:9229" # Node.js debugger
postgres:
ports:
- "5432:5432" # Expose DB for local tools
environment:
POSTGRES_PASSWORD: devpassword # OK for dev only!
# .env (dev defaults)
TAG=latest
API_PORT=3000
NODE_ENV=development
DB_HOST=postgres
DB_NAME=myapp_dev预发布环境:docker-compose.staging.yml
# docker-compose.staging.yml
services:
api:
build:
target: production
environment:
NODE_ENV: staging
LOG_LEVEL: info
deploy:
replicas: 2
resources:
limits:
memory: 512M
cpus: "0.5"
postgres:
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
# .env.staging
TAG=3.2.1-rc.1
API_PORT=3000
NODE_ENV=staging
DB_NAME=myapp_staging生产环境:docker-compose.prod.yml
# docker-compose.prod.yml
services:
api:
build:
target: production
restart: unless-stopped
environment:
NODE_ENV: production
LOG_LEVEL: warn
deploy:
replicas: 4
resources:
limits:
memory: 1G
cpus: "1.0"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
postgres:
restart: unless-stopped
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
deploy:
resources:
limits:
memory: 2G
redis:
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
secrets:
db_password:
file: ./secrets/db_password.txt
# .env.prod
TAG=3.2.1
API_PORT=3000
NODE_ENV=production
DB_NAME=myapp_prod各环境命令
# Development (auto-loads docker-compose.override.yml)
docker compose up
# Staging
docker compose -f docker-compose.yml -f docker-compose.staging.yml \
--env-file .env.staging up -d
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml \
--env-file .env.prod up -d
# Verify resolved config before deploying:
docker compose -f docker-compose.yml -f docker-compose.prod.yml \
--env-file .env.prod config安全最佳实践
环境变量对敏感数据本质上不安全。遵循以下实践来最小化风险。
1. 始终 .gitignore 你的 .env 文件
# .gitignore
.env.secrets
.env.local
.env.*.local
secrets/
*.pem
*.key2. 提交模板,而非真实文件
# .env.secrets.example (committed to git)
# Copy this file to .env.secrets and fill in real values
DB_PASSWORD=
API_KEY=
JWT_SECRET=
SMTP_PASSWORD=
AWS_SECRET_ACCESS_KEY=
# .env.secrets (gitignored — never committed)
DB_PASSWORD=RealSuperSecretP@ssw0rd
API_KEY=sk-prod-abc123xyz789
JWT_SECRET=your-256-bit-secret-here
SMTP_PASSWORD=sendgrid-api-key
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCY3. 生产环境使用 Docker Secrets
Docker secrets 将敏感数据以文件形式挂载到 /run/secrets/,而不是作为环境变量暴露:
# docker-compose.yml with Docker secrets
services:
api:
image: myapp:latest
secrets:
- db_password
- api_key
environment:
# App reads secrets from files instead of env vars
DB_PASSWORD_FILE: /run/secrets/db_password
API_KEY_FILE: /run/secrets/api_key
postgres:
image: postgres:16
secrets:
- db_password
environment:
# PostgreSQL natively supports _FILE suffix
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt4. 注意 docker inspect
环境变量通过 docker inspect 可见。任何有 Docker 访问权限的人都能读取:
# Anyone with Docker access can see ALL environment variables:
docker inspect myapp-api-1 --format '{{range .Config.Env}}{{println .}}{{end}}'
# Output includes secrets if passed via environment:
# DB_PASSWORD=RealSuperSecretP@ssw0rd <-- EXPOSED!
# API_KEY=sk-prod-abc123xyz789 <-- EXPOSED!
# Docker secrets are NOT visible via inspect.
# They only exist as files inside the container at /run/secrets/5. 按环境轮换密钥
永远不要在开发、预发布和生产环境使用相同的密码、API 密钥或令牌。为每个环境生成唯一值。
# Generate unique secrets per environment:
# Dev
openssl rand -base64 32 > secrets/dev/db_password.txt
openssl rand -hex 32 > secrets/dev/jwt_secret.txt
# Staging
openssl rand -base64 32 > secrets/staging/db_password.txt
openssl rand -hex 32 > secrets/staging/jwt_secret.txt
# Production
openssl rand -base64 32 > secrets/prod/db_password.txt
openssl rand -hex 32 > secrets/prod/jwt_secret.txt调试:docker compose config、docker inspect
当环境变量未按预期工作时,使用这些系统化的调试步骤。
步骤1:查看解析后的 Compose 文件
docker compose config 显示所有变量解析后的完整 Compose 文件:
# See the fully resolved docker-compose.yml:
docker compose config
# With a specific env file:
docker compose --env-file .env.prod config
# Show only specific service:
docker compose config --services
docker compose config api
# Output shows all variables with their final values,
# so you can verify interpolation worked correctly.步骤2:检查容器环境
查看运行中容器内实际设置的所有环境变量:
# List all env vars in a running container:
docker compose exec api env
# Search for a specific variable:
docker compose exec api env | grep DB_HOST
# Or using printenv:
docker compose exec api printenv DB_HOST步骤3:检查已停止的容器
检查已退出容器的环境变量:
# Inspect a stopped/exited container:
docker inspect myapp-api-1 --format '{{range .Config.Env}}{{println .}}{{end}}'
# Or as JSON:
docker inspect myapp-api-1 --format '{{json .Config.Env}}' | jq .步骤4:验证 .env 文件位置
.env 文件必须在项目目录中(docker-compose.yml 所在位置)。验证方法:
# Check that .env exists in the right directory:
ls -la .env
# Check Compose project directory:
docker compose ls
# Explicitly specify the env file:
docker compose --env-file ./my-custom.env config步骤5:检查覆盖
主机 shell 变量会覆盖 .env 值。检查你的 shell 设置了什么:
# Check host shell for conflicting variables:
echo $DB_HOST
echo $NODE_ENV
printenv | grep -i db_
# Unset a conflicting variable:
unset DB_HOST
# Then re-run:
docker compose config调试命令速查表
| Command | Purpose |
|---|---|
| docker compose config | Show resolved Compose file with all variables |
| docker compose config --format json | Resolved config as JSON (pipe to jq) |
| docker compose exec <svc> env | List env vars inside running container |
| docker compose exec <svc> printenv VAR | Get specific variable value |
| docker inspect <container> --format '{{.Config.Env}}' | Env vars of any container (running or stopped) |
| docker compose --env-file FILE config | Test with a specific .env file |
| docker compose logs <svc> 2>&1 | head | Check for startup errors related to missing vars |
常见问题
Docker Compose 中 env_file 和 .env 有什么区别?
env_file 是一个指令,从外部文件将变量直接加载到容器环境中。.env 文件(根级)由 Docker Compose 自动读取,用于 docker-compose.yml 内部的变量替换,但不会将变量注入容器。它们用途不同:env_file 用于容器配置,.env 用于 Compose 文件模板化。
同一服务可以同时使用 environment 和 env_file 吗?
可以,这是一种常见模式。env_file 的变量先加载,然后 environment 的值覆盖任何重复的键。这让你可以在文件中保留默认值,并在行内覆盖特定值。例如,通过 env_file 在 .env.shared 中设置共享配置,然后在 environment 中为特定服务覆盖 DB_HOST。
为什么我的 .env 变量没有到达容器?
根级 .env 文件仅用于 docker-compose.yml 中的 ${} 插值。它不会自动在容器内设置变量。要将 .env 值传入容器,你必须:(1) 使用 env_file 显式指向 .env 文件,或 (2) 使用 environment 配合 ${VAR} 语法进行插值。这是 Docker Compose 环境变量混淆的第一大来源。
如何在 Docker Compose 中不使用环境变量处理密钥?
使用 Docker secrets。在 docker-compose.yml 中定义指向本地文件的密钥,然后授予服务访问权限。密钥以文件形式挂载在容器内的 /run/secrets/<name>。许多官方镜像支持 _FILE 后缀约定(如 POSTGRES_PASSWORD_FILE=/run/secrets/db_password)。这防止密钥出现在 docker inspect、进程列表或日志中。
env_file 支持像 ${VAR} 这样的变量替换吗?
不支持。与 docker-compose.yml 不同,通过 env_file 加载的文件不支持 ${} 变量替换。每行都被视为字面的 KEY=VALUE 对。如果你需要变量展开,请在 environment 指令中使用 ${VAR} 语法定义变量,这些变量会从 .env 或主机 shell 变量中插值。