Docker 从 0 到 1 再到 Kubernetes 实战:第13篇 Compose 环境变量与配置管理
2026/5/31 23:34:22 网站建设 项目流程

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。

在第 12 篇中,我们把 Compose 文件的三大核心模块——services、networks、volumes——拆解得明明白白。但你有没有注意到一个问题:我们的docker-compose.yml里还残留着硬编码?

比如FLASK_ENV=production直接写死在 YAML 里,REDIS_HOST=cache也是写死的。如果换一台机器、换一个环境,这些值需要改动怎么办?难道每次都要改 Compose 文件然后小心别提交到 Git?敏感信息比如数据库密码、API 密钥又该怎么处理,才能既保证安全又不影响团队协作?

这篇就来解决这些问题。读完你会发现,Docker Compose 提供了一套完整的环境变量注入机制——从简单的环境变量设置,到.env文件加载,再到多配置文件的组合覆盖。这套机制的核心思路,在 Kubernetes 的 ConfigMap 和 Secret 中得到了更彻底的实现。

一、为什么需要配置管理?

任何需要部署到多个环境的应用程序,都绕不开配置管理的三个核心挑战:

  • 环境差异性:开发环境用本地 Redis,生产环境用 Redis 集群。如果配置写死在代码或镜像里,换个环境就要重新构建。

  • 安全性:数据库密码、API 密钥、证书——这些东西绝对不能提交到 Git 仓库。

  • 团队协作:每个开发者可能需要不同的本地配置,但不能相互干扰。

Docker Compose 的设计原则是:将应用的行为定义(YAML)与环境的差异配置(变量)分离。镜像和 Compose 文件可以提交到 Git,而敏感的或与环境相关的配置通过环境变量在运行时注入。这个原则,在 Kubernetes 中体现为 ConfigMap(非敏感配置)和 Secret(敏感配置)的分离,以及 Deployment 中通过envFrom注入的方式。

二、Compose 环境变量的四种来源

Docker Compose 支持多种方式向容器传递环境变量,它们有不同的优先级。理解优先级至关重要,否则可能遇到“明明设置了变量,容器里读到的却是旧值”的困惑。

2.1 Compose 文件中的 environment

最直接的方式,在docker-compose.ymlenvironment下直接写:

services: flask-app: environment: -FLASK_ENV=production -REDIS_HOST=cache -LOG_LEVEL=info

或者用字典格式(等号写法):

services: flask-app: environment: FLASK_ENV: production REDIS_HOST: cache LOG_LEVEL: info

这种方式适合那些可以公开、不敏感的配置项,比如日志级别、运行模式。但如果把数据库密码写在这里,任何能访问代码仓库的人都能看到——这就是硬编码敏感信息的典型反模式。

2.2 env_file:从文件加载

services: flask-app: env_file: - .env - .env.local# 可以指定多个文件,后者覆盖前者中同名的变量

.env文件内容(标准 key=value 格式):

FLASK_ENV=developmentREDIS_HOST=localhostREDIS_PORT=6379

env_fileenvironment可以同时使用,environment中的值会覆盖env_file中的同名变量。当同一个变量在多个来源中出现时,优先级从高到低为:命令行-e参数 > Compose 文件environmentenv_file文件 > 镜像默认值。

关键补充——.env 文件的项目级用途

需要注意,.env文件除了作为env_file的来源,还有一个特殊角色:Compose 项目级变量。当.env文件放在docker-compose.yml同目录时,其中的变量会自动被 Compose 用于解析 YAML 文件中的${VAR}插值——但不会自动注入容器。如果想让变量注入容器,必须在env_fileenvironment中显式引用。

安全提醒.env文件如果包含密码、API 密钥等敏感信息,必须加入.gitignore,否则这些机密会被提交到版本控制系统。本系列后续 Kubernetes 部分将介绍如何用 Sealed Secrets 或 External Secrets Operator 实现更安全的 GitOps 密钥管理。

2.3 Shell 环境变量与 .env 项目文件

Compose 在解析docker-compose.yml时,会自动替换${VARIABLE}占位符。这些变量的值可以从两个来源读取:一是 Shell 环境变量,二是.env项目文件。

重要:.env项目文件 vsenv_file的区别

这是 Compose 使用中最容易混淆的概念之一:

  • .env项目文件:放在 Compose 文件同目录,自动被 Compose 读取,用于解析 YAML 中的${VAR}占位符,但不自动注入容器。

  • env_file:在 services 下显式声明,变量直接注入对应容器内部,应用程序可以通过os.environ.get('KEY')读取。

举个例子就清楚了:

# docker-compose.ymlservices: flask-app: image: flask-redis-counter:${TAG:-2.0}# ← ${TAG} 从 .env 项目文件读取environment: -REDIS_HOST=${REDIS_HOST}# ← ${REDIS_HOST} 从 .env 项目文件读取-DB_PASSWORD=${DB_PASSWORD}# ← 也来自 .env 项目文件,但会被 environment 注入容器

对应的.env项目文件:

TAG=3.0REDIS_HOST=redis-prodDB_PASSWORD=secret123

TAG只影响 YAML 的插值,不会被注入容器;REDIS_HOSTDB_PASSWORD既影响 YAML 插值,又通过environment注入容器。而如果你在env_file中指定的文件里有LOG_LEVEL=debug,这个变量会被直接注入容器,但不会用于 YAML 中的${LOG_LEVEL}替换——除非你在.env项目文件中也定义了它。

Shell 环境变量和.env项目文件的优先级

Shell 环境变量会覆盖同名.env项目文件中的值。这个机制非常实用——CI/CD 流水线可以在 Shell 中设置生产环境的变量,自动覆盖.env中的默认值,而无需修改任何文件。

2.4 命令行 -e 参数(最高优先级)

dockercompose run-eFLASK_ENV=staging flask-app

这是临时调试或一次性操作时的利器,优先级最高,会覆盖其他所有来源的同名变量。

三、变量替换语法

Compose 支持多种 Shell 风格的变量替换语法:

实际应用示例:

services: flask-app: image: flask-redis-counter:${TAG:-latest}# TAG 未设置时默认用 latestports: -"${PORT:-5000}:5000"# 端口可动态配置environment: -DB_PASSWORD=${DB_PASSWORD:?数据库密码必须设置}# 强制要求设置,未设置则报错退出

四、多环境配置实战

现在我们为贯穿案例配置三种环境:开发、测试、生产。项目目录结构如下:

flask-redis-counter/ ├── .env.dev# 开发环境├── .env.test# 测试环境├── .env.prod# 生产环境├── docker-compose.yml# 基础配置└── docker-compose.override.yml# 本地覆盖(开发环境)

4.1 各环境的 .env 文件

.env.dev:

TAG=2.0FLASK_ENV=developmentREDIS_HOST=redisLOG_LEVEL=debugPORT=5000

.env.prod:

TAG=2.0FLASK_ENV=productionREDIS_HOST=redis-prod.internalLOG_LEVEL=warnPORT=80

4.2 使用 --env-file 指定环境

# 开发环境dockercompose --env-file .env.dev up-d# 生产环境dockercompose --env-file .env.prod up-d

--env-file参数告诉 Compose 使用指定的.env项目文件来解析${VAR}占位符。这样,同一份docker-compose.yml就能在不同环境中表现出不同的行为。

4.3 优先级验证实验

我们来做一个完整的实验,验证各种变量来源的优先级。这是透彻理解配置管理的关键一步。

创建一个测试用的环境文件.env.test

PRIORITY=from-env-fileENV_FILE_ONLY=only-in-env-file

Compose 文件中定义一个测试服务:

services: test-env: image: alpine container_name: env-test environment: -PRIORITY=from-compose-file -COMPOSE_ONLY=only-in-compose env_file: - .env.test command:sh-c"echo 'PRIORITY='$$PRIORITY; echo 'ENV_FILE_ONLY='$$ENV_FILE_ONLY; echo 'COMPOSE_ONLY='$$COMPOSE_ONLY; echo 'SHELL_VAR='$$SHELL_VAR; sleep 10"

这里用$$而不是$:因为 Compose 在解析 YAML 时会先处理${VAR}占位符,如果你写$PRIORITY,Compose 会在主机 Shell 或.env项目文件中寻找PRIORITY变量进行替换。写成$$PRIORITY是将单个$传递给容器内的 shell,让容器内的 shell 去展开这个变量。

执行测试:

# 先查看 .env.test 内容cat.env.test# 不带任何额外变量dockercompose up test-envdockerlogs env-test# PRIORITY=from-compose-file ← environment 覆盖了 env_file# ENV_FILE_ONLY=only-in-env-file ← 仅存在于 env_file# COMPOSE_ONLY=only-in-compose ← 仅存在于 environment# SHELL_VAR= ← Shell 中未设置,为空dockercompose down# 用 Shell 环境变量覆盖(Linux/macOS)SHELL_VAR=from-shelldockercompose up test-envdockerlogs env-test# ...# SHELL_VAR=from-shell ← Shell 环境变量成功传入!dockercompose down# 用命令行 -e 覆盖(最高优先级)dockercompose run--rm-ePRIORITY=from-cli test-env# PRIORITY=from-cli ← CLI 参数覆盖一切

通过这个实验,你可以清晰地看到:environment会覆盖env_file中的同名变量,而 Shell 变量和 CLI-e参数又能覆盖environment。在排查“变量值不对”的问题时,按这个优先级链条逐一检查即可。

4.4 清理测试容器

dockerrm-fenv-test2>/dev/null||truedockercompose down2>/dev/null||true

五、安全实践:哪些不能写进 Compose 文件?

这是一个容易被忽视但极其重要的主题。以下内容永远不要硬编码在 YAML 中,也不要提交到 Git:

  • 数据库密码

  • API 密钥 / Token

  • 第三方服务凭证(AWS Access Key、SMTP 密码等)

  • 加密证书和私钥

  • 签名密钥

这些值应该通过运行时注入,Compose 中常用的安全注入方式包括:将敏感信息写入.env文件并加入.gitignore;在 CI/CD 流水线中通过环境变量注入;使用 Docker Swarm 的 Secrets(Swarm 模式);在 Kubernetes 中使用 Secret + Sealed Secrets。

.gitignore中添加:

.env .env.*!.env.example# 示例文件可以提交,但不应包含真实凭证

示例模板的最佳实践:建议在项目中提供一个.env.example文件,包含所有必需变量的键名和占位示例值,提交到 Git 供团队成员参考。新成员克隆项目后,只需cp .env.example .env并填入真实值即可启动项目。

六、实战:为 Flask + Redis 重构多环境配置

现在我们把前面学到的知识,落地到贯穿案例的docker-compose.yml中。

6.1 消除硬编码的 Compose 文件

# docker-compose.ymlservices: redis: image: redis:alpine restart: unless-stopped command: redis-server--appendonlyyes--maxmemory256mb volumes: - redis-data:/data networks: - app-net healthcheck: test:["CMD","redis-cli","ping"]interval: 10s timeout: 3s retries:3start_period: 5s flask-app: image: flask-redis-counter:${TAG:-2.0}restart: unless-stopped ports: -"${PORT:-5000}:5000"environment: -FLASK_ENV=${FLASK_ENV:-production}-REDIS_HOST=redis -LOG_LEVEL=${LOG_LEVEL:-info}volumes: - flask-logs:/app/logs networks: - app-net depends_on: redis: condition: service_healthy healthcheck: test:["CMD","curl","-f","http://localhost:5000/health"]interval: 30s timeout: 3s retries:3start_period: 5s volumes: redis-data: flask-logs: networks: app-net: driver: bridge

注意这里REDIS_HOST=redis没有用${}占位——因为服务名redis是由 Compose 网络 DNS 自动解析的,跨环境都是同一个值,没必要参数化。需要参数化的是那些随环境变化的变量。

6.2 环境配置文件

.env.dev(开发环境):

TAG=devFLASK_ENV=developmentLOG_LEVEL=debugPORT=5000

.env.prod(生产环境):

TAG=2.0FLASK_ENV=productionLOG_LEVEL=warnPORT=80

6.3 多环境启动验证

# 开发环境启动dockercompose --env-file .env.dev up-ddockercomposepscurlhttp://localhost:5000# Hello World! I have been seen 1 times.# 查看容器内的环境变量,确认正确注入dockercomposeexecflask-appenv|grepFLASK_ENV# FLASK_ENV=developmentdockercomposeexecflask-appenv|grepLOG_LEVEL# LOG_LEVEL=debug# 切换环境前先清理dockercompose down-v

注意:这里用了-v来删除数据卷。因为开发环境和生产环境的数据应该是隔离的,清理卷能避免数据残留导致的困惑。但在生产环境中操作时,-v会永久删除所有持久化数据,务必确认备份后再执行。

# 生产环境启动dockercompose --env-file .env.prod up-ddockercomposeps

6.4 .env.example 模板

在项目根目录创建.env.example,提供给团队成员:

# ===========================================# Flask + Redis 计数器应用 —— 环境配置模板# 复制此文件为 .env 并填入真实值# ===========================================# 镜像版本TAG=2.0# 运行环境:development / productionFLASK_ENV=development# 日志级别:debug / info / warn / errorLOG_LEVEL=debug# 宿主机端口PORT=5000

七、Compose 配置调试技巧

当变量不生效时,如何快速定位问题?

7.1 查看最终配置

这条命令会输出变量替换后的完整配置。如果某个${VAR}没有被替换,说明变量未设置且没有默认值。这是排查配置问题的第一步,也是最重要的一步。

7.2 查看容器内的实际环境变量

dockercomposeexecflask-appenv

这能看到最终注入到容器内的所有环境变量——environmentenv_file和镜像自带的环境变量都会被列出。

7.3 常见问题排查

问题一:变量没被替换,YAML 中直接输出${TAG}字面量。

原因.env项目文件不存在,或 Shell 环境中也未设置该变量,且没有提供默认值${VAR:-default}。Compose 找不到变量值就会保留原始占位符。解决方案是确保.env文件存在于docker-compose.yml同级目录,或显式指定--env-file路径。

问题二:容器内的变量值不对。

原因:可能是env_fileenvironment同时设置了同名变量,而environment覆盖了env_file;或者是.env项目文件和env_file文件内容不同导致混淆。解决方案是用docker compose config确认最终注入值,并用docker compose exec <service> env查看容器内实际环境变量。

问题三:敏感信息泄露到了镜像层。

原因:在 Dockerfile 中用ENV硬编码了密码。docker history可以查看所有镜像层的 ENV 值,密码就这样暴露了。解决方案是永远不要在 Dockerfile 的ENV中设置敏感信息,而是通过运行时环境变量注入。

八、命令速查表

九、本篇总结

  • 四种环境变量来源environment(直接写 YAML)、env_file(从文件加载)、Shell 环境变量 /.env项目文件(YAML 变量插值)、命令行-e(最高优先级)。

  • 变量替换语法${VAR}${VAR:-default}${VAR:+value}${VAR:?error}——理解这些语法是编写灵活 Compose 文件的基础。

  • 多环境切换:通过--env-file指定不同的.env文件,同一份 YAML 适配开发、测试、生产环境,无需修改 Compose 文件本身。

  • 安全红线:敏感信息永远不硬编码在 YAML 中、不提交到 Git,通过.env文件 +.gitignore管理。提供.env.example模板方便团队协作。

  • 调试三板斧docker compose config(查看替换后的配置)→docker compose exec <service> env(查看容器内变量)→ 核对优先级链(CLI > environment > env_file)。

下一篇文章——第 14 篇:Compose 开发环境最佳实践:热重载与调试,我们将深入开发效率优化,用 Bind Mount 实现代码修改后秒级生效,结合 VS Code 远程调试,让你的本地开发体验从“频繁重建镜像”变成“保存即刷新”。

想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询