跳转至

镜像 — 分层文件系统、Dockerfile 与多阶段构建

这一章讲三件事: 镜像是什么(分层的 tarball,Docker 世界里的"安装盘"); 怎么造镜像(三条路,正路是 Dockerfile);造好怎么发(命名、推送、供应链安全)。 主线例子贯穿始终:从一个 Alpine 开始,先手动改造它理解机制, 再用 Dockerfile 和多阶段构建把它做成生产可用的最小镜像。

1. 顶层全景:从一个 tarball 到一条交付链

  镜像 = 分层 tarball(不可变层 + 顶层可写容器层)
      │  三种造法:
      │    ① 交互式:run 容器 → 改 → commit(理解机制用)
      │    ② Dockerfile:文本描述 → docker image build(生产正路)
      │    ③ save/load:tar 包进出口(离线搬运)
      ▼
  名字 = <注册表>/<组织>/<名字>:<标签>   ← tag 是引用,不是拷贝
      ▼
  push 到注册表(Docker Hub / ECR / ACR……)→ 别人 pull → 他们的容器
      ▼
  供应链安全:官方镜像、扫描(Trivy)、签名(Notary/Cosign)、最小权限、勤更新

  图说:左边是"造",右边是"发"。本章的推理顺序:先把镜像结构讲透(3.1),
  再讲怎么造(3.2–3.6),最后讲怎么发(3.7–3.9)。

2. 镜像的结构:分层的 tarball

2.1 一切皆是文件,镜像是一大坨文件

Linux 上"一切皆文件",整个操作系统就是一个文件系统。顺着这个事实,书里给出镜像的定义: 镜像就是一个大 tarball,而且是一个分层的 tarball1。

tarball(.tar 归档)是把多个文件和目录打包成单个文件的格式,常用 gzip 再压缩, Linux/macOS 上用 tar 命令解包1。把一个文件系统打进 tarball,它就能像货物一样被搬运—— 这正是第 01 章"标准化封装"的技术形态。

2.2 层:每层只是相对下层的增量

镜像是层(layer)的栈,不是一整块。第一层叫基础层(base layer), 通常是 Docker Hub 上的官方 Linux 发行版镜像(Alpine、Ubuntu、CentOS); 之后每层只装"相对下面各层的变化"。原书的例子:一个 nginx 网页应用镜像是三层—— Alpine 基础层 + "加 nginx"层 + 应用文件(HTML/CSS/JS)层2。

每层的内容映射到宿主机的一个特殊目录(通常是 /var/lib/docker/ 下的子目录)。 关键性质:层是不可变的(immutable)——一旦生成就不能改,唯一能做的操作是物理删除。 书里强调这个性质"打开了巨大的机会空间",主要指缓存(见 2.5)3。

2.3 写时复制:改文件先抄上来

层不可变,那容器里改文件怎么办?答案是写时复制(copy-on-write): 上层要修改下层已有的文件时,先把文件复制到本层,再改副本;如果只是使用下层文件, 直接引用,不动它4。效果是:读共享、写隔离,谁也不破坏谁。

2.4 存储驱动:把层叠成一个视图

把多层合并成一个容器能用的根文件系统,由存储驱动(storage driver,旧称 graph driver) 完成。当今推荐且最常用的是 overlay2——原书给的理由是性能和效率; 旧的 overlay 基本被它取代5。还记得第 03 章的 unionfs 吗?存储驱动就是 unionfs 思想的 工程实现:合并时给分支定优先级,同名文件显示高优先级的那个。

2.5 可写容器层与层共享:为什么容器又快又省

从镜像创建容器时,Docker 在只读层栈顶上再加一个可写容器层(read/write)6。 由于底下各层不可变,它们可以被任意多个容器共享——每个容器只需要一个薄薄的写层。

资源账是这么算的:镜像层只需为第一个容器加载进内存一次,之后的容器只创建薄写层。 容器启动快、占资源少,一半功劳在这7。这也再次解释了第 03 章那句 "容器启动以毫秒计,VM 要数秒到几分钟":VM 没有这层共享,每台都背一整份 OS。

3. 造镜像的三条路

书里明确说:造新镜像有三种方式——交互式构建后提交、用 Dockerfile 构建、 从 tarball 导入;其中 Dockerfile 是"最重要的一种"8。

3.1 主线例子第一遍:交互式造 my-alpine(理解机制)

这条路的价值是把"层"和"提交"这两个概念变成肉眼可见。照书里的步骤走:

  ① docker container run -it --name sample alpine:3.21 /bin/sh
     → 起一个 Alpine 容器,交互式进 shell(alpine 镜像约 8 MB,最小 Linux)
  ② 容器里:apk update && apk add curl    ← Alpine 的包管理器 apk 装 curl
  ③ curl -I <某个网址>                    ← 验证装好了(只看响应头)
  ④ exit 退出;容器停在 Exited 状态
  ⑤ docker container diff sample
     → 输出 A /usr/bin/curl 之类:A=added,C=changed,D=deleted
     → 这就是"可写容器层相对镜像层的增量",逐文件列出来[^9]
  ⑥ docker container commit sample my-alpine
     → 输出 sha256:78a1a06bef41...  ← 新镜像的 ID
  ⑦ docker image ls
     → my-alpine 的 ID 正是 78a1a06bef41,自动带上 latest 标签[^10]
  ⑧ docker image history my-alpine
     → 列出镜像的每一层;最顶上那层就是刚才加 curl 产生的,
       下面两层来自 Alpine 3.21 官方镜像两个月前的构建[^11]

  图说:diff 让你看见"增量",commit 把"增量"冻结成新的一层,history 让你回看每层从哪来。

交互式的缺点书里说得直白:手动、不可重复、不可扩展、易错——"就像人类做的任何手工任务一样"12。 探索、做原型可以这么干,正经营生不行。 这就逼出了 Dockerfile。

3.2 Dockerfile:声明式的造法

Dockerfile 是一个文本文件(默认就叫 Dockerfile),里面写着怎么构建镜像, 是一种声明式(declarative)的做法——你描述期望的结果,让系统自己琢磨怎么达成; 相对的是命令式(imperative),一步步下指令13。

先给一个六行的完整样品,后面逐词拆14:

FROM python:3.12
RUN mkdir -p /app
WORKDIR /app
COPY ./requirements.txt /app/
RUN pip install -r requirements.txt
CMD ["python", "main.py"]

FROM——一切从基础镜像开始。 每个 Dockerfile 以 FROM 开头,指定基础镜像; Docker Hub 上有各大发行版和主流语言运行时的官方镜像。特殊写法 FROM scratch 表示从零开始,用于只装单个静态链接二进制的超小镜像;scratch 是空镜像, 而且 FROM scratch 实际是空操作,不产生层15。

RUN——构建期执行命令。 参数是任意合法 Linux 命令,比如 RUN yum install -y wget (CentOS/RHEL 系)或 RUN apt-get update && apt-get install -y wget(Debian/Ubuntu 系)。 多条命令建议用 \ 续行合并成一条,原因见 3.6 的层数优化16。

COPY 与 ADD——把文件搬进镜像。 两者都从构建上下文(build context)复制文件进镜像, 区别只有一个:ADD 额外支持解包 TAR 文件和从 URL 下载17。例子:

COPY . /app              # 当前目录全部递归复制到 /app
COPY sample.txt /data/my-sample.txt   # 复制并重命名
ADD sample.tar /app/bin/ # 解包 tar
ADD http://example.com/sample.txt /data/   # 从 URL 下载

安全要点:复制进镜像的文件默认属主是 UID/GID 0(即 root);可用 --chown=11:22 改归属 (用名字则该用户必须已存在于镜像的 /etc/passwd)17。

WORKDIR——工作目录,而且会"记住"。 它和 RUN cd 有个致命差别:

RUN cd /app/bin
RUN touch sample.txt     # 文件落在镜像根目录!cd 的效果不会被保留

WORKDIR /app/bin
RUN touch sample.txt     # 文件落在 /app/bin——只有 WORKDIR 跨层保持上下文[^18]

CMD 与 ENTRYPOINT——定义容器启动时跑什么。 这两个和其他关键字不同: 其他都在构建时执行,它俩定义的是容器启动时发生什么19。书里有句难得的坦白: "两者的区别很微妙,老实说大多数用户没完全搞懂,也没按设计意图用;好在多数时候容器照样跑。"20

原书的讲法是把一条 Linux 命令拆成"命令 + 参数",比如 ping -c 3 8.8.8.8:

  • ENTRYPOINT 定义命令(ping),CMD 定义参数(-c 3 8.8.8.8)21:
FROM alpine:3.21
RUN apk update && apk add curl
ENTRYPOINT ["ping"]
CMD ["-c","3","8.8.8.8"]
  • 方括号写法叫 exec form(JSON 数组(方括号括起的字符串列表),逐项对应命令被空格切开的每一段),是推荐写法; 裸命令写法 CMD command param1 叫 shell form21。
  • 覆盖规则:run 时追加参数会覆盖 CMD(docker container run pinger -w 5 127.0.0.1 → ping 环回地址 5 秒);要覆盖 ENTRYPOINT 得用 --entrypoint ash22。
  • 坑:ENTRYPOINT 缺省时默认是 /bin/sh -c,CMD 的字符串会交给 shell 执行—— 结果主进程变成了 /bin/sh 而非你的应用,信号处理等行为都会变得别扭23。 这也是"exec form 优先"的实践理由(补充,不在书里,来自通用知识)。

构建与运行:

  $ docker image build -t pinger .      ← 末尾的 . 是构建上下文(当前目录)
  $ docker container run --rm -it pinger
      ↑ --rm:容器的主进程一结束就自动删除容器

  BuildKit 的输出长这样(书里逐行解读)[^24]:
    [+] Building 10.8s (6/6) FINISHED          ← 全程约 11 秒、6 步
    => [1/2] FROM docker.io/library/ubuntu:25.04@sha256...   ← 正在执行第 1/2 条指令
    => [2/2] RUN apt-get update && apt-get install -y wget
    => writing image sha256:df997d6c1fb...     ← 产出镜像 ID
    => naming to docker.io/library/my-ubuntu   ← 命名 my-ubuntu:latest

一个"复杂 Dockerfile"的样品(Node.js 应用),注意 COPY 的顺序设计25:

FROM node:23-bookworm
RUN mkdir -p /app
WORKDIR /app
COPY package.json /app/    # 先只拷 package.json
RUN npm install            # 装依赖(慢,要让它可缓存)
COPY . /app                # 再拷其余源码(变得快)
ENTRYPOINT ["npm"]
CMD ["start"]

3.3 多阶段构建:260 MB → 136 KB 的主线例子

这是全章最硬的一组数字。书里的例子是一个 C 语言 Hello World:

  单阶段 Dockerfile:FROM alpine → 装 alpine-sdk(含 gcc)→ 编译 → 镜像 260 MB
  问题:一个 Hello World 二进制,背着一整套编译器进生产[^26]

  多阶段 Dockerfile:
    FROM alpine:3.21 AS build          ← 第一阶段,别名 build
    RUN apk update && apk add --update alpine-sdk
    WORKDIR /app
    COPY . /app
    RUN gcc -static -O2 hello.c -o bin/hello   ← 静态编译,运行零依赖

    FROM scratch                        ← 第二阶段,空镜像
    COPY --from=build /app/bin/hello /app/hello   ← 只把产物拿过来
    ENTRYPOINT ["/app/hello"]

  结果:260 MB → 136 KB,缩小超过三个数量级[^27]

  图说:构建工具留在第一阶段,第二阶段只带产物。阶段之间用 --from 传递文件。

小镜像的收益书里列了四条:攻击面更小、磁盘和内存占用更少、容器启动更快、 从注册表下载更省带宽27。多阶段构建是 Docker 工程化里性价比最高的一招,没有之一。

3.4 Dockerfile 最佳实践:缓存、层数、体积

① 容器是朝生暮死的(ephemeral)。 设计应用时,让初始化和清理都尽量快—— 容器随时会被停掉重建,别把"启动时要做很久的事"写进应用28。

② 利用不可变层的缓存:顺序决定速度。 规则只有一句:某层变了,它后面的所有层都要重建29。 书中分析的经典反面教材:

COPY . /app        # 源码每次变 → 这层每次都变
RUN npm install    # 排它后面 → 每次都被迫重跑(几分钟!)

解法就是 3.2 那个样品:把几乎不变的 package.json 先拷进去、npm install 提前, 频繁变动的源码最后拷30。变更频率低的指令写前面,高的写后面——这条规则值很多钱。

③ 减少层数。 每条 FROM/COPY/RUN 指令产生一层;层越多,存储驱动合并成根文件系统的 工作量越大,容器启动越慢。把多个 RUN 用 && 串成一条(例:apt-get update && apt-get install ... && rm -rf /var/lib/apt/lists/*),三层变一层31。顺带一提: 那条命令末尾删掉 apt 列表缓存,本身也是减体积的惯用手法。

④ 保持镜像最小。 三招:用 .dockerignore(作用类似 .gitignore,把不要的文件排除在 构建上下文外);不装没用的包;用多阶段构建32。

3.5 第三条路:save 与 load

镜像"不过是个 tarball"的最直接证据:docker image save -o my-alpine.tar my-alpine 把镜像导出成文件,docker image load -i my-alpine.tar 再导回来(输出 Loaded image: my-alpine:latest)33。离线环境、隔离网段搬运,靠的就是这条。

4. 升降迁移:把老应用装进容器(lift and shift)

不是所有应用都能重写。书里面对的是企业现实:一大票在生产线上跑着、文档稀缺、 "没人敢碰"的传统应用,业务又离不开34。Docker 2017 年给企业客户推过叫 MTA (Modernize Traditional Apps)的项目,承诺不改一行代码把 Java/.NET 应用容器化; 其他语言(C/C++/Python/Node.js/Ruby/PHP/Go)同理35。

书里假设的对象是一个 10 年前的 Java SE 6 应用,工序一步一步来:

① 盘外部依赖。 列出应用与外部世界的所有"接缝":用什么数据库、连接串什么样、 调了哪些外部 API(API key 在哪)、是否接企业服务总线(ESB)。做成清单36。

② 盘点源码和构建。 源码归拢到一个项目根目录(它就是 build 的 context); 记下原来的构建命令——Java 多半是 Maven,.NET 是 MSBuild,C/C++ 是 make37。

③ 配置分三类。 构建期配置(构建时要的)、环境配置(随环境变:开发/预发/生产)、 运行期配置(应用跑着时取的,比如访问外部 API 的 secrets)38。

④ secrets 单列。 书里给了明确的好坏排序:最差是硬编码或明文写在配置文件/环境变量里; 好得多的是运行期从加密存储里取,比如 HashiCorp Vault、AWS Secrets Manager、Azure Key Vault (通过 TLS 安全连接下发)39。

⑤ 写 Dockerfile,允许难看。 原书的提醒很有人情味:别指望一次写对,可能要迭代好几轮; Dockerfile 可能又长又丑,先求有工作镜像,再慢慢调40。要点: 基础镜像选与应用兼容的(有官方 Java 镜像就用,没有就从发行版装); WORKDIR /app + COPY . . 归拢源码(别忘 .dockerignore); 构建用原来的命令(RUN mvn --clean install);ENV 给非密配置提供默认值、 secrets 绝对不给默认值;EXPOSE 声明端口;ENTRYPOINT 定义启动命令, 需要预运行任务就写个 docker-entrypoint.sh(记得 chmod +x)41。

值不值? 书里的答案引的是 ROI:DockerCon 2018/2019 上企业客户公开披露, 传统应用 Docker 化后维护成本节省超 50%、新版本部署间隔缩短最多 90%42—— 与第 01 章那组数字同源,这里补上了"传统应用"这个适用口径。

5. 分享与运输:命名、注册表、推送

5.1 名字的语法

镜像要被拉取,得有一个全局唯一的名字,这个动作叫 tag(打标签); 存放镜像的中心位置叫注册表(registry)43。

镜像全限定名的语法(照原书表格接住)44:

  <注册表 URL>/<用户或组织>/<名字>:<标签>
部分 含义 省略时
registry URL 注册表地址,默认 docker.io 省略 = Docker Hub
User/Org 个人或组织的 Docker ID 官方镜像可省略
name 镜像名(也叫 repository) 不可省
tag 标签,常用来标版本 省略 = latest

省略规则的三个特例:不写注册表 → Docker Hub;不写标签 → latest;官方镜像 → 不需要组织名45。 例子:alpine = Docker Hub 官方 alpine 的 latest;ubuntu:22.04 = 官方 ubuntu 的 22.04 标签; hashicorp/vault = hashicorp 组织的 vault;https://registry.acme.com/engineering/web-app:1.0 = 私有注册表 acme 上 engineering 组织的 web-app 1.045。

5.2 官方镜像是什么

Docker Hub 的镜像存在 repository 里;官方仓库(official repositories)由镜像内 那个软件的负责方(个人或组织)亲自维护——比如 Ubuntu 团队维护 Ubuntu 镜像。 官方镜像覆盖基础 OS、主流语言运行时、常用数据存储等; Docker 有一个专门的团队负责评审发布,并且会扫描所有官方镜像的漏洞46。

5.3 推送:tag 是引用,不是拷贝

推送三部曲(书里的实例,用户名换成你的)47:

  $ docker image tag alpine:latest gnschenker/alpine:1.0
      ↑ 注意:这一步不产生新镜像,只是给同一个镜像起了个新名字(新引用)
  $ docker login -u gnschenker -p <密码>
  $ docker image push gnschenker/alpine:1.0
      → The push refers to repository [docker.io/gnschenker/alpine]
      → 04a094fe844e: Mounted from library/alpine
      → 1.0: digest: sha256:5cb04fce... size: 528

"Mounted from library/alpine" 这行泄露了机制:你本地的 alpine 层和官方镜像的层是 同一批内容,推送时注册表发现已有就直接挂载引用,只上传增量47。 push 会自动在注册表上创建 repository,可设私有(要权限才能拉)或公开48。

5.4 供应链安全五条军规

章末的安全清单(详细展开在第 08、13 章)49:

  1. 用官方或可信厂商验证过的镜像——它们持续更新、持续被扫描;
  2. 定期扫描漏洞——工具:Trivy、Clair、Docker Scout;
  3. 签名与验签——数字签名(Docker Content Trust / Notary)保证镜像未被篡改、来源可信;
  4. 最小权限——容器不用 root 跑,权限给到够用为止;
  5. 勤更新——持续跟进镜像维护者的补丁。

6. 作者的判断与证据

  • "手动造镜像不可重复、不可扩展"→ 用 Dockerfile:这是工程判断,依据是手工流程 的固有缺陷;交互式路径被保留只为探索12。
  • 多阶段构建与缓存排序是带数字的证据:260 MB → 136 KB(实测对比)、 npm install 层重排(机制推演 + 构建时间可验证)2730。
  • ROI 数字(50%+/90%):作者称来自 DockerCon 2018/2019 客户披露,无书面出处42。
  • CMD/ENTRYPOINT 的坦白("大多数用户没搞懂")是作者承认的设计复杂性, 他自己也只给"ENTRYPOINT=命令、CMD=参数"这一个实用框架,更深的细节留给读者试错20。

判断(我们的,不是书里的): 本章最该带走的一个心智模型是—— 镜像名(tag)是指针,镜像内容由哈希(sha256)定身份。 理解了这一点, "latest 漂移"" Mounted from""digest 固定版本"这类运维现象全都自动理顺; 反之如果只记命令,换一个注册表就会迷路。 如果错,会错在: 个别注册表实现会把 tag 实现为内容寻址之外的别名机制, 但"tag 为引用、digest 为身份"是 OCI 分发规范的核心语义,大范围不会错。

7. 边界与局限

  • 缓存规则的例外:BuildKit 对有些指令做了优化,"大多数指令产生一层"是近似说法, 不是定理50。
  • layer 与镜像的内部格式(tar 的分层布局、manifest)本章未展开,只到"分层 tarball" 的直观层。
  • lift and shift 的工序是清单式的,不含真实老应用的完整 Dockerfile 成品; 照着做仍会踩应用特有的坑(书也承认要迭代)40。
  • 供应链安全是预告片,工具细节在 08(扫描)与 13(安全专章);这里只立军规。
  • 数字时效:overlay2 推荐、Trivy/Clair/Scout 名单、260→136 KB 的对比, 都是 2026 年初的状态;工具名单会变,"层缓存、多阶段、最小化"的原则不会。

8. 可带走的

  1. 镜像 = 分层的 tarball;每层是不可变增量,层间靠写时复制共享, overlay2 存储驱动把它们叠成根文件系统145。
  2. 容器 = 镜像的只读层 + 一个薄可写层;多容器共享底层,所以快、省67。
  3. 造镜像三条路:交互式 commit(学机制)、Dockerfile(生产正路)、save/load(离线搬运)8。
  4. diff 看增量、commit 冻结层、history 回看层——理解层的三个调试命令911。
  5. CMD=参数、ENTRYPOINT=命令;exec form 优先;run 追加参数覆盖 CMD, --entrypoint 覆盖 ENTRYPOINT2122。
  6. 缓存铁律:一层变,其后全变——变得慢的依赖装前面,变得快的源码拷后面2930。
  7. 多阶段构建:构建环境和运行产物分阶段,产物用 COPY --from 拿过来; 书里实测 260 MB → 136 KB27。
  8. 镜像名 = 注册表/组织/名:标签;省略有默认(Docker Hub/latest),tag 是引用不是拷贝4447。
  9. .dockerignore 排除无关文件;RUN 串联合并层;不装没用的包3132。
  10. 供应链军规:官方镜像、定期扫描(Trivy)、签名验签、非 root、勤更新49。

9. 原文地图

主题 原书章 原文位置
镜像=分层 tarball、tarball 解释 What are images? text/52-fm-what-are-images.txt:5(搜「big tarball」) · text/52-fm-what-are-images.txt:7(搜「tarball」)
层、基础层、不可变、缓存 What are images? text/52-fm-what-are-images.txt:20(搜「base layer」) · text/52-fm-what-are-images.txt:34(搜「immutable」) · text/52-fm-what-are-images.txt:60(搜「cached」)
可写容器层、层共享 What are images? text/52-fm-what-are-images.txt:66(搜「writable container layer」) · text/52-fm-what-are-images.txt:74(搜「shared among many containers」)
写时复制 Creating Docker images text/53-fm-creating-docker-images.txt:2(搜「Copy-on-write」) · text/53-fm-creating-docker-images.txt:7(搜「copies this file up」)
存储驱动、overlay2 Creating Docker images text/53-fm-creating-docker-images.txt:17(搜「graph drivers」) · text/53-fm-creating-docker-images.txt:27(搜「overlay2」)
三条造镜像之路 Creating Docker images text/53-fm-creating-docker-images.txt:32(搜「three ways」)
交互式造镜像、diff A/C/D Creating Docker images text/53-fm-creating-docker-images.txt:42(搜「interactively」) · text/53-fm-creating-docker-images.txt:97(搜「diff」) · text/53-fm-creating-docker-images.txt:104(搜「A stands for added」)
commit、history Creating Docker images text/53-fm-creating-docker-images.txt:107(搜「commit」) · text/53-fm-creating-docker-images.txt:128(搜「78a1a06bef41」) · text/53-fm-creating-docker-images.txt:134(搜「history」)
手动造法的缺陷 Creating Docker images text/53-fm-creating-docker-images.txt:152(搜「not repeatable」)
Dockerfile 定义、声明式 Creating Docker images text/53-fm-creating-docker-images.txt:156(搜「text file」) · text/53-fm-creating-docker-images.txt:160(搜「declarative」)
FROM、scratch Creating Docker images text/53-fm-creating-docker-images.txt:194(搜「FROM keyword」) · text/53-fm-creating-docker-images.txt:211(搜「FROM scratch」) · text/53-fm-creating-docker-images.txt:216(搜「no-op」)
RUN 多行与包管理器 Creating Docker images text/53-fm-creating-docker-images.txt:221(搜「RUN keyword」) · text/53-fm-creating-docker-images.txt:250(搜「--no-install-recommends」)
COPY/ADD 区别、--chown Creating Docker images text/53-fm-creating-docker-images.txt:267(搜「COPY and ADD」) · text/53-fm-creating-docker-images.txt:277(搜「unpack TAR」) · text/53-fm-creating-docker-images.txt:302(搜「UID」)
WORKDIR vs cd Creating Docker images text/53-fm-creating-docker-images.txt:316(搜「WORKDIR keyword」) · text/53-fm-creating-docker-images.txt:337(搜「persisted across layers」)
CMD/ENTRYPOINT、坦白 Creating Docker images text/53-fm-creating-docker-images.txt:343(搜「CMD and ENTRYPOINT」) · text/53-fm-creating-docker-images.txt:355(搜「most users don't fully understand」)
ENTRYPOINT=命令、CMD=参数、exec form Creating Docker images text/53-fm-creating-docker-images.txt:430(搜「define the command」) · text/53-fm-creating-docker-images.txt:379(搜「JSON array」)
覆盖 CMD/ENTRYPOINT、/bin/sh -c Creating Docker images text/53-fm-creating-docker-images.txt:410(搜「override the CMD」) · text/53-fm-creating-docker-images.txt:422(搜「--entrypoint」) · text/53-fm-creating-docker-images.txt:438(搜「/bin/sh -c」)
构建命令、BuildKit 输出 Creating Docker images text/53-fm-creating-docker-images.txt:396(搜「image build」) · text/53-fm-creating-docker-images.txt:524(搜「Building 10.8s」) · text/53-fm-creating-docker-images.txt:548(搜「df997d6c1fb」)
Node 复杂 Dockerfile Creating Docker images text/53-fm-creating-docker-images.txt:447(搜「complex」) · text/53-fm-creating-docker-images.txt:453(搜「node:23-bookworm」)
多阶段构建、260MB→136KB Creating Docker images text/53-fm-creating-docker-images.txt:559(搜「multi-stage」) · text/53-fm-creating-docker-images.txt:618(搜「260 MB」) · text/53-fm-creating-docker-images.txt:637(搜「FROM scratch」) · text/53-fm-creating-docker-images.txt:660(搜「136 KB」)
缓存铁律与 npm install 重排 Creating Docker images text/53-fm-creating-docker-images.txt:685(搜「subsequent layers」) · text/53-fm-creating-docker-images.txt:695(搜「npm install」) · text/53-fm-creating-docker-images.txt:707(搜「package.json」)
层数合并、.dockerignore、最小化 Creating Docker images text/53-fm-creating-docker-images.txt:717(搜「number of layers」) · text/53-fm-creating-docker-images.txt:752(搜「.dockerignore」) · text/53-fm-creating-docker-images.txt:757(搜「unnecessary packages」)
save/load Containerizing a legacy app using the lift and shift approach text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:1(搜「Saving and loading」) · text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:10(搜「image save」)
MTA 与不改一行代码 Containerizing a legacy app using the lift and shift approach text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:45(搜「Modernize Traditional Apps」) · text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:49(搜「without the need to change a single line」)
依赖清单、构建工具 Containerizing a legacy app using the lift and shift approach text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:61(搜「external dependencies」) · text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:88(搜「Maven」)
配置三类、secrets 排序 Containerizing a legacy app using the lift and shift approach text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:104(搜「Build time」) · text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:127(搜「hardcoding」) · text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:138(搜「Vault」)
Dockerfile 迭代忠告、EXPOSE/ENTRYPOINT Containerizing a legacy app using the lift and shift approach text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:144(搜「one-shot-and-go」) · text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:201(搜「EXPOSE」) · text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:218(搜「docker-entrypoint.sh」)
ROI 数字 Containerizing a legacy app using the lift and shift approach text/55-fm-sharing-or-shipping-images.txt:4(搜「DockerCon 2018」) · text/55-fm-sharing-or-shipping-images.txt:6(搜「50% saving」) · text/55-fm-sharing-or-shipping-images.txt:7(搜「90% reduction」)
tag 与注册表定义 Sharing or shipping images text/55-fm-sharing-or-shipping-images.txt:15(搜「globally unique」) · text/55-fm-sharing-or-shipping-images.txt:19(搜「image registries」)
全限定名语法 Sharing or shipping images text/55-fm-sharing-or-shipping-images.txt:47(搜「fully qualified name」) · text/55-fm-sharing-or-shipping-images.txt:49(搜「registry URL」) · text/55-fm-sharing-or-shipping-images.txt:91(搜「omit the registry URL」)
官方镜像 Sharing or shipping images text/55-fm-sharing-or-shipping-images.txt:128(搜「official」) · text/55-fm-sharing-or-shipping-images.txt:141(搜「scans all official images」)
tag 是引用、push 三步、Mounted Supply chain security practices text/56-fm-supply-chain-security-practices.txt:12(搜「docker image tag」) · text/56-fm-supply-chain-security-practices.txt:14(搜「new reference」) · text/56-fm-supply-chain-security-practices.txt:19(搜「docker login」) · text/56-fm-supply-chain-security-practices.txt:30(搜「Mounted from library/alpine」)
私有/公开仓库 Supply chain security practices text/56-fm-supply-chain-security-practices.txt:33(搜「repository can be private」)
供应链军规五条 Summary text/57-fm-summary.txt:2(搜「official and verified」) · text/57-fm-summary.txt:4(搜「Trivy」) · text/57-fm-summary.txt:6(搜「signing」) · text/57-fm-summary.txt:9(搜「least privilege」) · text/57-fm-summary.txt:11(搜「Regular updates」)

  1. 出处:「What are images?」第 1–14 段(text/52-fm-what-are-images.txt:2,搜「everything is a file」;:5,搜「layered filesystem」;:8,搜「tar archive」)。 ↩↩↩

  2. 出处:「What are images?」第 16–48 段(text/52-fm-what-are-images.txt:20,搜「base layer」;:27,搜「delta of changes」;:43,搜「Add nginx」)。 ↩

  3. 出处:「What are images?」第 27–61 段(text/52-fm-what-are-images.txt:34,搜「immutable」;:58,搜「/var/lib/docker」;:60,搜「without ever becoming stale」)。 ↩

  4. 出处:「Creating Docker images」第 1–15 段(text/53-fm-creating-docker-images.txt:2,搜「Copy-on-write」;:6,搜「copies this file up」)。 ↩↩

  5. 出处:「Creating Docker images」第 17–28 段(text/53-fm-creating-docker-images.txt:18,搜「graph drivers」;:22,搜「merges multiple image layers」;:27,搜「overlay2」)。 ↩↩

  6. 出处:「What are images?」第 63–76 段(text/52-fm-what-are-images.txt:64,搜「stack of immutable」;:66,搜「writable container layer」;:71,搜「read/write」)。 ↩↩

  7. 出处:「What are images?」第 74–83 段(text/52-fm-what-are-images.txt:79,搜「tremendous reduction」;:82,搜「thin container layer」)。 ↩↩

  8. 出处:「Creating Docker images」第 31–39 段(text/53-fm-creating-docker-images.txt:32,搜「three ways」;:36,搜「Dockerfile」;:37,搜「tarball」)。 ↩↩

  9. 出处:「Creating Docker images」第 84–105 段(text/53-fm-creating-docker-images.txt:97,搜「diff」;:104,搜「A stands for added」;:105,搜「C stands for changed」)。 ↩

  10. 出处:「Creating Docker images」第 106–130 段(text/53-fm-creating-docker-images.txt:110,搜「commit」;:114,搜「78a1a06bef41」;:123,搜「image ls」;:128,搜「78a1a06bef41」)。原文说明未显式指定 tag 时 Docker 总是默认打上 latest。 ↩

  11. 出处:「Creating Docker images」第 132–144 段(text/53-fm-creating-docker-images.txt:134,搜「history」;:140,搜「marked in red」)。 ↩

  12. 出处:「Creating Docker images」第 149–158 段(text/53-fm-creating-docker-images.txt:151,搜「it is a manual」;:152,搜「not repeatable」)。 ↩↩

  13. 出处:「Creating Docker images」第 156–166 段(text/53-fm-creating-docker-images.txt:157,搜「declarative way」;:160,搜「Declarative versus imperative」)。 ↩

  14. 出处:「Creating Docker images」第 168–192 段(text/53-fm-creating-docker-images.txt:170,搜「FROM python:3.12」)。 ↩

  15. 出处:「Creating Docker images」第 194–219 段(text/53-fm-creating-docker-images.txt:201,搜「FROM ubuntu:24.10」;:211,搜「FROM scratch」;:216,搜「no-op」)。 ↩

  16. 出处:「Creating Docker images」第 221–265 段(text/53-fm-creating-docker-images.txt:225,搜「yum install」;:249,搜「--no-install-recommends」;:260,搜「backslash」)。 ↩

  17. 出处:「Creating Docker images」第 267–314 段(text/53-fm-creating-docker-images.txt:277,搜「unpack TAR」;:281,搜「COPY . /app」;:299,搜「UID」;:306,搜「--chown」)。 ↩↩

  18. 出处:「Creating Docker images」第 316–341 段(text/53-fm-creating-docker-images.txt:317,搜「working directory」;:335,搜「persisted across layers」)。 ↩

  19. 出处:「Creating Docker images」第 343–357 段(text/53-fm-creating-docker-images.txt:344,搜「executed at the」;:349,搜「when a container is」)。 ↩

  20. 出处:「Creating Docker images」第 355–357 段(text/53-fm-creating-docker-images.txt:355,搜「most users don't fully understand」)。 ↩↩

  21. 出处:「Creating Docker images」第 358–392 段(text/53-fm-creating-docker-images.txt:430,搜「define the command」;:376,搜「ENTRYPOINT ["ping"]」;:379,搜「JSON array」;:384,搜「exec form」)。 ↩↩↩

  22. 出处:「Creating Docker images」第 402–428 段(text/53-fm-creating-docker-images.txt:404,搜「--rm」;:410,搜「override the CMD」;:417,搜「-w 5 127.0.0.1」;:422,搜「--entrypoint」)。 ↩↩

  23. 出处:「Creating Docker images」第 430–445 段(text/53-fm-creating-docker-images.txt:438,搜「/bin/sh -c」;:444,搜「main process」)。 ↩

  24. 出处:「Creating Docker images」第 479–554 段(text/53-fm-creating-docker-images.txt:504,搜「image build」;:506,搜「period」;:524,搜「Building 10.8s」;:530,搜「FROM docker.io」;:548,搜「df997d6c1fb」)。 ↩

  25. 出处:「Creating Docker images」第 447–477 段(text/53-fm-creating-docker-images.txt:453,搜「node:23-bookworm」;:459,搜「ENTRYPOINT ["npm"]」)。 ↩

  26. 出处:「Creating Docker images」第 559–621 段(text/53-fm-creating-docker-images.txt:618,搜「260 MB」;:620,搜「whole SDK」)。 ↩

  27. 出处:「Creating Docker images」第 622–663 段(text/53-fm-creating-docker-images.txt:627,搜「AS build」;:637,搜「FROM scratch」;:638,搜「--from=build」;:660,搜「136 KB」;:661,搜「smaller attack surface」)。 ↩↩↩

  28. 出处:「Creating Docker images」第 665–677 段(text/53-fm-creating-docker-images.txt:672,搜「ephemeral」;:674,搜「minimum of setup」)。 ↩

  29. 出处:「Creating Docker images」第 679–698 段(text/53-fm-creating-docker-images.txt:684,搜「only layers」;:685,搜「subsequent layers」)。 ↩↩

  30. 出处:「Creating Docker images」第 686–715 段(text/53-fm-creating-docker-images.txt:695,搜「npm install」;:699,搜「COPY command」;:707,搜「package.json」;:713,搜「rarely changes」)。 ↩↩↩

  31. 出处:「Creating Docker images」第 717–743 段(text/53-fm-creating-docker-images.txt:724,搜「each line」;:738,搜「&&」;:743,搜「single layer」)。 ↩↩

  32. 出处:「Creating Docker images」第 745–761 段(text/53-fm-creating-docker-images.txt:752,搜「.dockerignore」;:757,搜「unnecessary packages」;:759,搜「multi-stage builds」)。 ↩↩

  33. 出处:「Containerizing a legacy app using the lift and shift approach」第 1–31 段(text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:10,搜「image save」;:20,搜「image load」;:24,搜「Loaded image」)。 ↩

  34. 出处:「Containerizing a legacy app using the lift and shift approach」第 33–44 段(text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:36,搜「mission-critical」;:40,搜「Never touch a running system」)。 ↩

  35. 出处:「Containerizing a legacy app using the lift and shift approach」第 45–58 段(text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:45,搜「Modernize Traditional Apps」;:49,搜「without the need to change a single line」)。 ↩

  36. 出处:「Containerizing a legacy app using the lift and shift approach」第 61–73 段(text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:66,搜「database」;:69,搜「Enterprise Service Bus」;:72,搜「seams」)。 ↩

  37. 出处:「Containerizing a legacy app using the lift and shift approach」第 75–95 段(text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:88,搜「Maven」;:91,搜「MSBuild」;:93,搜「make would most likely」)。 ↩

  38. 出处:「Containerizing a legacy app using the lift and shift approach」第 97–114 段(text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:104,搜「Build time」;:107,搜「Environment」;:111,搜「Runtime」)。 ↩

  39. 出处:「Containerizing a legacy app using the lift and shift approach」第 116–139 段(text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:127,搜「hardcoding」;:130,搜「secret store」;:138,搜「HashiCorp's Vault」)。 ↩

  40. 出处:「Containerizing a legacy app using the lift and shift approach」第 141–147 段(text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:144,搜「one-shot」;:146,搜「long and ugly」)。 ↩↩

  41. 出处:「Containerizing a legacy app using the lift and shift approach」第 148–225 段(text/54-fm-containerizing-a-legacy-app-using-the-lift-and-s.txt:192,搜「never provide default values」;:201,搜「EXPOSE」;:218,搜「docker-entrypoint.sh」;:221,搜「chmod +x」)。 ↩

  42. 出处:「Containerizing a legacy app using the lift and shift approach」第 227–233 段(text/55-fm-sharing-or-shipping-images.txt:4,搜「DockerCon 2018」;:6,搜「50% saving」;:7,搜「90% reduction」)。 ↩↩

  43. 出处:「Sharing or shipping images」第 14–22 段(text/55-fm-sharing-or-shipping-images.txt:15,搜「globally unique」;:19,搜「image registries」)。 ↩

  44. 出处:「Sharing or shipping images」第 40–81 段(text/55-fm-sharing-or-shipping-images.txt:47,搜「fully qualified name」;:49,搜「registry URL」;:63,搜「ECR」;:85,搜「registry.acme.com」)。 ↩↩

  45. 出处:「Sharing or shipping images」第 90–123 段(text/55-fm-sharing-or-shipping-images.txt:91,搜「omit the registry URL」;:92,搜「omit the tag」;:93,搜「official image」)。 ↩↩

  46. 出处:「Sharing or shipping images」第 128–141 段(text/55-fm-sharing-or-shipping-images.txt:133,搜「curated」;:141,搜「scans all official images」)。 ↩

  47. 出处:「Supply chain security practices」第 7–31 段(text/56-fm-supply-chain-security-practices.txt:12,搜「docker image tag」;:14,搜「new reference」;:19,搜「docker login」;:25,搜「docker image push」;:30,搜「Mounted from library/alpine」)。 ↩↩↩

  48. 出处:「Supply chain security practices」第 33–37 段(text/56-fm-supply-chain-security-practices.txt:33,搜「repository can be private」;:37,搜「necessary permissions」)。 ↩

  49. 出处:「Summary」第 1–15 段(text/57-fm-summary.txt:2,搜「official and verified」;:4,搜「Trivy」;:6,搜「Docker Content Trust」;:9,搜「least privilege」;:11,搜「Regular updates」)。 ↩↩

  50. 出处:「Creating Docker images」第 183–184 段(text/53-fm-creating-docker-images.txt:183,搜「BuildKit has changed this」)。 ↩