多层镜像(Image)
RUN 命令中的续行符 \ 和层级的合并,那我们今天就彻底拆解一下 Docker 镜像的“洋葱结构”。
理解了多层镜像(Image Layers),你就会明白为什么有时候构建飞快,有时候又慢得像蜗牛。
1. 镜像的“叠罗汉”结构
Docker 镜像不是一个单一的巨大文件,而是由一系列只读层叠加而成的。每一行 Dockerfile 指令(FROM, COPY, RUN, ADD)都会创建一个新层。
想象你在做一个三明治:
- 第一层 (FROM):它是你的面包底(操作系统和基础环境)。
- 第二层 (RUN):你抹了一层黄油(创建了目录)。
- 第三层 (COPY):你放了一片生菜(拷入了代码)。
2. 核心特性:Copy-on-Write (写时复制)
这是 Docker 节省空间的神技。
- 所有层都是只读的:一旦镜像构建完成,里面的层就再也不能修改了。
- 容器层 (Writable Layer):当你运行容器时,Docker 会在最顶部加一个薄薄的“可读写层”。
- 修改文件:如果你在容器里修改了
/etc/nginx/nginx.conf,Docker 实际上是从底层的只读层把文件复制到顶部的读写层,然后修改它。底层的原始文件依然安然无恙。
3. 为什么我们要合并层级(使用 && 和 \)?
看下面两个例子,它们在功能上是一样的,但在存储上天差地远:
做法 A:产生三层 (Bad)
Dockerfile
RUN apt-get update
RUN apt-get install -y git
RUN rm -rf /var/lib/apt/lists/*- 问题:即使你在第三层删除了安装包,但在第一和第二层中,那些下载的临时文件已经永久地留在了镜像历史里,镜像体积依然很大。
做法 B:产生一层 (Good)
Dockerfile
RUN apt-get update && \
apt-get install -y git && \
rm -rf /var/lib/apt/lists/*- 优势:所有的下载、安装、清理都在同一层完成。当这一层“提交”时,临时文件已经被删除了,它们永远不会进入最终的镜像。
4. 缓存失效:构建速度的秘诀
Docker 在构建时会检查每一层是否有缓存。这是按顺序进行的。
- 缓存命中:如果你没改过 Dockerfile 的前三行,Docker 会直接跳过,显示
Using cache。 - 缓存失效:一旦某一层发生了变化,它之后的所有层缓存都会失效。
💡 实战优化技巧:
把最不常变动的指令放在前面,把最常变动(比如 COPY dist/)放在最后。
错误示例:
Dockerfile
COPY . . # 每次你改一行代码,这一层就变了
RUN npm install # 导致这一层每次都要重新下载几百MB依赖正确示例(你的脚本现在的逻辑): 你在本地 pnpm build,然后只 COPY dist/。因为 nginx:alpine 基础层永远不变,你的构建过程几乎秒开。
5. 如何查看你的镜像层级?
你可以直接在终端输入这个命令,看看你的 admin-web-project 是怎么叠出来的:
Bash
docker history admin-web-project你会看到类似这样的输出:
IMAGE: 每一层的 ID。CREATED BY: 产生了这一层的 Dockerfile 指令。SIZE: 这一层给镜像增加了多少重量。
🛠️ 总结
- 分层是为了复用:如果两个镜像都基于
nginx:alpine,它们会共用同一套底座,节省磁盘。 - 合并是为了精简:在同一个
RUN里完成“下载 -> 安装 -> 清理”,避免镜像虚胖。 - 排序是为了效率:让变动频繁的指令靠后,最大化利用缓存。