第 9 章拆解(上)— 容器化与 Docker:从 Dockerfile 到 Compose
容器化与 Docker:从 Dockerfile 到 Compose
这一章(和 13 一起)把前面八章造出来的应用装进集装箱。 上篇讲 Docker 本体: 为什么需要它、镜像和容器到底差在哪、两个真实的多阶段 Dockerfile 逐行讲、 命令怎么敲、CI 怎么自动推镜像;然后用 Docker Compose 把数据库、Redis、 MailHog、前后端七个服务一键拉起。主线例子:一条
docker compose up, 从空机器到整个工单系统在跑。
1. 顶层全景
为什么容器:虚拟机太重(内核跑两遍)→ Linux namespaces 隔离、宿主内核复用
↓
五个概念:镜像(蓝图)/ 容器(实例)/ 层(不可变、可缓存)/ 构建参数 / 环境变量
↓
两个 Dockerfile:后端多阶段(rust:nightly 构建 → debian-slim 运行)
前端多阶段(Wasm 读不到 env → lighttpd 反代 /backend)
↓
docker build → docker run(端口映射、--rm、--env-file)
↓
CI:GitHub Actions 登录 Docker Hub、Buildx、build-push
↓
Docker Compose:services / desired state / health checks / volumes
↓
编排(orchestration)定义 + YAGNI:别一上来就 Kubernetes
图说:单机容器 → 多容器编排 → 集群编排,三级火箭逐级点。2. 历史背景:从「在我机器上能跑」说起
先纠正一个概念:服务器不是一台机器,是一个角色——硬件或软件扮演的角色。 最早这个角色贴着裸金属:部署 = 往专用机器上拷文件;机器搬进机房,拷变成传, 本质没变1。
麻烦随之而来:为服务器优化的机器和日常电脑差异越来越大,环境冲突不断—— 那句著名的「works on my machine(在我机器上能跑)」就此诞生, 这不是借口,是参与各方的真实灾难2。
第一个解法是把应用连配置打包成轻量虚拟机(VM):能跑就行,但代价高—— 虚拟化要模拟 CPU、内存,还要再养一个完整操作系统。 「只要它在一处能跑,就必须到处能跑」的需求,不该用这么贵的方案买3。
3. Docker 的原理:namespaces 与层的复用
虚拟化的冗余在哪: 硬件之上是固件和操作系统;hypervisor(KVM/VMware/Hyper-V) 再虚拟一套硬件,上面再装一个操作系统——如果宿主和客户机是同一种系统, 内核等关键部件就在同一硬件上跑了两遍。能不能既复用宿主的系统部件、又不牺牲隔离? Linux 内核的 namespaces 正是答案:提供隔离性与复用性4。
但 namespaces 本来就难用得只有专家敢碰——Docker 的贡献是把 namespaces 包成人人会用的简单接口5。这就引出容器化的定义:
容器化 = 把应用包成标准化、可部署、可管理的统一格式;虚拟化细节被藏起来, 对环境来说一切如常;操作系统的重复部分被复用,而存储、进程、内存各自隔离。6
Docker 的五个核心概念(书里按出场顺序):7
| 概念 | 一句话 |
|---|---|
| 镜像(image) | 环境的蓝图 + 全部材料;不可变;由层构成;从 registry(如 Docker Hub)拉 |
| 容器(container) | 按蓝图装配出来的运行实例;为单一目的调好;用完即弃 |
| 层(layers) | 每次改动/加装 = 新一层,底层镜像不动;层数越少越好(合并命令、多阶段构建) |
| 构建参数(ARG) | 构建期变量;依赖一变只需改一处;多阶段里每段要重新定义 |
| 环境变量(ENV/.env) | 运行期配置;构建和运行都能传;.env 文件可以成批带 |
镜像与容器的关系,书里一句定音:「镜像是设计,容器是按设计的实现」—— 只能基于镜像建容器;镜像不可变以保值,容器易造以值得丢弃。8
Dockerfile 就是把上面全部概念写在项目根的一个文本文件里; 镜像构建好后,随时能实例化容器9。
4. 后端 Dockerfile:多阶段构建逐行讲
ARG BASE_IMAGE=rustlang/rust:nightly # 1 将来要换基础镜像,只改这一行
FROM ${BASE_IMAGE} AS base # 2 拉 nightly 镜像,命名 base 供后面引用
WORKDIR /src # 3 之后命令的工作目录
COPY backend ./backend/ # 4-11 五个 workspace 成员 + 根 src/tests
COPY entity ./entity/ # + Cargo.lock/Cargo.toml 全部拷进来
COPY extra_migrations ./extra_migrations/
COPY migration ./migration/
COPY shared ./shared/
COPY src ./src/
COPY tests ./tests/
COPY Cargo.lock Cargo.toml ./
FROM base AS build-server # 13 新阶段基于 base(构建失败不用重拷源码)
RUN cargo build --release # 14 生产构建:优化、无调试符号
FROM debian:bookworm-slim as prod # 16 运行镜像:瘦得只剩二进制
RUN apt-get update && apt install -y openssl # 17 装 OpenSSL;命令合并成一层
COPY --from=build-server /src/target/release/ticketing /bin/ # 18 只把二进制搬过来
ENTRYPOINT [ "/bin/ticketing" ] # 19 启动命令逐行要点:ARG 是好习惯(会变的东西前置);FROM 之后的几乎每一行都是一个新层——
所以要多阶段构建把「只用来编译的东西」全部扔掉10;apt-get update && install
合并成一条是为了少造层;最终产物从 debian:bookworm-slim 起跑,
容器里只有应用二进制,别无他物11。
5. 前端 Dockerfile:Wasm 的硬约束与反代解法
前端镜像前半段同款:装构建依赖(build-essential、cmake、clang…)、
装 stable 工具链 + wasm32 目标 + trunk、trunk build --release。
变招在最后:运行镜像用 jitesoft/lighttpd——因为「前端应用只是一堆静态文件,
自己不会服务自己;lighttpd 替它服务,还替它把 /backend 指到真后端」12。
为什么要反代?这是 Wasm 的硬约束: Wasm 跑在沙箱里,读不到环境文件——
后端地址没法运行期注入,只能构建期硬编码。硬编码了又想留改动的余地,怎么办?
假定后端永远在本机的 /backend 下(哪怕它其实在别处),由代理兑现这个假定。
lighttpd 配置文件里两行是关键:
server.error-handler-404 = "/index.html" ← 所有未知路径都交给 Yew 应用自己处理
$HTTP["url"] =~ "^/backend" { … proxy… } ← /backend 开头的请求转发给真后端
proxy.header 里把 URL 里的 "/backend" 前缀剥掉再转发
图说:前端请求 <服务器>:<端口>/backend/projects/1
→ lighttpd 改写成 <BACKEND_HOST>:<BACKEND_PORT>/projects/1
404 兜底同理:不配置的话,lighttpd 会把路径当磁盘文件找,Yew 路由直接报废。买到的自由:BACKEND_HOST/BACKEND_PORT 是 lighttpd 的环境变量,部署期随便改, Wasm 二进制一行不用重编13。
6. 命令、CI 与 Compose
构建与运行(命令逐个讲):14
docker build -t <用户名>/ticketing-backend . # -t 起名字;句点是 Dockerfile 所在目录
docker build -f Dockerfile.frontend -t <用户名>/ticketing-frontend . # -f 指定另一个 Dockerfile
docker run -d --rm -p 8000:8000 --name ticketing-backend \
--env-file .docker.env <镜像> env
# -d 后台跑;--rm 停了即删(容器用完即弃,不靠手工养);
# -p 宿主:容器 端口映射;--name 起名;--env-file 整文件传环境变量;-e 单个传CI 自动化(GitHub Actions): push 到 master/testing 触发,两个 job 各四步:
checkout → 登录 Docker Hub(账号密码走 secrets,红字:敏感数据永不硬编码)
→ Setup Buildx → build-push(指定 context、Dockerfile、push: true、tag …:latest)。
README 照例挂徽章15。
Docker Compose:多容器的指挥官。 单独容器不够用——应用还依赖数据库等外部组件; Compose 能把整套环境(含依赖)定义成一份文件、一键起齐16。
四个概念:
| 概念 | 是什么 | 文件里的痕迹 |
|---|---|---|
| Services(服务) | 可独立启动/重启/伸缩的计算资源 | 七个服务:MariaDB、Adminer(可选)、PostgreSQL(给 fang)、Redis、MailHog、backend、frontend |
| Desired state(期望状态) | 你描述「想看到它运行的样子」,偏离了就拉回来 | restart: always |
| Health checks(健康检查) | Compose 靠应用自报判断健康;怎么报自定 | Postgres 的 pg_isready,每秒一次、超时 5 秒、重试 10 次 |
| Volumes(卷) | 独立于容器生命周期的持久化抽象;容器死了数据还在 | MariaDB/Postgres 的 bind 卷(库文件留在宿主) |
七个服务一张网: depends_on 排依赖序(后端依赖四个基建,前端依赖后端);
前端服务的环境变量 BACKEND_HOST=ticketing-backend——注意用的是服务名,
Compose 内建 DNS 让容器互相按名字找到17。
编排(orchestration)在此定义: 「通过自动化生命周期管理、健康检查和各组件的伸缩,来管理容器化应用环境」—— 靠的正是期望状态这个思想18。
判断(我们的,不是书里的): 章末那条 YAGNI 忠告是全书最值钱的运维观: 「大多数应用单个容器就跑得很好;先单容器,观测到真需求再上 Compose, 再上 Swarm/K8s——K8s 一开始大概率是过度设计,还会偷走更要紧任务的开发资源。」 如果错,会错在: 如果组织已有 K8s 平台团队、或合规强制要求容器编排标准, 「从小做起」反而要多付一次迁移税。判断依据是:有没有现成的平台能力替你付这笔税。
7. 主线例子走一遍:一条命令起全家桶
git clone → cp .env 样例 → docker compose up
│ ① Compose 按 depends_on 起七个服务;MariaDB/Postgres 的 bind 卷找到旧数据
▼
postgres 健康检查 pg_isready 通过,backend 才开始连得上调度库
│ ② 后端容器:Rust 二进制跑在 debian-slim 里,8000 端口映射到宿主
▼
前端容器:lighttpd 在 8080 服务静态文件;/backend 反代到 ticketing-backend:8000
│ ③ 浏览器开 localhost:8080 —— 整套工单系统活在本机,一条命令
▼
改代码 → git push → CI 构建推 Docker Hub → 服务器 docker pull && up -d
——从开发机到生产的全链路,没有一步手工装环境。
图说:Compose 文件就是「整套环境的图纸」;CI 让图纸的产物自动更新。
(服务名、端口出自原书 compose 文件。)8. 作者的判断与证据(分开标)
有出处的: namespaces 原理、五个概念定义、多阶段构建的理由、 Wasm 读不到 env 的约束、Compose 四概念,均为原书原文47101216。
作者自己的评估:
- 前端用 lighttpd 反代是作者选的「工作流解」,Nginx/Caddy 同样可行(书里未比较);
- 「K8s 大概率是过度设计」的 YAGNI 立场(见判断块);
- 教学镜像
rustlang/rust:nightly偏大;生产可用 cargo-chef 做依赖层缓存——书里未提。
9. 边界与局限
- 没有讲镜像扫描与安全: 第 3 章的 cargo-deny 管依赖,容器层的漏洞扫描(trivy 等)没接;
- compose 只到单机多容器: 多机部署留给 Swarm/K8s(见 13 篇);
- 密钥管理仍靠 .env 文件:生产应上 secret manager,书里只在 object_store 那章点过;
- 健康检查只有 Postgres 一个示例,应用自身的 /health 端点被建议但未实现。
10. 可带走的
主线例子一行走完: docker compose up → 七服务按依赖启动 →
健康检查放行 → 前端 8080/lighttpd → /backend 反代后端 8000 → push 触发 CI 推镜像。
- 容器化的动机一句话: 虚拟机把内核跑两遍太贵,namespaces 隔离 + 宿主复用是正解。
- 镜像不可变、容器用完即弃;层数越少镜像越小。
- 多阶段构建 = 编译的垃圾不进产房:构建镜像与运行镜像分开,只搬二进制。
- Wasm 应用拿不到环境变量——部署期可变配置用「反代 + 路径前缀」兑现。
- SPA 托管必须配 404 → index.html,否则深链接全灭。
- docker run 的常识:
--rm(容器不靠手工养)、-p 宿主:容器、-e/--env-file。 - CI 推镜像四步: checkout → 登录(凭据走 secrets)→ Buildx → build-push。
- Compose 四概念:服务、期望状态、健康检查、卷;服务名就是内网 DNS 名。
- 编排 = 生命周期 + 健康 + 伸缩的自动化,本质还是「期望状态」。
- YAGNI 运维版: 单容器 → Compose → Swarm → K8s,逐级升级,别跳级。
5.5 Compose 文件逐服务解剖(七个服务各管什么)
原书的 compose 文件是整章的收束——七个服务就是过去八章基建的「全家福」。 逐个看它们各自承担什么、配置里的每个键为什么存在:19
MariaDB(主库): image: mariadb(官方镜像)+ command: --init-file /data/init.sql
(启动时执行初始化 SQL——bind 卷把 extra_migrations/mariadb 挂到容器 /data,
建库建账号一步到位)+ restart: always(期望状态:永远活着)+
env_file: ./.docker-compose.env(密码等配置整个文件注入,不进版本库)+
两个卷:一个匿名卷(/var/lib/mysql)存数据本体,一个 bind 卷送初始化脚本 +
ports: 3306:3306 映射到宿主(开发期用数据库客户端直连方便)20。
Adminer(可选): 数据库网页客户端,8088 映射出来—— 「观察应用数据状态的便利工具,没有它系统照样转」21。
PostgreSQL(给 fang 的调度库): 第 8 章埋的伏笔在这里兑现:
fang 绑死 PostgreSQL,所以全家桶里单开一台 postgres:14-alpine。
两个细节值得抄:健康检查 test: ["CMD-SHELL", "pg_isready"]、
interval: 1s、timeout: 5s、retries: 10——每秒探一次、5 秒算超时、
失败 10 次才判死重启;初始化脚本 bind 到官方约定的
/docker-entrypoint-initdb.d(镜像首次启动自动执行该目录 SQL)。
另有 POSTGRES_HOST_AUTH_METHOD=password、PGDATA 指定数据目录——
这个服务的配置密度是七个里最高的,因为它最不「即插即用」22。
Redis: 最省事的一个:image: redis:7-alpine + 一行 command
(redis-server --save 60 1 --loglevel warning——每 60 秒有一次写就快照持久化,
告警级日志)。键值库没有任何初始化负担,这就是第 8 章选它当队列的原因23。
MailHog: ports 把 1025/8025 都映射出来——SMTP 端口给应用连,
网页端口给人看信;restart: always24。
ticketing-backend: 唯一用我们自己镜像的服务(image: viktordaroczi/ticketing)。
depends_on 列四个基建(mariadb、postgres、redis、mailhog)——
Compose 按依赖顺序启动,但注意:depends_on 只保证启动顺序,不保证服务就绪
(Postgres 那套健康检查就是为此存在的;后端自己没有 readiness 检查,
靠 restart 兜底,这是文件里一个可以再精进的地方)。hostname: ticketing-backend
让前端能用这个名字找到它25。
ticketing-frontend: 依赖后端;environment 里两行把
BACKEND_HOST=ticketing-backend、BACKEND_PORT=8000 显式注入——
这就是第 5 节反代解法的运行期一半:lighttpd 拿这两个值去连后端;
8080 映射出来是对外的唯一门面26。
判断(我们的,不是书里的): 这个 compose 文件的教学价值不在「能跑」, 而在它把前八章每个技术决策都显式化了:MariaDB 是主库、PostgreSQL 是调度库、 Redis 是队列、MailHog 是邮件——每一句
depends_on都是一张架构图的边。 读别人的 compose 文件是摸一个系统架构的最快办法,写自己的 compose 文件是 检验架构清不清楚的最狠办法。 如果错,会错在: 把 compose 当「启动脚本」看的人会嫌它啰嗦; 但当它失宠那天,往往是因为系统长到了需要真正的编排(下一篇)。
6.5 构建进阶:三个容易忽略的细节
.dockerignore(书里散落的规矩,汇总在这里):
镜像构建的 COPY 会把构建上下文整个发给 Docker 守护进程——
target/(几 GB 的编译产物)、.git/、.env 全都会被拷进去,
又慢又泄密。.dockerignore 就是给 COPY 看的黑名单,
内容大致是 .git、target、.env、frontend/dist、frontend/node_modules。
「镜像里永不包含敏感信息」这条铁律,一半靠它执行27。
层缓存的脾气: Docker 构建缓存是逐层失效的——某一层变了,
它后面所有层都重建。所以 Cargo 项目的 Dockerfile 有一个经典的省力排法
(书里没展开,补充在这里,机制与书中「层」的概念完全一致):
先只 COPY Cargo.toml/Cargo.lock → cargo fetch 或空构建依赖 → 再 COPY 源码 → 真正构建。
依赖没变时,前面几层全命中缓存,改一行代码只重编最后两层;
而原书 Dockerfile 把源码和 manifest 一起拷,任何 .rs 变动都会让
cargo build --release 从头编译全部依赖——教学能接受,编译一次十几分钟,
CI 里就会肉疼。生产做法是引入 cargo-chef 之类的工具把「依赖编译」单独分层。28
标签策略: 示例里 CI 一律推 :latest——每次覆盖,回滚只能靠重推。
至少加一条版本号标签(如 :1.4.2)再叠 :latest,
服务器 up -d 时钉版本号,回滚 = 改回上一个标签。
书里用 latest 是演示简化,别带进生产。29
6.6 Composerize 与 Compose 合并的小工具
书末给了一个顺手的工具:Composerize——把 docker run 命令翻译成
compose 文件(信息本来都在命令里);多个 compose 文件也可以用
-f a.yml -f b.yml 合并成一个栈一次拉起。
机器生成的文件要人工过一遍再上线,字段命名和默认值经常要微调30。
7. 作者的判断与证据(分开标)
有出处的: namespaces 原理、五个概念定义、多阶段构建的理由、 Wasm 读不到 env 的约束、Compose 四概念、七服务配置,均为原书原文或工具事实4710121619。
作者自己的评估:
- 前端用 lighttpd 反代是作者选的「工作流解」,Nginx/Caddy 同样可行(书里未比较);
- 「K8s 大概率是过度设计」的 YAGNI 立场(见 §6 判断块);
- 「Composerize 生成后要人工微调」是经验之谈30;
- 补充(不在书里,来自通用知识):cargo-chef 分层、版本号标签策略, 是对原书 Dockerfile/CI 演示的生产化修正。
8. 边界与局限
- 没有讲镜像扫描与安全: 第 3 章的 cargo-deny 管依赖,容器层的漏洞扫描(trivy 等)没接;
- compose 只到单机多容器: 多机部署留给 Swarm/K8s(见 13 篇);
- 密钥管理仍靠 .env 文件:生产应上 secret manager,书里只在 object_store 那章点过;
- 健康检查只有 Postgres 一个示例,应用自身的 /health 端点被建议但未实现;
- 后端容器没有 readiness 检查,depends_on 只保顺序不保就绪(见 §5.5 判断块)。
9. 可带走的
主线例子一行走完: docker compose up → 七服务按依赖启动 →
健康检查放行 → 前端 8080/lighttpd → /backend 反代后端 8000 → push 触发 CI 推镜像。
- 容器化的动机一句话: 虚拟机把内核跑两遍太贵,namespaces 隔离 + 宿主复用是正解。
- 镜像不可变、容器用完即弃;层数越少镜像越小。
- 多阶段构建 = 编译的垃圾不进产房:构建镜像与运行镜像分开,只搬二进制。
- Wasm 应用拿不到环境变量——部署期可变配置用「反代 + 路径前缀」兑现。
- SPA 托管必须配 404 → index.html,否则深链接全灭。
- docker run 的常识:
--rm(容器不靠手工养)、-p 宿主:容器、-e/--env-file。 - CI 推镜像四步: checkout → 登录(凭据走 secrets)→ Buildx → build-push; 敏感数据永不硬编码进项目文件。
- Compose 四概念:服务、期望状态、健康检查、卷;服务名就是内网 DNS 名。
- 编排 = 生命周期 + 健康 + 伸缩的自动化,本质还是「期望状态」。
- YAGNI 运维版: 单容器 → Compose → Swarm → K8s,逐级升级,别跳级。
.dockerignore是半条安全红线; Cargo 项目把 manifest 与源码分层拷,救的是编译缓存。- 别只推
:latest; 版本号标签是回滚的锚。
10. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 服务器是角色不是机器 | Containerization and Orchestration | text/74-fm-containerization-with-docker.txt:13(搜「not a machine but a role」) |
| 「在我机器上能跑」 | 同上 | text/74-fm-containerization-with-docker.txt:16(搜「excuse」) |
| VM 的开销 | 同上 | text/74-fm-containerization-with-docker.txt:28(搜「hypervisor」) |
| namespaces 与复用 | 同上 | text/74-fm-containerization-with-docker.txt:31(搜「namespaces」)、:34(搜「simplified interface」) |
| 容器化定义 | 同上 | text/74-fm-containerization-with-docker.txt:4(搜「standardized and uniform」) |
| 镜像/容器/层/ARG/ENV | 同上 | text/74-fm-containerization-with-docker.txt:44(搜「immutable」)、:50(搜「Layers」)、:63(搜「Dockerfile」) |
| 后端 Dockerfile 逐行 | 同上 | text/74-fm-containerization-with-docker.txt:76(搜「BASE_IMAGE」)、:115(搜「cargo build --release」)、:121(搜「bookworm-slim」) |
| 命令合并少造层 | 同上 | text/74-fm-containerization-with-docker.txt:163(搜「combining commands」) |
| 前端 Dockerfile 与 lighttpd | 同上 | text/74-fm-containerization-with-docker.txt:241(搜「lighttpd」)、:260(搜「workaround」) |
| 404 兜底与 /backend 改写 | 同上 | text/74-fm-containerization-with-docker.txt:320(搜「error-handler-404」)、:323(搜「proxy.server」)、:387(搜「translate」) |
| docker build/run 参数 | 同上 | text/74-fm-containerization-with-docker.txt:402(搜「docker build」)、:426(搜「docker run」)、:435(搜「disposable」) |
| CI 工作流四步 | 同上 | text/74-fm-containerization-with-docker.txt:518(搜「login-action」)、:545(搜「build-push-action」)、:658(搜「never hardcode」) |
| Compose 的动机 | 同上 | text/74-fm-containerization-with-docker.txt:696(搜「comes into the picture」) |
| Services 定义 | 同上 | text/74-fm-containerization-with-docker.txt:947(搜「computation resource」) |
| Desired state | 同上 | text/74-fm-containerization-with-docker.txt:986(搜「intervenes」) |
| Health checks | 同上 | text/74-fm-containerization-with-docker.txt:808(搜「pg_isready」)、:995(搜「dedicated lightweight endpoint」) |
| Volumes | 同上 | text/74-fm-containerization-with-docker.txt:1001(搜「outlive any container」) |
| 编排定义 | 同上 | text/74-fm-containerization-with-docker.txt:1049(搜「automating lifecycle」) |
| YAGNI 忠告 | 同上 | text/74-fm-containerization-with-docker.txt:1062(搜「smallest possible scale」) |
| Composerize | 同上 | text/74-fm-containerization-with-docker.txt:1035(搜「Composerize」) |
| 七服务 compose 文件 | 同上 | text/74-fm-containerization-with-docker.txt:703(搜「name: ticketing」)、:709(搜「mariadb」)、:832(搜「redis」)、:865(搜「ticketing-backend」) |
6.8 Dockerfile 逐指令参考(每条指令干什么、何时失效)
把两个 Dockerfile 里出现的每条指令单独建档——这是「原书的教法」里最 值得原样接住的部分,因为 Docker 的错误信息永远指向这些词:31
| 指令 | 干什么 | 书里的使用点 |
|---|---|---|
ARG | 构建期变量,只在构建时存在 | BASE_IMAGE(将来换基础镜像只改一行)、SERVER_URL(前端的后端地址假定) |
FROM…AS | 拉基础镜像并给阶段起别名 | base / build-server / build-client / prod 四段;多阶段靠它串 |
WORKDIR | 之后所有命令的工作目录 | /src;前端版用 ENV SRCDIR 间接引用 |
COPY | 宿主机 → 镜像(新层) | 源码五成员 + Cargo 清单;--from=build-server 只搬二进制 |
RUN | 镜像里执行命令(新层) | cargo build --release;apt install(合并成一条) |
ENV | 镜像内的持久环境变量 | SRCDIR、SERVER_URL、PORT=8080 |
ENTRYPOINT | 容器启动命令 | 后端 /bin/ticketing |
层缓存的脾气(实务版): 每条指令产出的层,内容哈希不变就复用; 一变,它和它之后的所有层重建。所以「先 COPY manifest → 再 COPY 源码」 的排法能让依赖编译层跳过——原书 Dockerfile 没做这层优化(教学从简), CI 里编译一次十几分钟时就会肉疼;第 6.5 节给了修正方向32。
6.9 前端镜像的 Wasm 约束再展开(为什么必须是 lighttpd)
这个约束值得单独建档,因为它不是 Docker 问题,是 Wasm 沙箱问题:
编译进 Wasm 的应用「不允许访问 Wasm 运行时之外的文件」——.env
这类环境文件在浏览器沙箱里根本不存在。运行期配置注入这条路被焊死,
只剩构建期硬编码一条路;但硬编码又不甘,于是有了反代方案:33
构建期:SERVER_URL=/backend 编进 Wasm(假定的后端位置)
运行期:lighttpd 拿环境变量(BACKEND_HOST/BACKEND_PORT)兑现这个假定
/backend/* → 剥掉前缀 → 转发给真后端
其它路径 → 静态文件;404 → index.html(Yew 路由接管)
图说:假定留在二进制里,兑现放在容器外——二进制一次构建,到处部署。lighttpd 配置的四行关键(原书 Code listing 9.3):模块行(mod_proxy 必开);
server.error-handler-404 = "/index.html"(没有这行,深链接全灭——
lighttpd 会把 /ticket/42 当磁盘路径找);$HTTP["url"] =~ "^/backend"
块把匹配流量转给 env.BACKEND_HOST:env.BACKEND_PORT;proxy.header 里
map-urlpath 剥掉 /backend 前缀、upgrade => enable 放行 WebSocket 升级
(第 12 章的 WS 靠它)34。
6.10 Compose 文件的逐键速查(七个服务的共同骨架)
把 compose 文件里反复出现的键一次讲清(值都出自原书 docker-compose.yml):35
| 键 | 干什么 | 书中样例 |
|---|---|---|
| image | 用哪个镜像(不自建) | mariadb、postgres:14-alpine、redis:7-alpine、自建 ticketing |
| container_name | 容器名(人的视角) | ticketing-mariadb… |
| hostname | 容器内主机名(同伴视角) | ticketing-backend(前端反代指向它) |
| restart: always | 期望状态的最低表达 | 除构建外的所有服务 |
| env_file | 整个文件注入环境变量 | ./.docker-compose.env |
| environment | 逐条覆盖环境变量 | 前端的 BACKEND_HOST=ticketing-backend |
| ports | 宿主:容器 端口映射 | 3306:3306、8000:8000、8080:8080 |
| volumes | 持久化(匿名卷/bind 卷) | 数据库数据 + 初始化脚本 |
| depends_on | 启动顺序(不保证就绪!) | backend 依赖四基建 |
| healthcheck | 自报健康,供调度判断 | postgres 的 pg_isready 每秒一探 |
depends_on 与 healthcheck 的配合(书里藏着的坑): Compose 只按
depends_on 排启动顺序,不等服务就绪——Postgres 容器活着 ≠ 能接受连接;
所以后端真正依赖的是「Postgres 健康检查通过」,而 compose 里这层语义
要靠 condition: service_healthy(原书文件未用,属可精进处)。
「应用自身的 /health 端点」书里也建议了但同样未实现——两处都是骨架
之上的自然加厚点36。
6.11 从「跑起来」到「可交付」:本章未做但点了名的四件事
书里在 Docker 一节没做、但明说了「该做」的四件事,收在一起就是 生产化的 checklist:37
- 应用自身的健康端点——「最佳实践是做一个轻量的专用端点报告健康: 应用版本 + 数据库连接状态」(Compose 的 healthcheck 机制在等它);
- 镜像漏洞扫描——CI 里推镜像之前过一遍容器层扫描(trivy 类), 与第 3 章 cargo-deny(依赖层)互补;
- secret 管理升级——
.env文件只配活在开发机;生产换 secret manager (与第 8 章 object_store 那节的红线同一条); - 标签策略——
latest是演示简化;「至少加版本号标签再叠 latest, 回滚 = 改回上一个标签」(第 6.5 节展开)。
镜像与容器的关系再敲一次(考试级): 「镜像是蓝图 + 全部建材,不可变,
存于 registry;容器是按蓝图装配的运行实例,为单一目的调好,用完即弃。」
docker run 的 --rm 就是「用完即弃」的纪律表达——容器死了不心疼,
数据在卷里,配置在环境变量里,一切可重建38。
6.12 编排衔接段(为 13 篇铺路)
书末「Orchestration」一节用一段话完成换场:「编排 = 自动化地管理容器化 应用的环境:生命周期、健康检查、各组件的伸缩」;它靠什么?—— 还是期望状态:「引入期望状态的概念(前面讨论过),接下来把它展开到 更多细节。」下一篇的 Swarm 与 K8s,就是同一思想在集群规模上的两次实现: Swarm 用 stack 文件 + manager/worker 小步走到多机,K8s 用 20 个 YAML 把每个细节都变成可声明的对象39。
2.1 历史线的完整版(容器出现前的三代部署)
书里用很短的篇幅给了部署史的三代,值得排成一条线:40
第一代:裸金属拷贝。 服务器是「一个角色」而不是一台机器(裸金属 = 没装虚拟化的物理机);部署 = 拷文件。 机器为服务器优化后与开发机差异越来越大,「在我机器上能跑」由此诞生—— 这不是程序员的借口,是异构环境的必然产物。
第二代:虚拟机打包。 把应用连同整个操作系统装进 VM,「能跑就能到处跑」 实现了,但账单很贵:hypervisor 虚拟出整套硬件,客户 OS 与宿主 OS 的 内核在同一块硅上跑两遍;只为隔离一个应用,要养一个完整的操作系统。 「只要它在任何地方能跑,它就必须在任何地方能跑」的需求本身没错, 错的是买法。
第三代:namespaces + Docker。 Linux 内核早就给了答案——namespaces 把进程的存储、网络、进程表隔离开,同时复用宿主内核;「不再有完全功能 的第二个操作系统」。但 namespaces 难用到「通常只有专家敢碰」; Docker 的历史贡献就是给 namespaces 做了人人都会的接口—— 书里那句「这不是 Docker 发明的,是它的普及之功」值得当作技术史常识记住41。
3.1 五个概念的成对关系(考试级的三组对照)
镜像 vs 容器: 设计图 vs 按图装配的实例;不可变以保值 vs 易造以值得丢弃; 「镜像存于 registry 供复用,容器 from 镜像 instantiate」。
层 vs 镜像: 镜像是层的堆叠;每层是「一次改动」的不可变快照, 底层永远不动;层数越多镜像越大——所以有了两条纪律:合并 RUN 命令 少造层,多阶段构建把「只用于构建的层」全部扔掉。
ARG vs ENV: ARG 只在构建期存在(多阶段里每段要重定义,书里的坑);
ENV 进了镜像、运行期还在,能构建时传也能运行时覆盖,.env 文件可以
成批喂42。
容器 vs 卷: 容器用完即弃,数据不能跟着死;卷是独立于容器生命周期的 持久化抽象——「容器挂了,卷还在;下一个容器接着用」。bind 卷(数据库 文件留在宿主)与命名卷(由 Docker 管理)都服务于同一件事:可丢弃的 计算 + 不丢弃的数据43。
期望状态 vs 实际状态: Compose/Swarm/K8s 的共同心跳——你声明
「我想看到它 running」,系统持续对账;偏离就拉回(restart: always、
healthcheck 杀起重建)。第 13 篇会看到这个思想被复制到集群规模,
一行字都没改44。
5.1 后端 Dockerfile 的逐行账(带「为什么」的完整版)
1 ARG BASE_IMAGE=rustlang/rust:nightly # 将来换镜像只改这一行(最佳实践:会变的前置)
2 FROM ${BASE_IMAGE} AS base # nightly 做基础;起别名供后面 FROM 引用
3 WORKDIR /src # 之后命令的cwd
4-11 COPY ×8 # 五个 workspace 成员 + 根 src/tests + 清单;
# 「与 Dockerfile 同目录」是 docker build 句点的契约
13 FROM base AS build-server # 新阶段基于 base:构建失败不用重拷源码(层缓存)
14 RUN cargo build --release # 生产构建:优化、去调试符号
16 FROM debian:bookworm-slim as prod # 运行镜像:瘦到只剩二进制 + 一个 OpenSSL
17 RUN apt-get update && apt install -y openssl # 两条合一 = 少造一层
18 COPY --from=build-server /src/target/release/ticketing /bin/ # 只搬产物;构建垃圾不进产房
19 ENTRYPOINT ["/bin/ticketing"] # 容器启动命令
图说:三段分工 = 依赖层(base)/ 编译层(build-server)/ 运行层(prod);
前两段在出产后全部丢弃 —— 「multi-stage build:先造一个只为构建的容器,事后丢弃」。书里对 nightly 的一句坦白: 基础镜像选 nightly 是因为那时某些 Wasm 工具链要它;版本号是「写书时的状态」——原书自己提醒「出版时可能 已过时,代码仓库里有更新版」。所有具体版本号都当快照读45。
5.2 前端 Dockerfile 的逐行账(与后端的四处不同)
1-2 ARG BASE_IMAGE / SERVER_URL # 两个构建参数:基础镜像 + 后端地址假定
4 ENV SRCDIR=/src # WORKDIR 的值也做成变量
10 FROM base AS build-client # 第二阶段另起别名
11-12 ARG SERVER_URL / ENV SERVER_URL=… # ★坑:ARG 不跨阶段,必须重新声明再转 ENV
13-17 RUN apt 装编译链 && rustup stable && wasm32 目标 && trunk
19 RUN trunk build --release # 产出 dist/ 静态文件
21 FROM jitesoft/lighttpd as prod # ★不同:运行镜像不是 slim OS,是现成的静态服务器
22 ENV PORT=8080
23-24 COPY dist/ → /var/www/html/ + COPY lighttpd.conf
(无 ENTRYPOINT —— lighttpd 镜像自带启动命令)
图说:前端镜像没有「应用进程」,它是一叠静态文件 + 一个会反代的 Web 服务器。「应用不服务自己」这句话值得背: 「我们的前端应用只是一堆静态文件, 仅此而已;它不服务自己——lighttpd 替它服务,还替它把 /backend 指到 真后端。」前后端镜像的形制差异(有 ENTRYPOINT 的二进制 vs 静态文件 + 服务器)正是「两端部署与伸缩方式不同」这句架构判断的落地46。
6.13 镜像构建与运行的命令全账(含 CI 的四个 action)
本地命令的完整参数语义(原书 docker build/run 两行的逐词):47
docker build -t viktordaroczi/ticketing-backend .
# -t 标签 = 用户名/仓库名(将来 push 到 Docker Hub 的地址就是它)
# 句点 构建上下文 = Dockerfile 与源码所在目录;
# COPY 能看到的文件全在这个句点里(.dockerignore 管黑名单)
docker build -f Dockerfile.frontend -t …/ticketing-frontend .
# -f 显式指定 Dockerfile(默认只认 ./Dockerfile)
docker run -d --rm -p 8000:8000 --name ticketing-backend \
--env-file .docker.env viktordaroczi/ticketing-backend env
# -d 后台(detach),命令行立刻还给你
# --rm 停了即删 —— 容器用完即弃,不手工养
# -p H:C 宿主:容器 端口映射;无此项容器内部端口不可达
# --name 容器名(列表里好认;不起就随机名)
# --env-file 整文件注入环境变量;-e KEY=VAL 单条注入(前端镜像的用法)
# 尾部 env 镜像起后执行的第一条命令(ENTRYPOINT 之外再叠)
图说:build 造的是「类」,run 造的是「实例」;--rm 是「实例不养老」的纪律。CI 工作流的四个 action 各管一段(uses: 引用的都是官方现成品):48
| 步骤 | action | 干什么 |
|---|---|---|
| Checkout | actions/checkout@v4 | 把仓库拉到 CI 机器 |
| 登录 | docker/login-action@v3 | 用 secrets.DOCKERHUB_USERNAME / _TOKEN 登录 registry |
| 构建环境 | docker/setup-buildx-action@v3 | 装 Buildx(多架构/缓存优化的构建器) |
| 构建并推 | docker/build-push-action@v5 | context=.、file=./Dockerfile(或 .frontend)、push=true、tags=…:latest |
红字再引: 「with 的参数就是各 action 期待的输入;比如把 Docker Hub 凭据 传给构建。记住:项目文件里永不硬编码任何敏感数据」——凭据走 GitHub Secrets,打日志也会自动打码49。
6.14 Compose 的更多机制(healthcheck、卷、依赖的三层语义)
healthcheck 五个键(test/interval/timeout/retries,原书 Postgres
样例):test 是探针命令(Postgres 自带 pg_isready);interval: 1s
每秒一探;timeout: 5s 单次超时;retries: 10 失败上限——超过就杀容器、
起新实例(期望状态的对账动作)。「怎么判断健康由你定;最佳实践是应用
自带轻量 /health 端点报版本 + 数据库连通性」50。
卷的两形态与取舍: bind 卷(数据库文件留在宿主目录,人能直接摸)、 命名卷(由 Docker 管理,人只引用名字)。书里 MariaDB/Postgres 用了双卷: 匿名卷存数据 + bind 卷送初始化 SQL——「容器可以读写它们,但即使失败 也毁不掉卷」。数据的安全边界 = 卷的生命周期独立于容器51。
depends_on 的三层语义(超书注记,机制仍属 Compose 通用知识):
默认只排启动顺序;condition: service_started(等它启动)、
service_healthy(等健康检查过)、service_completed_successfully
(等一次性任务成功)。原书文件停在默认档,生产应至少对数据库升到
service_healthy——这正是 healthcheck 一节存在的意义52。
6.15 「读 compose 文件 = 读架构」的完整示范(七服务的依赖图)
┌──────────────┐
│ Adminer │ 8088(可选,观察数据)
└──────┬───────┘
│ 连
┌───────────┐ │ ┌────────────┐
│ MariaDB │◄──┴───►│ backend │ 8000
│ (主库) │ │ (Axum API)│
└───────────┘ └─────┬──────┘
┌───────────┐ │ 反代 /backend
│ PostgreSQL│──────────────┤
│ (fang调度)│ ▼
└───────────┘ ┌────────────┐
┌───────────┐ │ frontend │ 8080(唯一门面)
│ Redis │────────┤ (lighttpd)│
│ (队列缓存)│ └────────────┘
└───────────┘
┌───────────┐
│ MailHog │ 1025/8025(SMTP + 网页)
└───────────┘
图说:backend 是枢纽 —— 四路基建进、一路前端出;
compose 的 depends_on 就是这张图的边,ports 就是对外开放的门。这张图的读法,就是第 6 篇(后端定型)到第 11 篇(并行通信)全部内容 的 30 秒版——「配置即架构」的实例:每一条边(依赖)都能在文件里 找到一行,每一扇门(端口)都能找到映射53。
6.16 本章主线的最终复盘(从裸金属到一条命令)
裸金属时代 拷文件;「在我机器上能跑」成灾
→ VM 时代 打包即 OS;内核跑两遍,太贵
→ Docker namespaces 复用宿主内核;镜像不可变、容器用完即弃
→ 多阶段构建 编译垃圾不进产房(rust:nightly 构建 → debian-slim 运行)
→ Compose 七服务一张网;services/desired state/healthcheck/volumes
→ CI push 即构建推送;徽章对外报健康
→ 编排(Swarm/K8s,见 13 篇) 期望状态思想原样放大到集群
图说:每一级都在重复同一句话 —— 把「环境」从人的记忆里搬到文件里。这一章对全书的意义: 第 3–8 章造的应用,到此成为一件可以在任何
机器上重建的制品——「它在一处能跑,处处能跑」不再是愿望,是
docker compose up 的副产品。而「环境即文件」也正是第 13 篇编排的
入场券:Swarm 与 K8s 读的还是文件,只是文件的粒度从「一个栈」细化到
「每个对象」54。
6.17 前端构建链在镜像里的完整还原(为什么装那一串系统包)
前端 build-client 阶段那行 apt install build-essential libssl-dev cmake clang pkg-config openssl 不是摆设,逐个给理由(依据原书清单与 Rust
生态的通行事实):build-essential = C 工具链(cargo 的 build scripts
要编译少量 C);libssl-dev/openssl = rustls 之外的 TLS 选项与链接;
cmake/clang/pkg-config = 某些 crate 的本地构建依赖(openssl-sys 是
典型)。trunk 则需要 stable 工具链 + wasm32 目标(nightly 基底镜像里
另外装)。「装好这一套,trunk build --release 才产得出 dist/」——
前端镜像的构建层比后端重得多,这正是「两端分别构建、分别伸缩」的
微观证据55。
SERVER_URL 的三次转手(一个 ARG 的旅程):Dockerfile 顶部声明 →
跨阶段重新声明(不重新声明就取不到,书里的坑)→ 转 ENV 进 build-client
环境 → trunk 构建时编进 Wasm。每一手都「交接签字」,少一手就在运行期
变成默认值56。
6.18 与 13 篇的交接清单(Docker 章交给编排章的四件行李)
- 七服务的 compose 文件——Swarm 直接复用(同规格),K8s 靠 Kompose 翻译;
- 期望状态的心法——restart: always 是它在单机的样子;deploy 块、 liveness 探针是它在集群的样子;
- secrets 的种子——.env 时代结束于 Swarm 的 secret 挂载, 再升级为 K8s 的可编程 secret 管理;
- CI 的镜像产物——两条流水线推上 Docker Hub 的
:latest, 是两种编排器的共同食材。57
7. 本章一页复习(容器化上半场的最终形态)
裸金属 → VM(内核两遍) → Docker(namespaces + 复用)
概念五件:镜像(不可变蓝图)/ 容器(用完即弃)/ 层(少造)/ ARG / ENV
两个 Dockerfile:后端多阶段(rust 构建 → slim 运行)
前端多阶段(Wasm 无 env → lighttpd 反代 /backend)
命令两板斧:docker build -t … . / docker run -d --rm -p H:C --env-file …
CI 四步:checkout → login(secrets)→ buildx → build-push
Compose 四概念:services / desired state / healthchecks / volumes
七服务:MariaDB + Adminer + PostgreSQL + Redis + MailHog + backend + frontend
图说:每条竖线都通向第 13 篇 —— Swarm 与 K8s 只是把这份文件换个规模重读。交接清单: compose 文件直接给 Swarm 复用;expect-state 心法在
deploy 块与探针里续命;secrets 从 env_file 进化到挂载再进化到
K8s 对象;CI 的 :latest 是两种编排器的共同食材。58
19. 本篇卷尾(一句话版)
容器化章的全部贡献,是把「环境」从人的记忆里搬进文件:Dockerfile 管 一个组件,compose 管整套系统,CI 管文件产物的持续更新—— 「它在一处能跑,处处能跑」从此是一句陈述句,不是一句愿望。59
20. 本篇卷尾补记(compose 即架构的读法)
读别人系统的 compose/k8s 文件,是摸架构最快的办法;写自己的, 是检验架构清不清楚的最狠办法——每条 depends_on 是一条边、每个 published 端口是一扇门。本章的七服务文件,不妨当作这张读法的练习题。60
Footnotes
-
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:13,搜「not a machine but a role」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:16,搜「work on my machine」与「not an acceptable excuse」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:28,搜「hypervisor」;:19,搜「runs anywhere」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:28,搜「kernel, are running twice」;:31,搜「namespaces offer isolation」)。 ↩ ↩2 ↩3 -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:34,搜「simplified interface」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:4,搜「standardized and uniform format」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:40,搜「blueprint」)、:50(搜「Layers」)、:53(搜「Arguments」)、:58(搜「Environment variables」)。 ↩ ↩2 ↩3 -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:44,搜「immutable」)、:47(搜「ephemeral」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:63,搜「Dockerfile」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:143,搜「multi-stage」)、:152(搜「do not start over」)。 ↩ ↩2 ↩3 -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:158,搜「nothing else」)、:163(搜「combining commands」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:276,搜「nothing more, nothing less」)。 ↩ ↩2 ↩3 -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:260,搜「cannot read environment files」)、:387(搜「without having to recompile」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:402,搜「docker build」)、:426(搜「docker run」)、:435(搜「disposable」)、:441(搜「env-file」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:518,搜「login-action」)、:545(搜「build-push-action」)、:658(搜「never hardcode any sensitive data」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:696,搜「Compose」)、:941(搜「services」)。 ↩ ↩2 ↩3 -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:950,搜「seven services」)、:919(搜「BACKEND_HOST」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:1049,搜「automating lifecycle management」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:703,搜「name: ticketing」)、:709(搜「mariadb」)、:718(搜「init-file」)、:748(搜「adminer」)、:766(搜「postgres」)、:832(搜「redis」)、:844(搜「mailhog」)、:865(搜「ticketing-backend」)、:901(搜「ticketing-frontend」)。 ↩ ↩2 -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:709,搜「mariadb」)、:742(搜「3306:3306」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:959,搜「convenience database client」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:805,搜「healthcheck」)、:829(搜「14-alpine」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:841,搜「save 60 1」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:853,搜「1025:1025」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:871,搜「depends_on」)、:868(搜「hostname」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:919,搜「BACKEND_HOST=ticketing-backend」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:70,搜「never want to include sensitive information」)。「.dockerignore 黑名单」这层机制是通用知识(补充,不在书里)。 ↩ -
补充(不在书里,来自通用知识):层缓存逐层失效与「manifest 先拷」分层法是 Docker 构建的通行做法;cargo-chef 是 Rust 社区实现该模式的工具。 ↩
-
补充(不在书里,来自通用知识):
:latest覆盖式标签的回滚问题与「版本号 + latest 双标签」策略是发布管理的通行做法。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:1035,搜「Composerize」与「review the generated file」)。 ↩ ↩2 -
指令语义为 Docker 通用知识;书中用法见
text/74-fm-containerization-with-docker.txt:76起各代码清单。 ↩ -
层缓存机制为 Docker 通用知识(补充,不在书里);原书 Dockerfile 见
text/74-fm-containerization-with-docker.txt:76。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:260,搜「cannot read environment files」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:284,搜「mod_proxy」)、:320(搜「error-handler-404」)、:323(搜「proxy.server」)、:344(搜「upgrade」)。 ↩ -
键与值见原书 docker-compose.yml(
text/74-fm-containerization-with-docker.txt:703起)。 ↩ -
「depends_on 不保就绪」「建议自建 /health」为我们的工程注记;健康检查机制本身见
text/74-fm-containerization-with-docker.txt:992。 ↩ -
健康端点建议见
text/74-fm-containerization-with-docker.txt:995;secret 红线见text/65-fm-ticket-attachments.txt:366;标签策略见本书第 6.5 节(补充,不在原书)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:44,搜「immutable」)、:47(搜「ephemeral」)、:435(搜「disposable」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:1049,搜「automating lifecycle」)、:1052(搜「desired state」)。 ↩ -
历史线见
text/74-fm-containerization-with-docker.txt:13(服务器是角色)、:16(works on my machine)、:22(VM 开销)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:28,搜「kernel, are running twice」)、:34(搜「specialists」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:50,搜「Layers」)、:53(搜「Arguments」)、:58(搜「Environment variables」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:1001,搜「outlive any container」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:986,搜「desired state」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:76,搜「BASE_IMAGE」)、:1508(搜「might be well outdated」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:276,搜「nothing more, nothing less」)。 ↩ -
命令与参数语义见
text/74-fm-containerization-with-docker.txt:402、:426。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:518,搜「login-action」)、:533(搜「setup-buildx-action」)、:545(搜「build-push-action」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:658,搜「never hardcode」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:808,搜「pg_isready」)、:995(搜「dedicated lightweight endpoint」)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:1001,搜「outlive any container」)、:1004(搜「bind volumes」)。 ↩ -
condition 三档为 Compose 通用知识(补充,不在原书);原书文件见
text/74-fm-containerization-with-docker.txt:865。 ↩ -
依赖图为我们对原书 docker-compose.yml 的图示化;文件见
text/74-fm-containerization-with-docker.txt:703起。 ↩ -
意义为我们的归纳;「if it runs anywhere, it must run everywhere」见
text/74-fm-containerization-with-docker.txt:19。 ↩ -
包清单见
text/74-fm-containerization-with-docker.txt:220;各包的构建角色为 Rust 生态通行知识(补充,不在书里)。 ↩ -
出处:「Containerization with Docker」(
text/74-fm-containerization-with-docker.txt:263,搜「propagate this argument」)、:268(搜「environment variable from it」)。 ↩ -
交接清单为我们的对账;compose 文件见
text/74-fm-containerization-with-docker.txt:703。 ↩ -
复习图与交接为我们的归纳;衔接见
13-orchestration.md§3.4–§3.5。 ↩ -
卷尾为我们的归纳;「if it runs anywhere」见
text/74-fm-containerization-with-docker.txt:19。 ↩ -
读法见本篇 §6.15;七服务文件见原书
text/74-fm-containerization-with-docker.txt:703。 ↩