目录
Docker 构建缓存教程:掌握缓存规则并精简镜像
同一个 Dockerfile,写法不同,构建时间可能从不到 1 秒变成好几分钟,镜像大小也可能相差很多。本节通过实验说明缓存规则和精简技巧。
缓存如何工作
docker build 会依次处理每条指令。对于每一行,Docker 都会检查:这条指令以及它之前的所有层,是否与上次构建时相同?
- 相同:直接使用缓存,输出会显示
CACHED。 - 不同:重新执行这一行,而且之后的每一行都不再使用缓存。
| 指令 | 判断依据 |
|---|---|
RUN | 指令字符串是否相同(不会检查网络上的软件包是否已更新) |
COPY / ADD | 被复制的文件内容是否相同 |
| 其他 | 指令和参数是否相同 |
实验:指令顺序的影响
使用一个依赖 Express 的小项目(package.json 中有 express 包),比较两种 Dockerfile。
错误的顺序(Dockerfile.bad):
FROM node:24-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
正确的顺序(Dockerfile.good):
FROM node:24-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]
错误的顺序:修改一行代码
先构建一次,然后只修改 server.js 中的一个字,再次构建:
$ docker build -f Dockerfile.bad -t cache-bad .
[+] Building 6.9s (9/9) FINISHED docker:default
=> [internal] load build definition from Dockerfile.bad 0.0s
=> => transferring dockerfile: 125B 0.0s
=> [internal] load metadata for docker.io/library/node:24-alpine 0.2s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 65B 0.0s
=> [1/4] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 280B 0.0s
=> CACHED [2/4] WORKDIR /app 0.0s
=> [3/4] COPY . . 0.1s
=> [4/4] RUN npm install 5.4s
=> exporting to image 1.0s
=> => exporting layers 0.5s
=> => exporting manifest sha256:d27475f082a53018e303541bc1c0691ac25526e87761300e2aa8ec76d73e 0.0s
=> => exporting config sha256:19f2024df4329a5f2af23e50d8c18826f073d425e18133e9571c393999d913 0.0s
=> => exporting attestation manifest sha256:58b4730e9566f2ec7e8222cd41c14ea971d87272b6b0c190 0.0s
=> => exporting manifest list sha256:58b8fc79124f2fe21270801d5e40f09839c06ba5203304423125270 0.0s
=> => naming to docker.io/library/cache-bad:latest 0.0s
=> => unpacking to docker.io/library/cache-bad:latest 0.4s
[3/4] COPY . . 因为 server.js 发生变化而重新执行,后面的 [4/4] RUN npm install 也随之重跑,花了 5.4 秒重新下载并安装所有包。
正确的顺序:修改一行代码
第一次构建(需要执行 npm install):
$ docker build -f Dockerfile.good -t cache-good .
[+] Building 5.7s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile.good 0.0s
=> => transferring dockerfile: 147B 0.0s
=> [internal] load metadata for docker.io/library/node:24-alpine 0.1s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 65B 0.0s
=> [1/5] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 95B 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> [3/5] COPY package.json ./ 0.0s
=> [4/5] RUN npm install 4.4s
=> [5/5] COPY . . 0.1s
=> exporting to image 0.9s
=> => exporting layers 0.5s
=> => exporting manifest sha256:4b36d92a774b02cc1a188193e178a445b90f330b19f2dcc741d631e246e8 0.0s
=> => exporting config sha256:4a597aa667d9ba52eab36c4483f7507bbcae158b1b22af5ca6e4ac5b7020fa 0.0s
=> => exporting attestation manifest sha256:efea2ef65e2672718391b6419b9f5f2be50396163032a29e 0.0s
=> => exporting manifest list sha256:236918100fd2092d0388da5c03b32dfa3abaa2e62304abb3e3dd335 0.0s
=> => naming to docker.io/library/cache-good:latest 0.0s
=> => unpacking to docker.io/library/cache-good:latest 0.4s
修改 server.js 后再次构建:
$ docker build -f Dockerfile.good -t cache-good .
[+] Building 0.4s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile.good 0.0s
=> => transferring dockerfile: 147B 0.0s
=> [internal] load metadata for docker.io/library/node:24-alpine 0.1s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 65B 0.0s
=> [1/5] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 280B 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> CACHED [3/5] COPY package.json ./ 0.0s
=> CACHED [4/5] RUN npm install 0.0s
=> [5/5] COPY . . 0.0s
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => exporting manifest sha256:6cd8c2671a295aab85724aefe72f8dc749f4d332224f5d534fff5e4e57fa 0.0s
=> => exporting config sha256:7df3d574ae91f98ea3634c4dcede63e64ce8018c902643c2db08c2b4dee4c3 0.0s
=> => exporting attestation manifest sha256:fc9ff7e57f4187c04b74446f5dd96e46ef9c7f0ea88372a6 0.0s
=> => exporting manifest list sha256:79e8a6d76ff2382f02bcadc183a44670959d1d0a30467a27b9d619e 0.0s
=> => naming to docker.io/library/cache-good:latest 0.0s
=> => unpacking to docker.io/library/cache-good:latest 0.0s
整个构建只花了 0.4 秒:COPY package.json、RUN npm install 都显示 CACHED,只有最后的 COPY . . 重新执行。这个项目只有一个依赖;真实项目动辄包含几百个包,npm install 可能要好几分钟,差距会更大。
同样的道理适用于所有语言:
| 语言 | 先复制 | 再安装 |
|---|---|---|
| Node.js | package.json、package-lock.json | npm ci |
| Python | requirements.txt 或 pyproject.toml | pip install |
| Go | go.mod、go.sum | go mod download |
| Java (Maven) | pom.xml | mvn dependency:go-offline |
| PHP | composer.json、composer.lock | composer install |
实验:在同一层中清理临时文件
每一层都会叠加在前一层之上。在后面的 RUN 中删除文件,并不会减少前一层的大小。比较安装 curl 的两种写法:
Dockerfile.separate:
FROM debian:trixie-slim
RUN apt-get update
RUN apt-get install -y --no-install-recommends curl
RUN rm -rf /var/lib/apt/lists/*
Dockerfile.same:
FROM debian:trixie-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
构建后比较大小:
$ docker images --filter reference=apt-separate --filter reference=apt-same --filter reference=debian:trixie-slim
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
apt-same:latest 5357e9775c1e 136MB 34.9MB
apt-separate:latest ad6c0a37d9ea 174MB 51.7MB
内容完全相同(都安装了 curl,也都删除了软件包列表),但 apt-separate 却大了 38 MB。用 docker history 查看原因:
$ docker history apt-separate
IMAGE CREATED CREATED BY SIZE COMMENT
ad6c0a37d9ea 11 seconds ago RUN /bin/sh -c rm -rf /var/lib/apt/lists/* #… 20.5kB buildkit.dockerfile.v0
<missing> 11 seconds ago RUN /bin/sh -c apt-get install -y --no-insta… 13.4MB buildkit.dockerfile.v0
<missing> 16 seconds ago RUN /bin/sh -c apt-get update # buildkit 21.6MB buildkit.dockerfile.v0
<missing> 8 days ago # debian.sh --arch 'amd64' out/ 'trixie' '@1… 87.7MB debuerreotype 0.17
$ docker history apt-same
IMAGE CREATED CREATED BY SIZE COMMENT
5357e9775c1e 1 second ago RUN /bin/sh -c apt-get update && apt-get… 13.4MB buildkit.dockerfile.v0
<missing> 8 days ago # debian.sh --arch 'amd64' out/ 'trixie' '@1… 87.7MB debuerreotype 0.17
apt-separate 的 apt-get update 层有 21.6 MB(下载的软件包列表),后面的 rm -rf 只是在新的一层中将其“标记为删除”,原来的 21.6 MB 仍留在镜像里。把命令写在同一个 RUN 中,软件包列表会在这一层结束前删除,从未存入任何一层。
各种语言常用的“不留临时文件”写法:
RUN pip install --no-cache-dir -r requirements.txt
RUN npm ci && npm cache clean --force
RUN apk add --no-cache curl
.dockerignore
构建开始时,Docker 会把整个构建上下文(通常是当前文件夹)发送给构建引擎。没有排除 node_modules 时,Dockerfile 入门中的示例需要传送 30 MB,排除后只有 159 B。.dockerignore 示例:
.git
node_modules
dist
*.log
.env
.env.*
Dockerfile*
.dockerignore
这样不仅能加快构建,也能避免这些文件的变化导致 COPY . . 缓存失效,并防止 .env、私钥被打包进镜像。
选择精简的基础镜像
镜像一节实测的 Python 3.14 镜像大小:
| 基础镜像 | 硬盘空间 |
|---|---|
python:3.14 | 1.63 GB |
python:3.14-slim | 180 MB |
python:3.14-alpine | 82.6 MB |
多阶段构建
编译工具和开发包只在构建时需要,不应留在最终镜像中。多阶段构建是最有效的精简方式。
缓存挂载:加快软件包下载
BuildKit 支持缓存挂载,让包管理工具的下载缓存可以在多次构建之间保留。即使 RUN npm ci 这一层的缓存失效,也不必从网络重新下载所有包:
# syntax=docker/dockerfile:1
FROM node:24-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
缓存挂载的内容不会进入镜像,因此不会增加镜像大小。
强制不使用缓存
需要获取软件包的最新版本,或怀疑缓存有问题时:
$ docker build --no-cache -f Dockerfile.good -t cache-good .
[+] Building 6.6s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile.good 0.0s
=> => transferring dockerfile: 147B 0.0s
=> [internal] load metadata for docker.io/library/node:24-alpine 0.3s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 65B 0.0s
=> [1/5] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 280B 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> [3/5] COPY package.json ./ 0.0s
=> [4/5] RUN npm install 5.1s
=> [5/5] COPY . . 0.1s
=> exporting to image 1.0s
=> => exporting layers 0.4s
=> => exporting manifest sha256:30688d5c22069d97e9db0bd32d5939e8e99fda0c6a0c4af1f32fb5baacc9 0.0s
=> => exporting config sha256:e8e75108925e8f145e7367b8e36350a8175c3776154f560fb47a9fb287fe21 0.0s
=> => exporting attestation manifest sha256:adbbb2fc6683c9a35c67bb9237cf3afcfd24c39dd9aa4b55 0.0s
=> => exporting manifest list sha256:6b92b5704d4fa7a3f11cfda5ffde00e6516bdbf358e23dbece712a2 0.0s
=> => naming to docker.io/library/cache-good:latest 0.0s
=> => unpacking to docker.io/library/cache-good:latest 0.5s
--no-cache 会让每一步都重新执行(没有任何 CACHED)。加上 --pull 还会重新检查基础镜像是否有新版本。
查看和清理构建缓存
构建缓存会不断累积:
$ docker builder du
ID RECLAIMABLE SIZE LAST ACCESSED
mr6xd3ylof28pr8obxiet3fo8 true 403B 3 minutes ago
l34l5occgcgh8bscivg2iusrn true 626B 3 minutes ago
... (中间省略)
Shared: 488.2MB
Private: 118.5MB
Reclaimable: 606.7MB
Total: 606.7MB
$ docker builder prune -f
ID RECLAIMABLE SIZE LAST ACCESSED
xqb9jtg0kmq0mdzb0vrmod68j true 20.67kB 3 minutes ago
u1pbndrekyh1q91qbb4n9e3fy* true 8.192kB 5 minutes ago
... (中间省略)
Total: 118.5MB
docker builder prune 只会删除构建缓存,不会影响镜像和容器。清理所有 Docker 数据的方法见清理硬盘空间。
重点回顾
- 某一层发生变化后,它后面的所有层都要重新构建。
- 把不常变化的步骤(安装软件包)放在前面,常变化的步骤(复制代码)放在后面。
- 在同一个
RUN中下载、安装并清理临时文件,临时文件才不会留在镜像中。 RUN --mount=type=cache可以让软件包下载缓存供多次构建共用。
动手练习
练习 1:下面两个 Dockerfile 片段,哪一个生成的镜像更小?为什么?
# A
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# B
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
答案
B 更小。A 的 rm 在新的一层中执行,软件包列表仍留在前面的层中;B 在同一层中下载并删除,列表没有被保存下来。
练习 2:对同一个项目分别使用 docker build --no-cache 和不带参数的命令构建一次,比较 [+] Building 显示的总时间。
提示
不带参数时,大多数步骤会显示 CACHED,通常只需零点几秒;--no-cache 会重新执行每一步,耗时取决于安装软件包的速度。
常见问题
RUN apt-get update 被缓存,安装到旧版软件包怎么办?
缓存只比较指令字符串,不会检查网络上的软件包是否已更新。把 apt-get update 和 apt-get install 写在同一个 RUN 中;需要时使用 docker build --no-cache 强制重新构建。
构建缓存占用很多硬盘空间?
使用 docker builder du 查看,用 docker builder prune 清理;实测见[清理硬盘空间](/zh/docker/cleanup/)。
在 CI 中每次都从头构建很慢?
使用外部缓存,例如 GitHub Actions 的 cache-from: type=gha,见[GitHub Actions 自动构建](/zh/docker/ci-cd/)。
更新于 2026年10月14日