在日常部署服务时,很多人会发现打包出来的 ref="/tag/2019/" style="color:#8B0506;font-weight:bold;">Docker 镜像动不动就几百MB,甚至上GB。不仅推拉镜像慢,还占用大量服务器空间。其实问题很可能出在构建方式上——还在用单阶段构建?该试试多阶段镜像构建了。
为什么需要多阶段构建
举个常见例子:你用 Go 写了个小服务,本地编译完二进制文件才十几MB,但 Docker 镜像却有 800MB。原因是你在镜像里装了 golang:alpine 这种完整开发环境,编译完又没清理依赖,最终把编译器、源码、中间文件全打包进去了。
多阶段构建的核心思路是“分步走”:前面的阶段负责编译和准备文件,后面的阶段只拿最终需要的成品,丢掉所有临时内容。这样打出的镜像干净又小巧。
一个实际的构建例子
比如你要构建一个基于 Node.js 的前端项目,生产环境其实只需要静态文件,不需要 node_modules、npm、webpack 等工具。传统写法可能直接在运行环境中安装依赖并构建,结果就把整个开发套件留在了镜像里。
用多阶段构建可以这样写:
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
第一阶段叫 builder,用 Node 镜像把代码打包出 dist 文件;第二阶段用轻量的 nginx 镜像,只把打包好的静态文件复制过去。最终镜像不包含 Node、npm、源码,体积通常能从 900MB+ 压到 20MB 左右。
Go 项目更受益
Go 项目尤其适合多阶段构建。看这个例子:
FROM golang:1.21 AS build
WORKDIR /src
COPY . .
RUN go build -o myapp .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=build /src/myapp /usr/local/bin/myapp
CMD ["/usr/local/bin/myapp"]
第一阶段完成编译,生成可执行文件;第二阶段使用极简的 Alpine 镜像,只复制二进制文件进去。最终镜像只有几十MB,启动快,传输快,安全面也更小。
还能做更多事
多阶段不限于两个阶段。你可以设置多个中间阶段,比如一个专门跑测试,一个打包,一个做安全扫描。通过 --from=xxx 指定来源,灵活取用。
也可以给不同阶段命名(AS xxx),方便引用。如果不命名,就用数字索引,比如 --from=0 表示第一个阶段。
别忘了.dockerignore
虽然多阶段能瘦身,但如果每次构建都把整个项目目录 COPY 进去,包括 node_modules、log、.git 这些无关文件,还是会拖慢构建速度。记得配好 .dockerignore,只传必要的文件进去,提升效率。
多阶段构建不是什么高深技术,但它实实在在解决了一个普遍痛点。改几行 Dockerfile,就能让镜像变小、部署变快、服务器压力变轻,何乐不为。