多阶段构建的核心作用是物理隔离编译与运行环境:builder阶段用完整工具链完成构建,runner阶段换极简镜像仅复制必需产物,确保最终镜像不含编译工具、源码、缓存等冗余内容。

多阶段构建的核心作用,就是让编译环境和运行环境物理隔离——编译用的工具链、头文件、缓存、源码,统统留在前一个阶段,最终镜像里只放真正要跑起来的东西。
明确划分 builder 和 runner 两个阶段
第一阶段(builder)负责干活:装 SDK、拉依赖、编译打包;第二阶段(runner)只负责运行:换小镜像、精准复制产物。
- builder 阶段用带完整工具的镜像,比如
maven:3.9-openjdk-17、golang:1.22、node:20-slim - runner 阶段必须换极简镜像,比如
amazoncorretto:17-jre-alpine、python:3.11-slim、alpine:3.20,绝不能继续用 JDK 或 full 版本 - 用
AS builder显式命名构建阶段,后续才能通过COPY --from=builder正确引用
只复制运行必需的文件,不拖整个目录
COPY --from 不是搬家,是快递——只送收件人点名要的那几样。复制整目录很容易把 /src、/target、/node_modules、测试文件全带进去。
- Java:只复制
COPY --from=builder /app/target/app.jar /app.jar,别拷/app/target/整个文件夹 - Go:静态编译后,只复制二进制
COPY --from=builder /app/myserver /usr/local/bin/myserver - Node.js:构建完
dist/后,只复制它和精简后的node_modules,再用npm ci --only=production - Python:在 builder 阶段建 venv,只复制
venv/lib和venv/bin中实际需要的可执行文件
构建阶段也要“打扫干净”,别留尾巴
即使分了阶段,如果 builder 阶段没清理缓存或中间文件,有些 COPY 操作仍可能意外带入冗余内容。
- Debian/Ubuntu 系:安装后加
&& apt-get clean && rm -rf /var/lib/apt/lists/* - Alpine:用
apk --no-cache add,结束后加&& apk del build-base - npm:构建后加
&& npm cache clean --force - pip:用
--no-cache-dir安装,避免/root/.cache/pip残留 - Go:设
CGO_ENABLED=0生成静态二进制,省去运行时对 glibc 的依赖
验证最终镜像是否真的“干净”
别光看 docker images 数字变小了就放心,得进去看看里面有没有不该有的东西。
- 运行
docker run --rm -it your-image ls -la /,确认没有/usr/lib/gcc、/usr/bin/mvn、/root/.m2这类构建残留 - 检查
docker history your-image,确认大体积层都集中在 builder 阶段,runner 阶段每层都很轻 - Java 应用可试
docker run --rm your-image java -version,确保 JRE 能正常响应,没缺证书或模块 - Go 静态二进制可用
docker run --rm your-image ldd /usr/local/bin/myserver(非 Alpine 下)验证是否真无动态链接











