多阶段构建能砍掉一半以上镜像体积,核心是只保留运行必需文件:第一阶段用大镜像编译(如golang:1.23、maven:3.9),最终阶段换极小基础镜像(alpine/scratch/jre-alpine),精准copy产物,清理中间缓存,并启用buildkit优化层结构。
多阶段构建能砍掉一半以上镜像体积,核心就一条:只把运行真正需要的东西放进最终镜像,其余全扔掉。
选对基础镜像,直接干掉几百MB
第一阶段用带编译器的大镜像(比如 golang:1.23、maven:3.9-openjdk-17、node:20)没问题,但最终阶段必须换小的。常见做法:
- Go/Rust 程序 → 用
alpine:3.19或干脆scratch(零体积) - Java 应用 → 不用完整 JDK 镜像,改用
eclipse-temurin:17-jre-alpine或amazoncorretto:17-alpine-jre - Node.js 后端 → 最终阶段用
node:20-alpine,别用node:20(后者基于 Debian,自带大量系统工具)
只 COPY 运行必需的文件,其他一概不带
很多单阶段 Dockerfile 直接 COPY . .,结果把 .git、test/、docs/、node_modules 全塞进去了。多阶段里要精准控制:
- 编译完后,只
COPY --from=builder /app/dist /usr/share/nginx/html(前端) - Java 只取
COPY --from=builder /app/target/*.jar /app.jar,不带target/整个目录 - Go 只复制二进制:
COPY --from=builder /app/server /usr/local/bin/server - 用
.dockerignore配合,避免源码中敏感文件或大文件被意外带上
分阶段清理中间产物,别让缓存“赖着不走”
有些依赖安装后会留下缓存(如 npm 的 ~/.npm、Maven 的 ~/.m2、Go 的 /go/pkg/mod),即使没显式 COPY,它们也可能因层叠加留在镜像里。解决办法:
- 在构建阶段末尾加清理命令,比如
RUN npm install && npm run build && rm -rf node_modules - Java 项目用
RUN mvn clean package -DskipTests && rm -rf ~/.m2 - Go 项目开启静态编译:
CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o server .,这样连 libc 都不用带
利用 BuildKit 自动优化层结构
Docker 19.03+ 默认启用 BuildKit,它能让多阶段更干净:
- 自动跳过未被
COPY --from引用的阶段,不生成对应层 - 支持
RUN --mount=type=cache把依赖缓存单独挂载,不写入镜像层 - 开启方式很简单:构建时加
DOCKER_BUILDKIT=1 docker build ...,或在 daemon.json 里设"features": {"buildkit": true}










