多阶段构建
多阶段构建对任何努力优化的人来说都很有用 Dockerfile 的 Dockerfile 中,同时保持它们易于阅读和维护。
使用多阶段构建
在多阶段构建中,您可以在 Dockerfile 中使用多个语句。
每条指令都可以使用不同的 base,并且每条指令都会开始一个新的
阶段。您可以选择性地将工件从一个阶段复制到
另一个,在最终镜像中留下你不想要的一切。FROM
FROM
以下 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 和任何
中间伪影会留下,并且不会保存在最终镜像中。FROM
scratch
COPY --from=0
为生成阶段命名
默认情况下,阶段未命名,您可以通过其整数来引用它们
number,第一个指令从 0 开始。但是,您可以
通过向指令添加 an 来命名您的阶段。这
example 通过命名阶段并使用
指令。这意味着,即使您的
Dockerfile 稍后重新排序,则不会中断。FROM
AS <NAME>
FROM
COPY
COPY
# 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 包含每个阶段。您可以指定目标构建阶段。这
以下命令假定您使用的是前一个,但停止在
名为 的阶段 :Dockerfile
build
$ docker build --target build -t hello .
这可能有用的几种情况是:
- 调试特定的构建阶段
- 使用启用了所有调试元件或工具的舞台,以及
精益阶段
debug
production
- 使用应用程序填充测试数据的阶段,但
使用使用真实数据的不同阶段进行生产构建
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 中构建目标仅意味着并进行处理。
没有对 的依赖性,因此会跳过它。stage2
base
stage2
stage1
$ 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
旧版构建器进程 ,即使不依赖于它。stage1
stage2