多阶段构建

多阶段构建对任何努力优化的人来说都很有用 Dockerfile 的 Dockerfile 中,同时保持它们易于阅读和维护。

使用多阶段构建

在多阶段构建中,您可以在 Dockerfile 中使用多个语句。 每条指令都可以使用不同的 base,并且每条指令都会开始一个新的 阶段。您可以选择性地将工件从一个阶段复制到 另一个,在最终镜像中留下你不想要的一切。FROMFROM

以下 Dockerfile 有两个独立的阶段:一个用于构建二进制文件, 另一个是二进制文件从第一阶段复制到下一阶段。

# syntax=docker/dockerfile:1
FROM golang:1.23
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 .

最终结果是一个很小的生产镜像,里面只有二进制文件。 构建应用程序所需的任何构建工具都不包含在 生成的镜像。

它是如何工作的?第二条指令使用 镜像作为其基础。该行仅复制 将 Artifact 从前一阶段构建到这个新阶段。Go SDK 和任何 中间伪影会留下,并且不会保存在最终镜像中。FROMscratchCOPY --from=0

为生成阶段命名

默认情况下,阶段未命名,您可以通过其整数来引用它们 number,第一个指令从 0 开始。但是,您可以 通过向指令添加 an 来命名您的阶段。这 example 通过命名阶段并使用 指令。这意味着,即使您的 Dockerfile 稍后重新排序,则不会中断。FROMAS <NAME>FROMCOPYCOPY

# syntax=docker/dockerfile:1
FROM golang:1.23 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 包含每个阶段。您可以指定目标构建阶段。这 以下命令假定您使用的是前一个,但停止在 名为 的阶段 :Dockerfilebuild

$ docker build --target build -t hello .

这可能有用的几种情况是:

  • 调试特定的构建阶段
  • 使用启用了所有调试元件或工具的舞台,以及 精益阶段debugproduction
  • 使用应用程序填充测试数据的阶段,但 使用使用真实数据的不同阶段进行生产构建testing

使用外部镜像作为舞台

使用多阶段构建时,您不仅限于从以下阶段复制 在 Dockerfile 中较早创建。您可以使用该指令 从单独的镜像复制,使用本地镜像名称、可用的标记 本地或 Docker 注册表,或标记 ID。Docker 客户端拉取镜像 (如有必要),并从中复制工件。语法为:COPY --from

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 不依赖于该阶段。--target

BuildKit 仅构建目标 stage 取决于。

例如,给定以下 Dockerfile:

# syntax=docker/dockerfile:1
FROM ubuntu AS base
RUN echo "base"

FROM base AS stage1
RUN echo "stage1"

FROM base AS stage2
RUN echo "stage2"

启用 BuildKit 后,在此 Dockerfile 中构建目标仅意味着并进行处理。 没有对 的依赖性,因此会跳过它。stage2basestage2stage1

$ DOCKER_BUILDKIT=1 docker build --no-cache -f Dockerfile --target stage2 .
[+] Building 0.4s (7/7) FINISHED                                                                    
 => [internal] load build definition from Dockerfile                                            0.0s
 => => transferring dockerfile: 36B                                                             0.0s
 => [internal] load .dockerignore                                                               0.0s
 => => transferring context: 2B                                                                 0.0s
 => [internal] load metadata for docker.io/library/ubuntu:latest                                0.0s
 => CACHED [base 1/2] FROM docker.io/library/ubuntu                                             0.0s
 => [base 2/2] RUN echo "base"                                                                  0.1s
 => [stage2 1/1] RUN echo "stage2"                                                              0.2s
 => exporting to image                                                                          0.0s
 => => exporting layers                                                                         0.0s
 => => writing image sha256:f55003b607cef37614f607f0728e6fd4d113a4bf7ef12210da338c716f2cfd15    0.0s

另一方面,在没有 BuildKit 的情况下构建相同的目标会导致所有 正在处理的阶段:

$ DOCKER_BUILDKIT=0 docker build --no-cache -f Dockerfile --target stage2 .
Sending build context to Docker daemon  219.1kB
Step 1/6 : FROM ubuntu AS base
 ---> a7870fd478f4
Step 2/6 : RUN echo "base"
 ---> Running in e850d0e42eca
base
Removing intermediate container e850d0e42eca
 ---> d9f69f23cac8
Step 3/6 : FROM base AS stage1
 ---> d9f69f23cac8
Step 4/6 : RUN echo "stage1"
 ---> Running in 758ba6c1a9a3
stage1
Removing intermediate container 758ba6c1a9a3
 ---> 396baa55b8c3
Step 5/6 : FROM base AS stage2
 ---> d9f69f23cac8
Step 6/6 : RUN echo "stage2"
 ---> Running in bbc025b93175
stage2
Removing intermediate container bbc025b93175
 ---> 09fc3770a9c4
Successfully built 09fc3770a9c4

旧版构建器进程 ,即使不依赖于它。stage1stage2