结论:缓存按层判断:某条指令或其依赖的文件内容变化,该层及之后所有层全部重建。排查先看是哪一层失效(--progress=plain 输出每层的 CACHED 标记),优化手段围绕"让易变的东西尽量靠后、让依赖层尽量稳定"。

展开:优化清单按收益排序:1)依赖与源码分离——COPY package.json yarn.lock ./ + RUN yarn install 在前,COPY . . 在后,源码变动不触发依赖重装(Go 同理先拷 go.mod/go.sum);2).dockerignore 排掉 .git、node_modules、测试产物——构建上下文 hash 变化是缓存失效的头号元凶,很多人忽略上下文本身参与计算;3)BuildKit 缓存挂载——RUN --mount=type=cache,target=/root/.cache yarn install,包管理器缓存跨构建复用,即使层失效也不用重新下载;4)CI 远程缓存——--cache-from/--cache-to type=registry 把缓存层推到镜像仓库,多 runner 共享;5)合并 RUN 与多阶段构建减层(次要收益)。易错点:COPY . . 提前导致任何文件改动全量重建;ARG 声明在缓存层之前,ARG 值变化使后续全失效(构建参数放越晚越好);基础镜像 tag 更新(FROM node:20 指到新 digest)自然失效,属预期行为。

# syntax=docker/dockerfile:1
FROM node:20 AS deps
COPY package.json yarn.lock ./
RUN --mount=type=cache,target=/root/.yarn yarn install --frozen-lockfile
COPY . .
RUN yarn build

追问方向:BuildKit 的 LLB 图并行构建、ko/buildpacks 这类无 Dockerfile 方案在 CI 缓存上的表现。