多阶段构建可最小化生产镜像体积,核心是构建与运行环境物理隔离,仅传递成品;构建阶段用完整镜像(如golang:1.21),运行阶段用极简镜像(如scratch),通过COPY --from=builder精确复制产物。

直接用多阶段构建(Multi-Stage Builds)就能把生产镜像体积压到最低——关键不是“能不能做”,而是“怎么分得干净”。核心就一条:构建环境和运行环境必须物理隔离,中间只传成品,不传工具、不传源码、不传缓存。
明确划分两个阶段:构建阶段只干活,运行阶段只执行
构建阶段用功能完整的镜像(如 golang:1.21、maven:3.8、node:18),专注下载依赖、编译代码、生成产物;运行阶段换极简镜像(如 alpine:3.19、scratch 或 distroless/static-debian12),只接收最终二进制文件或打包好的 dist 目录。
- 构建阶段必须用 AS 命名,比如
FROM golang:1.21 AS builder - 运行阶段不能有 AS,也不能包含任何构建命令(
RUN go build、RUN npm install等全删掉) - 复制必须用
COPY --from=builder显式指定来源阶段,路径要精确到文件级
选对运行阶段基础镜像,体积差5倍以上
同一份 Go 二进制,在不同运行镜像里体积差异极大:
- scratch:真正零依赖,仅含你的二进制,体积≈二进制本身(Go 静态编译后通常
- alpine:3.19:带基本 shell 和 ca-certificates,约 5MB,适合需要调试或发起 HTTPS 请求的场景
- distroless/static-debian12:Google 提供,无 shell、无包管理器、仅含运行时必需库,比 alpine 更安全,体积略大于 scratch
- 避免用
debian、ubuntu、node:18等完整发行版作为运行镜像——它们自带几百MB系统工具,完全没必要
构建阶段也要精简,别让缓存拖累体积
构建阶段虽然不进最终镜像,但如果写法松散,会拖慢构建速度、增加 layer 数量,间接影响可维护性:
- 先
COPY go.mod go.sum再RUN go mod download,利用 Docker 缓存加速重复构建 - Go 项目加
CGO_ENABLED=0 GOOS=linux,确保生成静态链接二进制,避免运行时缺 libc - Node.js 项目用
npm ci --only=production或yarn install --production,跳过 devDependencies - Java 项目用
maven:3.8-openjdk-11构建,但运行阶段用openjdk:11-jre-slim,彻底去掉 JDK 工具链
验证是否真的瘦身成功
光看最终镜像大小不够,得确认“不该有的东西”确实没进去:
- 运行
docker history your-image-name,检查各层体积分布——最终镜像应只有 1~3 层,每层不超过几 MB - 运行
docker run --rm -it your-image-name sh,如果报错sh: not found,说明用了 scratch 或 distroless,这是好事 - 用
docker run --rm -it your-image-name ls -la /查看根目录,确认没有/usr/local/go、/go/pkg/mod、/node_modules这类构建残留


















