编码代理与终端 · 收录于 2026-08-29 · 适用 1 工具 ✓ 实测验证

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

已验证 2026-08-29Docker build 缓存不生效是 COPY 顺序问题——依赖清单(package.json 等)先 COPY 再 RUN 安装,全量 COPY 放最后。

快速修复

# 一键:把依赖安装挪到 COPY 之前(npm 例:先 COPY 依赖清单,再 RUN 安装,最后 COPY 全量)
# Dockerfile 里:COPY package.json package-lock.json ./ && RUN npm ci 放在 COPY . /app 之前

pip 同理:COPY requirements.txt ./ → RUN pip install -r requirements.txt;go:COPY go.mod go.sum ./ → RUN go mod download。

你会遇到什么

只改了一个业务文件,npm install / pip install 整层却每次都重跑:

Step 3/4 : COPY . /app
Step 4/4 : RUN npm install        # 每次构建都执行,几分钟的重装

构建越来越慢,CI 上表现更明显(每次都全量重装)。

本文路径:快速修复(30 秒一段命令)→ 一步步排查(可跳读)→ 原理与变体(症状/原因/证据)。赶时间直接跳到「快速修复」。

为什么会这样

Docker 构建缓存的匹配是"父链 + 指令 + 输入":

  • 每一层缓存的 key 包含父层 ID、指令和该指令的输入(COPY/ADD 的校验和、RUN 的命令串);
  • COPY . . 把整个构建上下文都算进输入——改一个文件 = COPY 层校验和变化 = 该层及之后所有层缓存全部失效;
  • RUN npm install 本身命令串没变,但它的父层(COPY 层)变了,所以也无法命中缓存;
  • 构建上下文里塞了不该塞的东西(node_modules、.git、缓存目录)会让 COPY 层频繁失效。

本质:依赖安装层被"不应该影响它"的业务文件变更拖下水。修复方向 = 让 COPY 层只包含安装层真正需要的输入(依赖清单),并把安装层放在前面。

缓存失效的传导路径是「输入变化 → 层校验和变化 → 父链断裂」:Docker 对每一层只记录父层 ID、指令和输入摘要,COPY . . 的输入是整个构建上下文,业务文件一动 COPY 层摘要就变;RUN npm install 的缓存 key 里带着父层 ID,父层一变它跟着失效——缓存不是「命令相同就复用」,是「整条父链逐层校验」。

三个确定性推论:①依赖安装层必须放在 COPY 业务代码之前,让它只依赖依赖清单(package.json/lock)的摘要;②.dockerignore 收紧上下文 = 减少无关文件抖动摘要的概率,也减小上传体积;③调试时看 ---> Using cache 首次消失的位置,那处就是父链断裂点,往它前面找输入变化。

一步步来

1. 把依赖安装挪到 COPY 之前 — 必做

COPY package.json package-lock.json ./
RUN npm ci
COPY . /app

只有依赖清单变化才会重跑安装层,改业务代码不再触发。

2. 用 .dockerignore 收紧上下文 — 必做

排除 node_modules/.git/构建产物/缓存目录等,否则 COPY . . 的校验和会随无关文件抖动(也减小上传体积)。

3. 调试时看缓存标记 — 必做

build 输出里 ---> Using cache 出现的位置就是命中的层;改文件后如果 COPY 层没有 Using cache,它之后的所有层都会重跑——顺着输出往前找第一处失效点。

4. 需要跨层共享的缓存(BuildKit) — 必做

RUN --mount=type=cache,target=/root/.npm npm ci

把包管理器缓存挂成独立缓存卷,网络层命中率更高。

5. CI 里用 --cache-from — 必做

推送远端镜像做缓存源(buildx --cache-from),配合"安装前置"结构效果最好;顺序反了的话 --cache-from 也救不了频繁失效的安装层。

怎么确认修好了

改业务文件后安装层不再重跑(实测输出形态):

再次改 app.cfg 重建 → Step 3/4 RUN -- Using cache(安装层不重跑,修复生效)

修复前:改文件后 COPY 与 RUN 均无 Using cache(症状复现)。

其它环境的表现

环境说明
npmCOPY package.json package-lock.json ./ → RUN npm ci
pipCOPY requirements.txt ./ → RUN pip install -r requirements.txt
goCOPY go.mod go.sum ./ → RUN go mod download
构建上下文.dockerignore 排除 node_modules/.git/缓存
BuildKit--mount=type=cache 独立缓存卷
CI--cache-from + 安装前置结构

症状

构建镜像时缓存"总是失效":

# 只改了一个业务文件,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。
  1. 用 .dockerignore 收紧上下文:排除 node_modules/.git/构建产物/缓存目录等,

否则 COPY . . 的校验和会随无关文件抖动(也减小上传体积)。

  1. 调试时看缓存标记:build 输出里 ---> Using cache 出现的位置就是命中的层;

改文件后如果 COPY 层没有 Using cache,它之后的所有层都会重跑——顺着输出往前找第一处失效点。

  1. 需要跨层共享的缓存(BuildKit):RUN --mount=type=cache,target=/root/.npm 等,

把包管理器缓存挂成独立缓存卷,装依赖时网络层命中率更高。

  1. 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(COPY 不检测更新/缓存不生效)+ farscapian/lnplay#35(COPY 全部源码在 RUN yarn 之上导致安装层失效)同款。
  • 实测复现日志(2026-08-29)

相关 canonical

按「前置 → 同根 / 同域 / 相关」读:

适用工具 (1)

本问题方案已挂到下列工具的方案页——从工具页也能回到这里,双向可达。

docker · 外部工具

成套方案 (组合包)

这个问题包含在下列成套安装方案中。

来源与证据

收录 验证 ✓ 实测验证适用 1 工具
方案含本地实测或来源可复核的解法;未实测项已在文中如实标注。

素材来源:https://stackoverflow.com/questions/43073137/why-is-copy-in-docker-build-not-detecting-updates

← 编码代理与终端 问题域 | 问题货架 | 遇到其它问题?去 问题空间 提交求助卡