核心思路是让ci每次构建尽可能复用已有层,避免重复下载、安装和编译;关键在于将不变内容(如基础镜像、依赖文件)前置,使依赖安装等步骤稳定命中缓存,再通过多阶段构建分离构建与运行环境,并配合.dockerignore和显式缓存配置提升复用率。

核心思路是让CI每次构建尽可能复用已有层,避免重复下载、安装和编译。关键不在“快”,而在“不重做”。
把不变的指令放在Dockerfile最前面
基础镜像、系统依赖、语言运行时这些极少改动的内容,必须优先声明。比如先 COPY requirements.txt 再 RUN pip install,而不是先把整个代码目录拷进去。这样只要依赖没变,安装这一步就永远走缓存,哪怕你改了100行代码也不会触发重装。
- 推荐顺序:FROM → ENV → COPY 依赖文件(如 package.json / requirements.txt / environment.yml)→ RUN 安装依赖 → COPY 其余源码 → 构建/启动命令
- 避免在早期层 COPY . 或 src/,否则任何代码微调都会让后续所有层失效
用多阶段构建隔离构建与运行环境
CI里真正耗时的是编译、测试、打包环节,而这些工具链(如 Go 编译器、Node.js 构建工具、Python 测试套件)完全不需要出现在最终镜像里。多阶段构建能让你把构建产物单独拎出来,只把可执行文件或静态资源复制到精简的运行镜像中。
- 构建阶段可以复用缓存:go mod download、npm install、mamba env create 等操作只要依赖文件不变,就无需重复执行
- 运行阶段从零开始,但体积小、拉取快、启动快,CI部署验证也更轻量
- 示例中 builder 阶段的依赖下载层,即使应用代码每天更新十次,它依然命中缓存
在CI中主动管理缓存来源
默认情况下,每个CI job都是“干净”的,缓存是孤立的。要打破这个限制,就得显式告诉 Docker 去哪里找缓存。
- 本地CI(如自建Runner):用 --cache-to type=local,dest=./cache 和 --cache-from type=local,src=./cache 挂载同一目录,实现job间缓存延续
- 云端CI(如GitHub Actions):配合 --cache-to type=gha 或推送到私有registry(--cache-to type=registry,ref=your-registry/app:cache),让不同分支、不同PR共享基础层
- 务必配置 --cache-from,哪怕只加载一次上一轮成功构建的镜像,也能显著提升首层命中率
控制构建上下文,减少无效传输
Docker build 开始前会把整个上下文(当前目录及子目录)打包发给守护进程。如果里面塞着 node_modules、.git、logs、dist 这些CI根本用不上的东西,光传输就浪费几十秒。
- 写好 .dockerignore:至少排除
node_modules、.git、__pycache__、*.log、dist/、build/ - 在CI脚本里确认构建命令是否指向最小必要路径,比如
docker build -f ./Dockerfile .而不是docker build -f ./Dockerfile .. - 上下文越小,传输越快,缓存计算(尤其是 COPY 的文件哈希)也越轻量











