用多阶段构建瘦身镜像:做什么、为什么、怎么确认

准备工作

  • 已安装 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.