多阶段构建
多阶段构建对任何努力优化的人来说都很有用 Dockerfile 的 Dockerfile 中,同时保持它们易于阅读和维护。
使用多阶段构建
对于多阶段构建,您可以使用多个FROM语句。
每FROM指令可以使用不同的 base 来调用,并且每个
阶段。您可以选择性地将工件从一个阶段复制到
另一个,在最终镜像中留下你不想要的一切。
以下 Dockerfile 有两个独立的阶段:一个用于构建Binaries, 另一个是Binaries从第一阶段复制到下一阶段。
# 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 .
最终结果是一个很小的生产镜像,里面只有Binaries。 构建应用程序所需的任何构建工具都不包含在 生成的镜像。
它是如何工作的?第二个FROM指令开始一个新的构建阶段
这scratchimage 作为其基础。这COPY --from=0行仅复制
将 Artifact 从前一阶段构建到这个新阶段。Go SDK 和任何
中间伪影会留下,并且不会保存在最终镜像中。
为生成阶段命名
默认情况下,阶段未命名,您可以通过其整数来引用它们
number,第一个从 0 开始FROM指令。但是,您可以
为您的阶段命名,方法是添加AS <NAME>到FROM指令。这
example 通过命名阶段并使用
这COPY指令。这意味着,即使您的
Dockerfile 稍后重新排序,则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 中较早创建。您可以使用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.它将构建一个阶段,即使选中了
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 后,构建stage2target 在此 Dockerfile 中仅表示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不依赖于它。