Docker 从 0 到 1 再到 Kubernetes 实战:第13篇 Compose 环境变量与配置管理
IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章助你少走弯路。在第 12 篇中我们把 Compose 文件的三大核心模块——services、networks、volumes——拆解得明明白白。但你有没有注意到一个问题我们的docker-compose.yml里还残留着硬编码比如FLASK_ENVproduction直接写死在 YAML 里REDIS_HOSTcache也是写死的。如果换一台机器、换一个环境这些值需要改动怎么办难道每次都要改 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.yml的environment下直接写services: flask-app: environment: -FLASK_ENVproduction -REDIS_HOSTcache -LOG_LEVELinfo或者用字典格式等号写法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文件内容标准 keyvalue 格式FLASK_ENVdevelopmentREDIS_HOSTlocalhostREDIS_PORT6379env_file和environment可以同时使用environment中的值会覆盖env_file中的同名变量。当同一个变量在多个来源中出现时优先级从高到低为命令行-e参数 Compose 文件environmentenv_file文件 镜像默认值。关键补充——.env 文件的项目级用途需要注意.env文件除了作为env_file的来源还有一个特殊角色Compose 项目级变量。当.env文件放在docker-compose.yml同目录时其中的变量会自动被 Compose 用于解析 YAML 文件中的${VAR}插值——但不会自动注入容器。如果想让变量注入容器必须在env_file或environment中显式引用。安全提醒.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项目文件TAG3.0REDIS_HOSTredis-prodDB_PASSWORDsecret123TAG只影响 YAML 的插值不会被注入容器REDIS_HOST和DB_PASSWORD既影响 YAML 插值又通过environment注入容器。而如果你在env_file中指定的文件里有LOG_LEVELdebug这个变量会被直接注入容器但不会用于 YAML 中的${LOG_LEVEL}替换——除非你在.env项目文件中也定义了它。Shell 环境变量和.env项目文件的优先级Shell 环境变量会覆盖同名.env项目文件中的值。这个机制非常实用——CI/CD 流水线可以在 Shell 中设置生产环境的变量自动覆盖.env中的默认值而无需修改任何文件。2.4 命令行 -e 参数最高优先级dockercompose run-eFLASK_ENVstaging 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.devTAG2.0FLASK_ENVdevelopmentREDIS_HOSTredisLOG_LEVELdebugPORT5000.env.prodTAG2.0FLASK_ENVproductionREDIS_HOSTredis-prod.internalLOG_LEVELwarnPORT804.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.testPRIORITYfrom-env-fileENV_FILE_ONLYonly-in-env-fileCompose 文件中定义一个测试服务services: test-env: image: alpine container_name: env-test environment: -PRIORITYfrom-compose-file -COMPOSE_ONLYonly-in-compose env_file: - .env.test command:sh-cecho PRIORITY$$PRIORITY; echo ENV_FILE_ONLY$$ENV_FILE_ONLY; echo COMPOSE_ONLY$$COMPOSE_ONLY; echo SHELL_VAR$$SHELL_VAR; sleep 10这里用$$而不是$因为 Compose 在解析 YAML 时会先处理${VAR}占位符如果你写$PRIORITYCompose 会在主机 Shell 或.env项目文件中寻找PRIORITY变量进行替换。写成$$PRIORITY是将单个$传递给容器内的 shell让容器内的 shell 去展开这个变量。执行测试# 先查看 .env.test 内容cat.env.test# 不带任何额外变量dockercompose up test-envdockerlogs env-test# PRIORITYfrom-compose-file ← environment 覆盖了 env_file# ENV_FILE_ONLYonly-in-env-file ← 仅存在于 env_file# COMPOSE_ONLYonly-in-compose ← 仅存在于 environment# SHELL_VAR ← Shell 中未设置为空dockercompose down# 用 Shell 环境变量覆盖Linux/macOSSHELL_VARfrom-shelldockercompose up test-envdockerlogs env-test# ...# SHELL_VARfrom-shell ← Shell 环境变量成功传入dockercompose down# 用命令行 -e 覆盖最高优先级dockercompose run--rm-ePRIORITYfrom-cli test-env# PRIORITYfrom-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 的 SecretsSwarm 模式在 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}:5000environment: -FLASK_ENV${FLASK_ENV:-production}-REDIS_HOSTredis -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_HOSTredis没有用${}占位——因为服务名redis是由 Compose 网络 DNS 自动解析的跨环境都是同一个值没必要参数化。需要参数化的是那些随环境变化的变量。6.2 环境配置文件.env.dev开发环境TAGdevFLASK_ENVdevelopmentLOG_LEVELdebugPORT5000.env.prod生产环境TAG2.0FLASK_ENVproductionLOG_LEVELwarnPORT806.3 多环境启动验证# 开发环境启动dockercompose --env-file .env.dev up-ddockercomposepscurlhttp://localhost:5000# Hello World! I have been seen 1 times.# 查看容器内的环境变量确认正确注入dockercomposeexecflask-appenv|grepFLASK_ENV# FLASK_ENVdevelopmentdockercomposeexecflask-appenv|grepLOG_LEVEL# LOG_LEVELdebug# 切换环境前先清理dockercompose down-v注意这里用了-v来删除数据卷。因为开发环境和生产环境的数据应该是隔离的清理卷能避免数据残留导致的困惑。但在生产环境中操作时-v会永久删除所有持久化数据务必确认备份后再执行。# 生产环境启动dockercompose --env-file .env.prod up-ddockercomposeps6.4 .env.example 模板在项目根目录创建.env.example提供给团队成员# # Flask Redis 计数器应用 —— 环境配置模板# 复制此文件为 .env 并填入真实值# # 镜像版本TAG2.0# 运行环境development / productionFLASK_ENVdevelopment# 日志级别debug / info / warn / errorLOG_LEVELdebug# 宿主机端口PORT5000七、Compose 配置调试技巧当变量不生效时如何快速定位问题7.1 查看最终配置这条命令会输出变量替换后的完整配置。如果某个${VAR}没有被替换说明变量未设置且没有默认值。这是排查配置问题的第一步也是最重要的一步。7.2 查看容器内的实际环境变量dockercomposeexecflask-appenv这能看到最终注入到容器内的所有环境变量——environment、env_file和镜像自带的环境变量都会被列出。7.3 常见问题排查问题一变量没被替换YAML 中直接输出${TAG}字面量。原因.env项目文件不存在或 Shell 环境中也未设置该变量且没有提供默认值${VAR:-default}。Compose 找不到变量值就会保留原始占位符。解决方案是确保.env文件存在于docker-compose.yml同级目录或显式指定--env-file路径。问题二容器内的变量值不对。原因可能是env_file和environment同时设置了同名变量而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 思维