Docker 环境变量怎么配置?ENV、ARG 和 Compose 怎么选?
Docker 环境变量到底能配置在哪?
Docker 容器环境变量常见配置方式有四类:写在 Dockerfile 里、运行容器时传入、通过环境变量文件批量传入、在 Docker Compose 中配置。它们看起来都叫“环境变量”,但生效时机、覆盖规则和安全风险并不一样。
一句话先说结论:固定默认值可以放 Dockerfile,环境差异参数用运行时传入,生产敏感信息不要直接当环境变量裸奔。
Dockerfile 里的 ENV 和 ARG 有什么区别?
Dockerfile 里最容易混淆的是 ENV 和 ARG。
ENV:构建后仍然存在
ENV 用来设置镜像默认环境变量,构建出的镜像和启动后的容器里都能看到。
dockerfileFROM node:20-alpine ENV NODE_ENV=production ENV APP_PORT=3000 CMD ["node", "server.js"]
适合放默认运行参数,比如 NODE_ENV、默认端口、默认语言环境。它不适合放密码、Token、数据库连接串,因为这些值可能进入镜像层历史,也可能被 docker inspect 看到。
ARG:只在构建阶段使用
ARG 只在镜像构建时生效,默认不会保留到运行时环境里。
dockerfileFROM node:20-alpine ARG BUILD_VERSION RUN echo "build version: $BUILD_VERSION"
构建时传入:
bashdocker build --build-arg BUILD_VERSION=2026.06.19 -t my-app .
如果需要把 ARG 写入运行时环境,必须显式转成 ENV。这也意味着它会进入最终镜像环境。不要把“ARG 构建时用”误解成“ARG 一定安全”,构建日志、镜像历史和 CI 记录里仍可能留下痕迹。
docker run 怎么传环境变量?
运行容器时最直接的方式是 -e 或 --env。
bashdocker run -e NODE_ENV=production -e APP_PORT=3000 my-app
也可以只写变量名,让 Docker 从当前 Shell 环境里取值:
bashexport API_BASE_URL=https://api.example.com docker run --env API_BASE_URL my-app
如果变量比较多,用 --env-file 更清爽。
bashdocker run --env-file .env.production my-app
.env.production 示例:
envNODE_ENV=production APP_PORT=3000 API_BASE_URL=https://api.example.com
--env-file 不是 Shell 脚本,通常按 KEY=VALUE 读取,不要指望它执行命令、展开复杂表达式。值里如果有空格、引号、换行,最好先确认解析行为。
Docker Compose 里 environment、env_file 和 .env 有什么区别?
Compose 里有三个名字很像的东西:.env、env_file、environment。它们不是一回事。
.env:主要用于 compose 文件变量插值
项目根目录的 .env 常用于替换 compose.yml 里的占位变量。
envIMAGE_TAG=1.2.0 HOST_PORT=8080
yamlservices: web: image: my-app:${IMAGE_TAG} ports: - "${HOST_PORT}:3000"
这里 .env 的作用是让 Compose 在解析配置文件时,把 ${IMAGE_TAG}、${HOST_PORT} 替换掉。它不等于自动把所有变量都塞进容器。
env_file:把文件里的变量传给容器
yamlservices: web: image: my-app:1.2.0 env_file: - .env.production
这会把 .env.production 里的变量注入容器运行环境。
environment:直接在 compose 里声明容器变量
yamlservices: web: image: my-app:1.2.0 environment: NODE_ENV: production APP_PORT: "3000" API_BASE_URL: ${API_BASE_URL}
environment 的好处是配置直观,适合少量关键变量;变量很多时,env_file 更容易维护。
环境变量优先级怎么算?
对单个 docker run 来说,通常可以按这个理解:
docker run -e KEY=value显式传入的值优先级最高;docker run --env-file中的值次之;- 镜像里
Dockerfile ENV的默认值最后兜底。
Compose 的优先级更细一些:environment 里直接声明的值通常会覆盖 env_file 同名变量;env_file 会覆盖镜像 ENV 默认值;.env 主要影响 ${VAR} 插值,本身不自动等于容器环境变量;命令行临时覆盖通常最高。
如果变量来源很多,不要靠猜。可以用下面命令看 Compose 最终解析结果:
bashdocker compose config
再进容器确认实际值:
bashdocker compose exec web env | grep NODE_ENV
敏感信息能不能放环境变量?
能用,但不推荐把它当成安全存储。环境变量很方便,也很容易泄漏:docker inspect 可能看到容器环境变量,应用启动日志可能把配置整体打印出来,CI/CD 日志中也可能出现明文。
所以数据库密码、私钥、访问 Token 这类内容,生产环境更建议使用 Docker Swarm Secrets、Kubernetes Secrets、云厂商密钥管理服务,或者运行平台提供的 Secret 注入能力。
Secrets 不是环境变量,那应用怎么读?
很多运行平台会把 Secret 挂载成文件,而不是直接放进环境变量。Docker Swarm Secrets 常见路径类似:
text/run/secrets/db_password
不少官方镜像支持 _FILE 约定,例如:
envMYSQL_PASSWORD_FILE=/run/secrets/mysql_password POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password
这个约定不是 Docker 强制标准,而是很多镜像和框架采用的习惯。自己写应用时也可以照这个思路做:普通配置走环境变量,敏感值走文件或密钥服务。
一个更接近生产的配置方式
开发环境可以简单一点:
yamlservices: web: build: . ports: - "3000:3000" env_file: - .env.development environment: NODE_ENV: development
生产环境建议把默认值、环境差异和密钥分开:
yamlservices: web: image: my-app:1.2.0 environment: NODE_ENV: production APP_PORT: "3000" DB_HOST: db DB_PASSWORD_FILE: /run/secrets/db_password secrets: - db_password secrets: db_password: external: true
应用启动时读取:
jsimport fs from 'node:fs'; function readSecret(name) { const file = process.env[`${name}_FILE`]; if (file) return fs.readFileSync(file, 'utf8').trim(); return process.env[name]; } const dbPassword = readSecret('DB_PASSWORD');
配置后怎么验证?
查看 Compose 渲染后的配置:
bashdocker compose config
查看容器环境变量:
bashdocker exec <container> env | sort
查看镜像默认环境变量:
bashdocker image inspect my-app --format '{{json .Config.Env}}'
查看容器环境变量时要小心,不要在共享终端、CI 日志或工单截图里暴露敏感值。很多泄漏不是黑客攻破系统,而是排查问题时顺手贴了一段日志。
生产环境建议怎么定规则?
比较稳的做法是把变量按用途分层:镜像默认值放无敏感、跨环境都合理的默认配置;部署环境变量放不同环境会变化的普通配置;Secret 放密码、Token、私钥、证书;CI/CD 参数放构建版本、提交哈希、构建时间这类构建期信息。
还要给变量命名留点规矩。比如统一使用 APP_、DB_、REDIS_ 前缀;布尔值固定用 true/false;端口统一写字符串,避免 YAML 把值解析成奇怪的类型。
最后,给应用启动加一层配置校验。缺了关键变量就直接失败,不要带着默认空值跑起来。Docker 环境变量配置没有唯一答案,关键是边界清楚:默认配置归默认配置,环境差异归部署系统,敏感信息归 Secrets。