docker多阶段构建通过分离构建与运行职责解决镜像臃肿、攻击面大和部署慢问题:第一阶段(builder)装工具、编译依赖、生成产物;第二阶段(runtime)仅用极简镜像复制必需文件,剔除所有非运行时内容。

核心思路:两个阶段,各司其职
第一阶段(Builder)专注编译和准备:装工具、拉依赖、跑构建命令、生成产物(比如 dist/、.jar、main 二进制)。这一阶段用什么镜像都行,只要能干活——node:18、maven:3.8、golang:1.21 都可以。
第二阶段(Runtime)只负责运行:选一个极简基础镜像(如 alpine、distroless 或 openjdk:11-jre-slim),然后用 COPY --from=builder 把上一阶段生成的成品拷过来,其他一概不要。
关键操作:命名 + 复制,不能漏
必须给构建阶段起名字,否则后面没法引用:
-
FROM node:18-alpine AS builder—— 这个AS builder是必需的 -
COPY --from=builder /app/dist /usr/share/nginx/html——--from=builder指向的就是上面那个名字 - 不命名也能用索引(比如
--from=0),但可读性差,容易出错,不推荐
常见语言的典型写法
Node.js(前端+服务端)
- Builder 阶段:装
pnpm或npm,install生产依赖,build出dist/ - Runtime 阶段:用
nginx:alpine直接托管静态文件;或用node:18-alpine启动服务端,只复制dist/和node_modules(注意过滤 dev 依赖)
Java(Maven)
- Builder 阶段:
mvn clean package -DskipTests打出target/app.jar - Runtime 阶段:用
openjdk:17-jre-slim,只COPY --from=builder /app/target/app.jar .
Go
- Builder 阶段:设
CGO_ENABLED=0 GOOS=linux编译静态二进制,避免运行时依赖 glibc - Runtime 阶段:直接用
scratch(空镜像)或alpine,只放二进制文件,体积可压到 10MB 以内
容易踩的坑
别在最终镜像里留源码、node_modules 全量、package-lock.json、测试文件、配置文件(除非运行时真需要)。
构建命令要放在 Builder 阶段,而不是 Runtime 阶段;RUN 命令如果写错位置,会把中间产物塞进最终镜像。
用 docker build --target builder 可单独构建某阶段,方便调试;用 docker run -it <image> sh</image> 进去验证最终镜像里到底有什么。











