Docker层缓存的核心是让大部分步骤“不重做”,需将不变内容(如基础镜像、依赖声明文件)前置,分离变与不变,结合多阶段构建、显式缓存配置及.dockerignore控制上下文,确保CI中缓存有效流转。

核心不是让构建“更快”,而是让大部分步骤“不重做”。Docker 层级缓存生效的前提,是某一层的输入内容(指令+上下文)完全没变。CI 环境默认干净,必须主动设计、显式配置,缓存才能真正流转起来。
把不变的放前面,让依赖安装稳稳命中
缓存失效往往始于一行代码修改就触发整个依赖重装。关键在于分离“变”与“不变”:
- 先 COPY package.json / requirements.txt / go.mod 等声明依赖的文件,再 RUN 安装 —— 这样只要锁文件没改,安装层永远走缓存
- 把基础镜像(如 python:3.11-slim)、系统工具(apt-get install -y curl)、语言运行时这些极少变动的内容,全部放在 Dockerfile 顶部
- 避免在早期 COPY . 或 src/,否则任意源码改动都会让后续所有层失效
用好多阶段构建,隔离编译与运行环境
CI 中最耗时的是构建、测试、打包,但这些工具链(Node.js、Go 编译器、Python 测试套件)根本不需要进最终镜像:
- builder 阶段专注下载依赖、编译产物,它的缓存独立且稳定 —— 即使每天改十次代码,go mod download 或 npm ci 仍可复用
- runtime 阶段从精简镜像(如 alpine 或 distroless)开始,只 COPY 构建好的二进制或 dist 文件
- 两个阶段共享同一基础镜像版本,但互不干扰缓存,避免因运行阶段变化污染构建阶段的缓存
CI 中必须显式拉取并指定缓存来源
本地开发能自动继承历史层,但 CI job 是孤立容器,默认没有缓存。不配置,等于放弃 90% 的优化收益:
- 执行构建前,先 docker pull $REGISTRY/$IMAGE:cache(失败可忽略)
- 构建命令带上 --cache-from $REGISTRY/$IMAGE:cache,最好再加一个 --cache-from $REGISTRY/$IMAGE:latest 作兜底
- 云端 CI(如 GitHub Actions)推荐用 --cache-to type=gha;自建 Runner 可挂载本地目录 --cache-to type=local,dest=./cache
控制上下文体积,减少无效传输和哈希计算
Docker build 开始前会打包整个构建上下文发送给守护进程。塞进无关文件,既拖慢传输,又可能意外触发缓存失效:
- 用 .dockerignore 排除 node_modules、.git、logs、dist、*.log、*.tmp 等 CI 完全不用的目录和文件
- 避免在 RUN 指令中使用动态值(如 date +%s)、未锁定的包版本(如 apt-get install curl 应写成 curl=7.88.1-10+deb12u5)
- 基础镜像标签务必固定,例如用 debian:12.5-slim 而非 debian:latest


















