大幅提升缓存命中率的核心是将稳定指令(如拷贝依赖文件、安装依赖)前置,易变指令(如拷贝源码)后置,并通过多阶段构建隔离编译与运行环境,确保依赖层不因代码变更而重建。
大幅提升 docker 镜像层缓存命中率,核心是让 docker 在构建时尽可能复用已有层——关键不在“多做”,而在“少变”和“顺序对”。只要某一层变了,它后面所有层都会重建。所以优化本质是:把稳定的内容放前面,把易变的内容放后面,并切断它们之间的依赖链。
把依赖文件拷贝前置,和源码严格分离
这是最直接有效的操作。Node.js、Go、Python 等项目都适用。
- 先 COPY package.json / go.mod / requirements.txt 这类声明依赖的文件,再 RUN 安装依赖
- 等依赖安装完成,再 COPY 源码(src/、.go 文件等)
- 这样只要没改依赖声明,
RUN npm install或go mod download就能稳定命中缓存
按变动频率排序 Dockerfile 指令
Docker 缓存是线性的、单向的。每一层是否命中,只取决于它自身指令 + 上一层镜像内容是否完全一致。
- 基础镜像(FROM)要固定标签,比如用
golang:1.21-alpine而不是golang:latest - ENV、WORKDIR 这类轻量且稳定的指令尽量靠前
- COPY 操作按文件稳定性排序:配置文件 > 依赖文件 > 构建脚本 > 应用源码
- 避免在 COPY 源码之后再执行任何 RUN 命令(如 chmod、chown),否则一改代码就全重来
用好多阶段构建,隔离编译与运行环境
多阶段构建不只是为了减小体积,更是缓存解耦的关键手段。
- 第一阶段(builder)专注编译:COPY 依赖文件 → 下载 → COPY 源码 → 构建二进制
- 第二阶段(runtime)只 COPY 第一阶段产出的二进制,不带任何构建工具和源码
- 这样即使源码天天改,只要 go.mod 不变,builder 阶段的依赖下载层依然命中缓存
控制构建上下文,减少隐性变更
缓存是否失效,只看 COPY/ADD 显式引入的文件内容哈希。但很多“看似无关”的改动会悄悄污染上下文。
- 用
.dockerignore排除node_modules/、dist/、.git/、logs/等无关目录 - 避免在构建时动态生成文件(如写入 build-time timestamp),这类内容每次都不一样,会让 COPY 层失效
- CI 中确保每次构建传入的上下文一致,不要混入本地调试文件或临时产物


















