.env 插值不等于环境变量
承接 12 篇 Dockerfile 与构建期配置。镜像只固化构建期产物,运行期还需要把环境相关的值注入容器。建立 .env,仅包含本地实验配置,并加入 Git 忽略规则:
APP_ENV=dev
WEB_PORT=18080
BUILD_LABEL=lesson12建立 runtime.env:
APP_ENV=from-env-file在原 compose.yaml 中 替换 web 服务部分,保留 db、networks、volumes。移除 web 的 ./site 挂载,使页面确实来自构建出的镜像:
web:
image: delivery-demo:local
build:
context: .
args:
BUILD_LABEL: ${BUILD_LABEL:-local}
ports:
- "127.0.0.1:${WEB_PORT:-18080}:80"
env_file:
- runtime.env
environment:
APP_ENV: ${APP_ENV:?必须设置 APP_ENV}
networks:
- edge
- backend四个阶段要分别判断:
| 阶段 | 本例机制 | 验证方式 |
|---|---|---|
| 镜像构建 | ARG BUILD_LABEL | inspect 镜像 label |
| Compose 解析 | .env 或 Shell 给 ${APP_ENV} 插值 | docker compose config |
| 容器创建 | environment 覆盖同键 env_file,再覆盖镜像 ENV 默认值 | 容器 printenv APP_ENV |
| 程序启动 | Nginx 入口脚本读取变量并生成配置 | 请求 /environment |
渲染配置可能包含敏感值,不把完整 docker compose config 输出粘贴到公开日志。
三个覆盖实验
docker compose up -d --build web
curl --fail http://127.0.0.1:18080/environment
APP_ENV=staging docker compose up -d --no-build web
curl --fail http://127.0.0.1:18080/environment预期先为 dev,再为 staging。Shell 的值用于这次 Compose 插值,覆盖 .env;第二次没有重新构建镜像,却改变了应用响应。
接着从 Compose web 中移除 environment 整块,再执行 up -d --no-build web,预期为 from-env-file。最后移除 env_file,预期回到镜像默认值 local。只在 .env 写 APP_ENV,不会自动把它传入容器。实验后恢复完整 web 配置。
配置改变后需要按应用机制重新加载;本例用 up -d 重建配置发生变化的容器,单纯 restart 不会重新应用 Compose 环境配置。Nginx 不会持续监听 Shell 变量变化。真实应用还应校验必填项与类型,在启动前报告清楚的配置错误。
启动模式分流:up -d vs up --watch
前几节讲的是「配置按环境变化」——同一份 compose.yaml,通过 .env / env_file / APP_ENV 区分 dev 与 生产,这是配置维度的差异。
还有一类维度是启动模式:同一份 yaml,开发用 docker compose up --watch 触发文件监听,生产用 docker compose up -d 后台启动,行为完全不同。配置上需要在 services.<name> 下加一段:
services:
web:
build: .
develop:
watch:
- action: sync+restart
path: .
target: /code
- action: rebuild
path: requirements.txtdevelop: 是 Compose Spec 的可选段。docker compose up -d 默认完全不解析它,只有显式带 --watch 时才进入文件监听循环——所以同一份 yaml 可以同时承担:
- 本地开发:
up --watch,改 Python 文件自动同步到容器;改requirements.txt触发镜像重建 - 预发 / 生产:
up -d,watch 块被忽略,行为和没写一样;YAML 单一真源
两个边界
path: .在两端语义不同。开发机path: .是开发者本地工作目录;生产部署时同一份 yaml 的path: .指向生产服务器上当前目录。生产没人up --watch,所以没有影响,但读 yaml 时要知道这个path:只对 dev 生效,下它是个休眠配置。sync+restart写入的是容器可写层(UnionFS 的 upperdir),不是镜像层。容器一旦重建这些改动就消失,会让人误以为「已经部署」。同时本地复现事故时,如果加了--watch,行为会和生产不一致,排查路径分叉。
watch 的作用域
要求镜像一旦构建不再变更,生产发布应该是一次性原子动作(镜像 tag 锁定、配置注入、健康检查、流量切换)。watch 把这套流程绕开,改成「边跑边改」,变更窗口从一次原子操作变成持续时间段,回滚点变模糊。
所以 watch 的作用域基本锁在本地开发机,即便在 staging 也最好关掉——staging 是「生产镜像 + 生产配置」的预演,开了 watch 等于让 staging 和 prod 走两条不同的部署路径,预发验证就失效。同一份 yaml 可以同时承载两种启动模式,但启动参数要按场景分清。
这条线以后怎么走
本课回答的是运行期的问题:同一份镜像在不同环境下用不同配置(.env / env_file / environment),dev 与 prod 通过启动参数(up -d vs up --watch)分流。多套 Compose 项目同时跑在同一台机器时怎么不串味、共享怎么处理,见 14 篇 单机 Compose 多项目共存。