多阶段构建是生产镜像瘦身的刚性手段,通过隔离builder(含sdk编译)和runner(极简运行)两阶段,仅复制必需产物,可将900mb镜像压至90mb以下。

多阶段构建不是可选项,而是生产镜像瘦身的刚性手段。它通过物理隔离构建与运行环境,把原本含编译器、源码、缓存的臃肿镜像,压缩成仅含可执行文件或运行时依赖的精简产物——实际项目中,900MB 镜像压到 90MB 以下非常普遍。
明确划分 builder 和 runner 两个阶段
第一阶段专注“干活”,第二阶段只负责“运行”,两者之间不能混用、不能交叉污染:
- 构建阶段(builder)用完整镜像,如 golang:1.22、node:18-slim 或 maven:3.8-openjdk-11,安装依赖、拉取代码、执行编译
- 运行阶段(runner)必须换极小镜像,如 alpine:3.19、distroless/static-debian12 或 scratch,不带 shell、不带包管理器、不带调试工具
- 构建阶段必须用 AS builder 命名,运行阶段不能有 AS,也不能出现任何 RUN go build 或 RUN npm install 类命令
- 复制产物必须写明来源:COPY --from=builder /app/app /,路径要精确到文件,避免误拷整个目录
选对基础镜像,避免隐性膨胀
别只看标签名大小,要看实际运行兼容性和构建开销:
- Go 项目开启静态编译:CGO_ENABLED=0 GOOS=linux go build -a -o app,这样 runner 阶段可用 scratch(0B)或 alpine
- Python 项目优先用 python:3.11-slim(约 120MB),兼容 glibc、支持绝大多数预编译 wheel,比 Alpine 更稳
- Node.js 项目慎用 node:18-alpine,确认所有 native 模块(如 bcrypt)有 Alpine wheel;否则回退 node:18-slim
- 绝对不用 ubuntu、debian、node:18 这类完整发行版作为 runner 镜像——它们自带几百 MB 系统工具,完全没必要
每一步都精简,堵住体积泄漏点
即使用了多阶段,写法松散仍会让镜像悄悄变胖:
- 在 builder 阶段清理系统缓存:RUN apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*
- Go 项目先 COPY go.mod go.sum 再 RUN go mod download,利用 Docker 缓存加速重复构建
- Python 用虚拟环境 + --no-cache-dir:RUN python -m venv /opt/venv && /opt/venv/bin/pip install --no-cache-dir -r requirements.txt
- Node.js 用 npm ci --only=production 替代 npm install,跳过 devDependencies
验证是否真的瘦身成功
不能只看 docker images 的 SIZE 字段,要查内容是否干净:
- 运行 docker history your-image-name:最终镜像应只有 1~3 层,每层不超过几 MB;若看到几百 MB 的 layer,说明某步 COPY 或 RUN 泄漏了大文件
- 运行 docker run --rm -it your-image-name sh:如果报 sh: not found,说明没混入多余 shell 工具,这是好信号
- 进入镜像检查文件树:docker run --rm -it your-image-name ls -la /,确认只有预期的二进制、配置和必要库,没有 /app/src、/root/.cache、/go 等构建残留











