---
title: "用多阶段构建瘦身镜像：做什么、为什么、怎么确认 - Angryecho"
description: "准备工作 已安装 Docker，终端能运行 docker version。 按官方文档核实日期：2026-10-24；示例基础镜像标签以你本地可拉取的为准。[1] 建议开启 BuildKit（现代 Docker 默认常已开启）。"
canonical_url: "https://www.angryecho.com/c/Docker/p/P2TBBjZwHLwjtEi987wcu"
meta:
  "og:description": "准备工作 已安装 Docker，终端能运行 docker version。 按官方文档核实日期：2026-10-24；示例基础镜像标签以你本地可拉取的为准。[1] 建议开启 BuildKit（现代 Docker 默认常已开启）。"
  "og:image": "https://www.angryecho.com/_og/d/c_Post%2Ctitle_~5ZCQ5qe9572R%2Cdescription_~5ZCQ5qe95LiA5LiL77yM5bCx5LiK5ZCQ5qe9572R44CC%2CpostId_P2TBBjZwHLwjtEi987wcu%2Crevision_~MjAyNi0xMC0yN1QwMjo1Nzo0OS4yMjMzNTBa%2Cp_Ii9jL0RvY2tlci9wL1AyVEJCalp3SEx3anRFaTk4N3djdSI%2Cs_ltQ2sn5JpqahMCXL.png"
  "og:image:height": "630"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": 用多阶段构建瘦身镜像：做什么、为什么、怎么确认
---

> Index of every page on this site: [llms.txt](https://www.angryecho.com/llms.txt)

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

## 准备工作

- 已安装 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 镜像直接当运行时”的做法相比，最终镜像层更少、体积通常明显更小。

```dockerfile
# 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.

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

## Sitemap

See the full [sitemap](https://www.angryecho.com/sitemap.md) for all pages.
