关键在于分层缓存优化:先copy依赖清单(如package.json、yarn.lock),再run安装依赖,最后copy源码;配合.dockerignore排除干扰项,并固定基础镜像版本标签,确保构建缓存高效复用。
关键在于把变动少、更新慢的操作放在前面,让它们尽早生成稳定层;把频繁修改的部分(比如源码)往后放,避免一改代码就全链路重建。
先装依赖,再复制代码
这是最核心的排序原则。例如 Node.js 项目中:
- COPY package.json yarn.lock ./ → 依赖清单很少改动,这层缓存命中率高
- RUN yarn install --frozen-lockfile → 命令和输入文件稳定,能长期复用
- COPY . . → 源码天天改,放最后,只让它影响后续少数层(如构建、启动脚本)
这样改一行业务代码,不会触发 node_modules 重装。
多阶段构建中分拆依赖与构建
在 Go/Java/Rust 等编译型语言中,把依赖下载和源码编译拆成独立步骤:
- COPY go.mod go.sum . → 单独一层,仅当依赖声明变更才重建
- RUN go mod download → 缓存可跨多次提交复用
- COPY . . → 放在下载之后,不影响上面两层缓存
即使每天提交十次代码,go mod download 这一层也几乎不重跑。
用 .dockerignore 排除干扰项
COPY 或 ADD 指令会扫描整个构建上下文,如果目录里混着日志、临时文件、node_modules,哪怕没显式 COPY,只要它们在上下文中,某些旧版 Docker 仍可能误判哈希变化。加一条:
- .dockerignore 中写入 node_modules、*.log、.git、dist 等无关内容
能防止因隐藏文件变动导致 COPY 层意外失效。
固定基础镜像标签,避免 FROM 变更
FROM ubuntu:20.04 和 FROM ubuntu:20.04@sha256:abc... 是不同层;而 FROM ubuntu:latest 则每次构建都可能拉新镜像,直接清空全部缓存。
- 始终使用带明确版本号或 digest 的基础镜像,如 FROM golang:1.21.6-alpine
- 避免 latest、rolling、dev 等浮动标签
FROM 层一旦变化,后面所有层自动失效——这是缓存链最脆弱的一环。










