多阶段构建的核心是分离构建与运行环境,只保留运行必需内容。它通过多个from指令分阶段:第一阶段用含sdk的镜像编译生成产物,第二阶段换极小镜像(如alpine或scratch)仅复制产物,剔除编译器、缓存、源码等冗余,使java镜像从1.7gb降至200mb,go服务压至15–25mb,显著提升构建、传输效率并降低安全风险。
核心是把构建环境和运行环境彻底分开,只让最终镜像里留下真正需要的东西。
多阶段构建解决的根本问题
传统单阶段构建会把编译器、包管理器、源码、依赖缓存、临时文件全塞进一个镜像。比如用 maven:3.8-jdk-11 构建 Java 项目,镜像里就带着 JDK 全家桶、Maven 引擎、~/.m2 本地仓库,体积轻松破 800MB;但运行时只需要 JRE 和一个 .jar 文件。
多阶段构建用多个 FROM 拆开流程:前一阶段负责“干活”,后一阶段只“收成果”。中间所有构建工具、缓存、源码,都不会出现在最终镜像里。
典型写法结构
以 Go 服务为例,两阶段是最常见的模式:
-
第一阶段(builder):用完整开发镜像,如
golang:1.22,执行go mod download和go build,生成二进制文件 -
第二阶段(runtime):换极小基础镜像,如
alpine:latest或scratch,只COPY --from=builder拿走编译好的二进制,再配好运行依赖(如 ca-certificates)
Java、Node.js、Python 等语言也同理:构建阶段用带 SDK 的镜像,运行阶段切到 -slim 或 -alpine 变体,只复制打包产物(jar / dist / wheel)。
关键细节不能漏
光写两个 FROM 不够,还得注意这些实操要点:
-
给构建阶段起别名:比如
FROM golang:1.22 AS builder,后续才能用--from=builder精准复制 -
运行阶段避免装多余工具:用
alpine就别装vim或bash;用scratch更要确保二进制是静态链接的(如 Go 加CGO_ENABLED=0) -
合并 RUN 指令:在每个阶段内,把安装依赖和清理缓存写在同一行,比如
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*,避免残留层 -
配合 .dockerignore:排除
node_modules、target、.git等本地不需要上传的目录,减少构建上下文体积
效果能差多少
真实案例中,优化前后对比非常直观:
- Java Spring Boot 项目:从
openjdk:11-jre单阶段的 1.7GB → 多阶段 +debian:slim运行镜像,压到 200MB 左右 - Go Web 服务:从
golang:1.22单阶段的 1.1GB →alpine运行阶段,降到 15–25MB - Python 数据服务:从
python:3.9的 2.3GB →python:3.9-slim+ 多阶段,稳定在 380MB 内
体积降下来,CI 构建时间通常也能砍掉一半以上,推送和拉取速度明显提升,安全扫描告警也大幅减少。











