缓存失效规则很苛刻:某一层失效,它之后所有层全部重建。而 COPY . . 这种把全部源码塞一层的写法,任何文件变动都让这层失效——如果依赖安装写在它后面,每次构建都重装依赖。标准解法是依赖清单和代码分离:Java 先 COPY pom.xml 再 mvn dependency:go-offline 再 COPY src;Node 先 COPY package*.json 再 npm ci 再 COPY .;Python 先 COPY requirements.txt 再 pip install 再 COPY .。改代码不动依赖清单时,依赖层直接命中缓存。
指令顺序按变化频率从低到高排:基础镜像(几乎不变)→ 系统依赖(很少变)→ 应用依赖(偶尔变)→ 源码(天天变)→ 构建命令。RUN apt-get update 和 install 必须同一层(分开的话 update 层缓存命中,装的是过期索引)。
BuildKit 进阶(DOCKER_BUILDKIT=1 默认开):RUN --mount=type=cache,target=/root/.m2 把 maven/npm/pip 缓存目录挂成跨构建持久缓存,即使依赖层失效也只下增量,这招对 monorepo 里依赖频繁变的项目效果拔群。CI 场景配合 --cache-from 拉上一次构建的镜像做缓存源(镜像仓库要支持 manifest 缓存格式)。验证缓存命中:构建输出里每层显示 CACHED 就是命中,盯着第一个非 CACHED 层优化它的位置。