核心是将变动少的指令前置、变动多的后置,以固定缓存“锚点”;from和系统依赖应最先写并合并清理,应用依赖单独copy后安装,源码最后复制,配合.dockerignore、多阶段构建及buildkit提升缓存命中率。

核心是让变动少的指令靠前、变动多的靠后,把缓存“锚点”固定在稳定内容上。只要某一层没变,它和它之前的所有层都能复用,后续层也才可能继续命中。
按稳定性分层排列指令
Docker 每条指令生成一层,缓存是否生效取决于该层及其所有父层是否完全一致。所以顺序不是随意的,而是要匹配文件变更频率:
-
最顶层放 FROM:基础镜像更新不频繁,但一旦变,全链失效,所以必须最先写且选长期维护的 tag(如
python:3.11-slim-bookworm而非latest) -
紧接系统依赖安装:比如
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*,这类操作极少改动,应合并成单条 RUN 并清理临时文件 -
再放应用依赖安装:先
COPY requirements.txt .,再RUN pip install -r requirements.txt。只要requirements.txt不变,这步永远走缓存 -
最后复制源码:用
COPY . .或更精细的COPY src/ ./src/,确保代码修改只影响最后一两层
精准控制 COPY 的粒度与范围
COPY 触发缓存的依据是文件内容哈希,哪怕改一个空格也会让该层及之后全部重建。因此不能图省事一股脑复制整个目录:
- 用
.dockerignore排除.git、__pycache__、node_modules、.env等无关文件,避免它们悄悄改变哈希值 - Node.js 项目中,分两次 COPY:
COPY package.json package-lock.json ./→RUN npm ci --only=production→COPY src/ ./src/ - Go 项目中,先
COPY go.mod go.sum ./→RUN go mod download,比直接COPY . .后再go build高效得多
启用 BuildKit 并配合缓存源
默认构建器对多阶段和远程缓存支持有限,BuildKit 能显著提升命中率和并发能力:
- 构建时加环境变量:
DOCKER_BUILDKIT=1 docker build --progress=plain -t app . - CI 中可指定缓存来源:
--cache-from type=registry,ref=your-registry/app:build-cache,让不同分支或 PR 复用彼此的中间层 - 多阶段构建中,给 builder 阶段命名(如
FROM golang:1.21 AS builder),再用COPY --from=builder提取产物,运行阶段完全不参与构建缓存竞争
验证与调试缓存行为
光写对还不够,得看得见效果:
- 构建日志里出现
Using cache表示该层复用成功;出现Step X/X : ...且无 “Using cache” 提示,说明从这步开始重建 - 临时禁用缓存对比耗时:
docker build --no-cache -t test .,确认优化前后真实收益 - 用
docker image history <image-id></image-id>查看每层大小和创建命令,识别臃肿或重复的层











