核心思路是分离依赖与源码:先单独复制依赖文件(如package.json、go.mod)并安装,再复制源码,确保依赖未变时安装层稳定命中缓存;配合.dockerignore排除冗余文件,并在多阶段构建中锁定依赖下载层。
核心思路是:把依赖描述文件(如 package.json、go.mod、requirements.txt)单独、尽早地 copy 进镜像,再执行安装命令,最后才复制源码。这样只要依赖没变,安装步骤就能稳定命中缓存。
只拷贝依赖文件,不带其他无关内容
避免使用 COPY . . 一次性复制整个项目目录。它会把频繁变动的源码、日志、临时文件等一并带入,导致每次代码修改都触发后续所有层重建。
- 明确列出依赖文件:比如
COPY package.json package-lock.json ./ - Go 项目用
COPY go.mod go.sum ./ - Python 项目用
COPY requirements.txt ./ - 确保路径准确,不要多拷一个空格或错位斜杠
依赖文件必须放在安装命令之前
Docker 缓存按层顺序判断。只有 COPY 依赖文件这层没变,紧随其后的 RUN 安装命令才能复用缓存。
- 正确顺序:
COPY package.json .→RUN npm ci --only=production→COPY . . - 错误顺序:如果
COPY . .在前,哪怕只改了一个.js文件,npm ci这层就一定失效 - 安装命令本身也要稳定:推荐用
npm ci或pip install --no-deps -r requirements.txt等可复现的指令
配合 .dockerignore 排除干扰项
构建上下文里混入 node_modules、.git、dist、logs 等目录,不仅拖慢传输,还可能因时间戳或隐藏文件变化意外影响 COPY 层哈希值。
- 在项目根目录建
.dockerignore,写入:
node_modules
.git
dist
build
logs
*.log - 验证是否生效:运行
docker build --progress=plain . | grep "Sending build context",看上传体积是否明显减小
多阶段构建中分离依赖下载层
对于编译型语言(如 Go、Rust),可把依赖下载和源码编译拆到不同阶段,进一步锁定缓存边界。
- 第一阶段(builder):
COPY go.mod go.sum .→RUN go mod download - 第二阶段(runtime):
COPY --from=builder /go/pkg /go/pkg,跳过重复下载 - 这样即使源码全量修改,
go mod download层仍 100% 复用











