多阶段构建通过多个from指令分离编译与运行环境,仅将产物复制到精简镜像中:第一阶段用含工具链的镜像(如node:20、golang:1.23)编译,第二阶段用轻量镜像(如nginx:alpine、alpine:3.20)仅copy产物,彻底剔除源码、工具、依赖,显著减小体积并提升安全性。

在一个 Dockerfile 中同时完成编译与打包,靠的是多阶段构建(multi-stage build)——它允许你用多个 FROM 指令划分不同用途的构建阶段,只把最终运行所需的文件复制过去,不带编译工具、源码、中间产物。
核心思路:分离编译环境和运行环境
编译需要完整 SDK、构建工具链(如 Go toolchain、Maven、Node.js)、依赖库;而运行只需要可执行文件或 jar/war 包 + 最小运行时(如 alpine、scratch、busybox)。多阶段构建让这两者共存于一个文件,互不干扰。
- 第一阶段(比如叫 builder):拉取含编译器的镜像(如
golang:1.23、maven:3.9-openjdk-17、node:20),复制源码,执行编译/打包命令 - 第二阶段(运行阶段):拉取轻量基础镜像(如
alpine:3.20、debian:slim或scratch),仅 COPY 第一阶段生成的产物(如myapp、target/app.jar、dist/目录) - 最终镜像里没有
go、mvn、npm,也没有.go或.java源文件,体积大幅减小,安全性更高
关键语法:命名阶段 + 跨阶段复制
用 AS 给构建阶段起名,再用 COPY --from=阶段名 拿文件,比用数字索引更清晰、易维护:
-
FROM golang:1.23 AS builder—— 定义编译阶段,命名为builder -
COPY . /src→RUN cd /src && go build -o /app/myapp .—— 在 builder 阶段编译出二进制 -
FROM alpine:3.20—— 新阶段,不继承前一阶段任何内容 -
COPY --from=builder /app/myapp /usr/local/bin/—— 只复制编译结果 -
CMD ["/usr/local/bin/myapp"]—— 启动应用
常见语言示例对照
Go 项目:编译成静态二进制,可直接用 scratch 运行
FROM golang:1.23 AS builder WORKDIR /app COPY . . RUN CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o myapp . FROM scratch COPY --from=builder /app/myapp /myapp CMD ["/myapp"]
Java(Maven):提取 target/*.jar
FROM maven:3.9-openjdk-17 AS builder WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests FROM openjdk:17-jre-slim WORKDIR /app COPY --from=builder /app/target/*.jar app.jar CMD ["java", "-jar", "app.jar"]
前端(Node + Nginx):构建产物交给 nginx 服务
FROM node:20 AS builder WORKDIR /app COPY package*.json . RUN npm ci --only=production COPY . . RUN npm run build FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
构建时还能跳过某些阶段
如果只想验证编译是否成功,不用生成最终镜像,可用 --target 参数指定阶段:
-
docker build --target builder -t myapp-builder .—— 只构建 builder 阶段 -
docker build -t myapp-prod .—— 默认构建到最后一个阶段(即最终镜像)
不复杂但容易忽略:每个 FROM 都开启全新层,前面阶段的所有文件默认不会进入下一阶段。你控制“留什么、扔什么”,全在 COPY --from 这一行决定。











