多阶段构建通过物理隔离编译与运行环境大幅削减镜像体积:builder阶段用完整工具链编译,runtime阶段仅保留精简jre或distroless镜像及必要产物,辅以.dockerignore和层合并优化。

多阶段构建大幅削减生产镜像中冗余编译依赖,关键在于物理隔离编译环境与运行环境——编译用的工具链、源码、缓存、依赖包,一律不进入最终镜像。
明确划分 builder 和 runtime 两个阶段
第一阶段(builder)只负责“干活”:拉代码、装 Maven/Gradle/Node.js、执行编译。它用的是功能齐全的镜像,比如 maven:3.9-openjdk-17 或 node:20-slim。第二阶段(runtime)只负责“跑起来”:它必须换一个极简镜像,比如 eclipse-temurin:17-jre-alpine 或 gcr.io/distroless/java17-debian11,里面没有 javac、没有 mvn、没有 src 目录,也没有 pom.xml。
- builder 阶段必须用
AS builder显式命名,否则COPY --from=builder会找不到来源 - runtime 阶段不能出现任何
RUN安装命令或构建动作,只做复制和启动 - 两个阶段的基础镜像不能混用:builder 用 JDK,runtime 就绝不能用 JDK 镜像(哪怕带
-slim后缀)
精准复制,只取运行必需的产物
不要 COPY --from=builder /app 这种宽泛路径,它容易把 target/classes、node_modules/.cache 等中间文件一并拖进来。应该精确到最终输出文件:
- Java 应用:只复制
/app/target/*.jar - Go 应用:只复制
/app/main二进制 - 前端应用:只复制
/app/dist/下的静态资源 - 必要时补运行时依赖:比如 Alpine 镜像需
RUN apk add --no-cache ca-certificates,distroless 则靠基础镜像预置
选对 runtime 基础镜像,拒绝“伪精简”
不是所有带 -slim 的镜像都适合 runtime 阶段。例如 openjdk:17-jdk-slim 仍含完整 JDK,体积超 400MB;而 eclipse-temurin:17-jre-alpine 仅约 80MB,distroless/java17 更只有 167MB 且 CVE 数量锐减。
- 优先考虑 JRE 替代 JDK(体积减少 50%+)
- Alpine 镜像体积最小(~8MB),但注意 musl libc 兼容性,Java/Node.js 官方已提供适配版本
- Distroless 是更进一步的选择:无 shell、无包管理器、无调试工具,攻击面最小
配合 .dockerignore 和层合并进一步压缩
多阶段构建解决的是“编译工具是否进镜像”的问题,但源码和缓存若被误复制,依然会膨胀体积。因此需同步做两件事:
- 在项目根目录写好
.dockerignore:排除src/、pom.xml、node_modules/、.git、target/(非构建阶段)、**/*.md等无关文件 - 在 builder 阶段合并 RUN 指令:把
apt update && apt install && apt clean写在同一行,避免缓存残留成独立层











