多阶段构建通过一个Dockerfile分离构建与运行阶段,仅复制必要产物到精简镜像(如alpine或distroless),删减编译器、源码等冗余内容;需显式命名阶段、精准COPY产物、关闭CGO、启用BuildKit,并验证镜像体积与层结构。
多阶段构建在项目中落地,核心是用一个 dockerfile 分开“构建”和“运行”两个世界,只把最终需要的文件复制到精简镜像里。它不是加功能,而是做减法——删掉编译器、源码、依赖缓存、开发工具这些运行时完全用不到的东西。
明确划分构建与运行阶段
每个阶段用 FROM 开头,命名阶段(可选)便于引用。典型结构是:前一阶段装工具、拉依赖、编译;后一阶段只放二进制或静态资源,基础镜像越小越好(如 alpine 或 distroless)。
- 构建阶段可用完整镜像,比如
golang:1.23、node:20、maven:3.9-openjdk-17 - 运行阶段推荐:
alpine:latest(轻量)、debian:slim(兼容性好)、或 Google 的gcr.io/distroless/static-debian12(无 shell,更安全) - 阶段之间用
COPY --from=阶段名显式拷贝产物,不带任何多余路径
按语言/框架写法示例
不同项目类型写法略有差异,但逻辑一致:编译在前段完成,产物复制到后段。
-
Go 项目:构建阶段用
golang编译出静态二进制,运行阶段直接 COPY 进alpine,无需安装 Go 环境 -
Node.js 前端:构建阶段用
node:20执行npm install && npm run build,运行阶段 COPYdist/到nginx:alpine或node:20-alpine(仅运行,不装 devDependencies) -
Java Spring Boot:构建阶段用
maven:3.9-openjdk-17打出target/*.jar,运行阶段 COPY jar 到eclipse-temurin:17-jre-jammy,不带 Maven 和源码
关键细节不能漏
光有阶段还不够,几个实操要点直接影响效果:
-
避免 COPY 整个构建目录:只 COPY 编译产出(如
./myapp、./dist、./target/app.jar),别写COPY . . -
关闭 CGO(Go 项目):加
CGO_ENABLED=0让二进制静态链接,运行阶段就不用装 libc 等系统库 -
启用 BuildKit:构建时加
DOCKER_BUILDKIT=1 docker build ...,支持更清晰的阶段命名、并行构建、更好的缓存命中 -
给阶段起名:比如
FROM golang:1.23 AS builder,后续用COPY --from=builder /app/myapp /usr/local/bin/,语义清楚不易错
验证是否真正瘦身成功
构建完别急着推,先本地检查:
- 运行
docker images对比体积变化(常见从 1GB→50MB 左右) - 执行
docker history your-image,确认最后一层很小,且没有apt install、npm install等大体积操作残留 - 进容器试运行:
docker run --rm -it your-image ls -lh /usr/local/bin/,只看到你要的文件,没源码、没 node_modules、没 .m2 仓库


















