多阶段构建
原文:https://docs.docker.com/build/building/multi-stage/#use-multi-stage-builds
多阶段构建对任何曾经努力优化 Dockerfile 同时又希望它们易于阅读和维护的人都是有用的。
使用多阶段构建
在多阶段构建中,你在 Dockerfile 中使用多个 FROM
声明。每个 FROM
指令可以使用不同的基础镜像,每一个都开始一个新的构建阶段。你可以从一个阶段选择性地复制构件到另一个阶段,把你不想要的所有东西都留在最终镜像之外。
以下 Dockerfile 分为两个单独的阶段:一个用于构建二进制文件,另一个则将二进制文件从第一阶段复制到下一个阶段。
# syntax=docker/dockerfile:1
FROM golang:1.21
WORKDIR /src
COPY <<EOF ./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=0 /bin/hello /bin/hello
CMD ["/bin/hello"]
你只需要这一个 Dockerfile,不需要另外的构建脚本。只需运行 docker build
。
docker build -t hello .
最终结果是一个只包含二进制文件的小型生产镜像。构建应用所需的任何构建工具都不包含在最终镜像中。
它是如何工作的?第二个 FROM
指令以 scratch
镜像为基础开始一个新的构建阶段。COPY --from=0
行仅复制前一阶段构建的产物到这个新阶段。Go SDK 和任何中间构件都被留下,不保存在最终镜像中。
为你的构建阶段命名
默认情况下,阶段是不命名的,你通过它们的整数编号来引用它们,从第一个 FROM
指令的 0 开始。然而,你可以通过在 FROM
指令中添加 AS <NAME>
来命名你的阶段。以下示例通过命名阶段并在 COPY
指令中使用该名称,改进了前一个示例。这意味着即使你的 Dockerfile 中的指令稍后被重新排序,COPY
也不会出错。
# syntax=docker/dockerfile:1
FROM golang:1.21 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"]
停止在特定的构建阶段
当你构建你的镜像时,你不一定需要构建包括每个阶段的整个 Dockerfile。你可以指定一个目标构建阶段。以下命令假设你使用的是前面的 Dockerfile
,但停在名为 build
的阶段:
docker build --target build -t hello .
这可能在一些场景中很有用,例如:
- 调试特定的构建阶段
- 使用一个包含所有调试符号或工具的
debug
阶段,和一个精
简的 production
阶段
- 使用一个
testing
阶段,在该阶段中你的应用被填充测试数据,但是使用不同的阶段为生产构建,使用真实数据
使用外部镜像作为一个阶段
在使用多阶段构建时,你不仅限于复制你在 Dockerfile 中较早创建的阶段。你可以使用 COPY --from
指令从一个单独的镜像复制,无论是使用本地镜像名、本地或 Docker 注册中可用的标签,还是标签 ID。如果需要,Docker 客户端会拉取镜像并从中复制构件。语法是:
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
使用先前的阶段作为新阶段
你可以通过在使用 FROM
指令时引用之前的阶段,从先前的阶段继续开始。例如:
# syntax=docker/dockerfile:1
FROM alpine:latest AS builder
RUN apk --no-cache add build-base
FROM builder AS build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cpp
FROM builder AS build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp
传统构建器和 BuildKit 之间的差异
传统的 Docker Engine 构建器处理 Dockerfile 的所有阶段,直到选择的 --target
。即使所选目标不依赖该阶段,它也会构建一个阶段。
当 BuildKit 启用时,构建此 Dockerfile 中的 stage2
目标意味着只处理 base
和 stage2
。它不依赖于 stage1
,所以会跳过该阶段。