Problem entry · machine-readable · 编码代理与终端

Docker build 缓存不生效(每次改代码都重跑安装层)—— COPY 顺序与 .dockerignore

本页嵌入 #trove-entity JSON(本条目全量结构化数据);全库数据见 /agent/trove.json。

元数据

iddocker-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)