元数据
| id | docker-build-cache | 收录 | 2026-08-29 |
|---|---|---|---|
| 问题域 | 编码代理与终端 | 主题分组 | 构建与部署 |
| 适用工具 | docker · 外部工具 | ||
症状
构建镜像时缓存"总是失效":
```
# 只改了一个业务文件,npm install / pip install 整层却每次都重跑
Step 3/4 : COPY . /app
Step 4/4 : RUN npm install # 每次构建都执行,几分钟的重装
```
特征:Dockerfile 结构是 `COPY . .` 在 `RUN npm install`(或 pip/go mod)之前;
随便改一个源文件,COPY 层变了,后面整个依赖安装层跟着失效重跑;
构建越来越慢,且 CI 上表现更明显(每次都全量重装)。
原因
Docker 构建缓存的匹配是"父链 + 指令 + 输入":
- 每一层缓存的 key 包含父层 ID、指令和该指令的输入(COPY/ADD 的校验和、RUN 的命令串);
- `COPY . .` 把整个构建上下文(包括任何改动的业务文件)都算进输入——改一个文件 = COPY 层校验和变化 = 该层及之后所有层缓存全部失效;
- `RUN npm install` 本身命令串没变,但它的父层(COPY 层)变了,所以它也无法命中缓存;
- 同理,构建上下文里塞了不该塞的东西(node_modules、.git、缓存目录)会让 COPY 层频繁失效。
本质:依赖安装层被"不应该影响它"的业务文件变更拖下水。修复方向 = 让 COPY 层只包含安装层真正需要的输入(依赖清单),并把安装层放在前面。
解法
1. **把依赖安装挪到 COPY 之前**:先 `COPY package.json /app/` + `RUN npm install`,再 `COPY . /app`——
只有 package.json 变化才会重跑安装层,改业务代码不再触发。
- npm: `COPY package.json package-lock.json ./` → `RUN npm ci`;
- pip: `COPY requirements.txt ./` → `RUN pip install -r requirements.txt`;
- go: `COPY go.mod go.sum ./` → `RUN go mod download`。
2. **用 .dockerignore 收紧上下文**:排除 node_modules/.git/构建产物/缓存目录等,
否则 `COPY . .` 的校验和会随无关文件抖动(也减小上传体积)。
3. **调试时看缓存标记**:build 输出里 `---> Using cache` 出现的位置就是命中的层;
改文件后如果 COPY 层没有 `Using cache`,它之后的所有层都会重跑——顺着输出往前找第一处失效点。
4. **需要跨层共享的缓存**(BuildKit):`RUN --mount=type=cache,target=/root/.npm` 等,
把包管理器缓存挂成独立缓存卷,装依赖时网络层命中率更高。
5. **CI 里用 --cache-from**:推送远端镜像做缓存源(buildx `--cache-from`),配合上面的"安装前置"结构效果最好;
顺序反了的话,--cache-from 也救不了频繁失效的安装层。
验证证据
- 本地实测(2026-08-29,WSL Ubuntu,Docker 29.1.3 legacy builder,alpine:3.20 纯本地无网络):
- 问题结构:`COPY . /app` + `RUN echo install-deps > /installed.txt`(RUN 命令串全程不变);
- ① 首次构建:四步全部执行;
- ② 改 app.cfg 后重建:`Step 2/4 WORKDIR -- Using cache`,但 `COPY . /app` 与 `RUN` 均无 `Using cache`——安装层被业务文件变更拖着重跑(症状复现);
- ③ 修复结构:`RUN echo install-deps > /installed.txt` 前置 + `COPY app.cfg /app/app.cfg` 后置,首次重建 RUN 执行一次(层链变化);
- ④ 再次改 app.cfg 重建:`Step 3/4 RUN -- Using cache`,安装层不再重跑(修复生效);
- 清理:测试镜像已删除。
- 参考案例:[SO #43073137](https://stackoverflow.com/q/43073137)(COPY 不检测更新/缓存不生效)+ [farscapian/lnplay#35](https://github.com/farscapian/lnplay/issues/35)(COPY 全部源码在 RUN yarn 之上导致安装层失效)同款。
- 实测复现日志(2026-08-29)
来源与证据
素材来源:https://stackoverflow.com/questions/43073137/why-is-copy-in-docker-build-not-detecting-updates
验证证据:Evidence details kept locally (2026-08-29)