Skip to content

多层镜像(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: 这一层给镜像增加了多少重量。

🛠️ 总结

  1. 分层是为了复用:如果两个镜像都基于 nginx:alpine,它们会共用同一套底座,节省磁盘。
  2. 合并是为了精简:在同一个 RUN 里完成“下载 -> 安装 -> 清理”,避免镜像虚胖。
  3. 排序是为了效率:让变动频繁的指令靠后,最大化利用缓存。