用多阶段构建瘦身镜像:做什么、为什么、怎么确认
准备工作
已安装 Docker,终端能运行
docker version。按官方文档核实日期:2026-10-24;示例基础镜像标签以你本地可拉取的为准。[1]
建议开启 BuildKit(现代 Docker 默认常已开启)。
操作步骤
1、写两个 FROM:一阶段编译,二阶段只收产物
做什么:在同一份 Dockerfile 里使用多个 FROM。第一阶段用带编译器的基础镜像完成构建;第二阶段换精简基础镜像,只用 COPY --from=阶段名 拷贝产物。
为什么:最终镜像不需要编译器、包管理器缓存和源码树,体积更小,攻击面也更小。官方文档把这称为 multi-stage builds 的核心用途。[1]
怎么确认:docker build -t hello . 退出码为 0;docker image ls 能看到 hello;与“单阶段把 golang 镜像直接当运行时”的做法相比,最终镜像层更少、体积通常明显更小。
# syntax=docker/dockerfile:1
FROM golang:1.26 AS build
WORKDIR /src
COPY <<EOF /src/main.go
package main
import "fmt"
func main() { fmt.Println("hello, world") }
EOF
RUN go build -o /bin/hello ./main.go
FROM scratch
COPY --from=build /bin/hello /bin/hello
CMD ["/bin/hello"]说明:给阶段命名 AS build 后,即使后面重排指令,COPY --from=build 也不容易指错层;用序号 --from=0 也能工作,但可读性较差。[1]
2、需要调试中间层时用 --target
做什么:docker build --target build -t hello-build .
为什么:构建不一定要做到最终阶段。调试编译失败、检查中间产物、或单独保留带调试符号的阶段时,停在命名阶段更省事。[1]
怎么确认:docker image ls 出现 hello-build;docker history hello-build 能看到含 Go 工具链的层;而最终 hello 镜像历史里不应再出现完整 SDK 层。
3、理解 BuildKit 与 legacy builder 的差异
做什么:同一份含多个互不依赖阶段的 Dockerfile,分别用 BuildKit 与关闭 BuildKit 构建同一个 --target。
为什么:BuildKit 只会构建目标阶段依赖的上游阶段;legacy builder 会把目标之前的阶段都跑一遍,即使没有依赖关系。官方文档用 base/stage1/stage2 例子对照了两种行为。[1]
怎么确认:打开 BuildKit 时,日志里不应出现未依赖阶段的 RUN;关闭后同一目标可能多出无关阶段的步骤输出。
4、可选:从外部镜像拷文件
做什么:COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
为什么:多阶段不限于“自己前面的阶段”,也可以把已有镜像当来源,客户端会按需拉取。[1]
怎么确认:构建成功后,目标路径存在该配置文件(可用临时阶段或非 scratch 基础镜像验证)。
注意事项
最终阶段用
scratch时没有 shell 与 CA 证书,排障更难;生产上可改用 distroless 等,但不在本示例范围。不要把密钥、
.env用COPY打进任何阶段;构建参数里的秘密应用 BuildKit secret 一类机制(另见官方安全文档)。
来源
[1] Docker Docs · Multi-stage builds · https://docs.docker.com/build/building/multi-stage/ · 核实于 2026-10-24
#Docker #多阶段构建 #教程 #Dockerfile #BuildKit

成为第一条评论!
Nobody's responded to this post yet. Add your thoughts and get the conversation going.