多階段
本節探討多階段構建。使用多階段構建有兩個主要原因
- 它們允許您並行執行構建步驟,使您的構建流程更快、更高效。
- 它們允許您建立一個佔用空間更小的最終映象,其中只包含執行程式所需的元件。
在 Dockerfile 中,構建階段由 FROM
指令表示。上一節中的 Dockerfile 沒有利用多階段構建。它只是一個構建階段。這意味著最終映象中充斥著用於編譯程式的資源。
$ docker build --tag=buildme .
$ docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest c021c8a7051f 5 seconds ago 150MB
程式編譯成可執行二進位制檔案,因此您不需要在最終映象中存在 Go 語言實用程式。
新增階段
使用多階段構建,您可以選擇對構建和執行時環境使用不同的基礎映象。您可以將構建工件從構建階段複製到執行時階段。
修改 Dockerfile 如下。此更改使用最小的 scratch
映象作為基礎建立另一個階段。在最終的 scratch
階段,將之前階段構建的二進位制檔案複製到新階段的檔案系統中。
# syntax=docker/dockerfile:1
FROM golang:1.21-alpine
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
+
+ FROM scratch
+ COPY --from=0 /bin/client /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
現在,如果您構建映象並檢查它,您應該看到一個明顯更小的數字
$ docker build --tag=buildme .
$ docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest 436032454dd8 7 seconds ago 8.45MB
映象大小從 150MB 降至只有 8.45MB。這是因為生成的映象只包含二進位制檔案,其他什麼都沒有。
並行
您已減少了映象的佔用空間。下一步將展示如何使用多階段構建來提高構建速度,並利用並行性。構建目前一次生成一個二進位制檔案。沒有理由需要先構建客戶端再構建伺服器,反之亦然。
您可以將二進位制檔案構建步驟拆分為單獨的階段。在最終的 scratch
階段,從每個相應的構建階段複製二進位制檔案。透過將這些構建細分為單獨的階段,Docker 可以並行執行它們。
構建每個二進位制檔案的階段都需要 Go 編譯工具和應用程式依賴項。將這些通用步驟定義為可重用的基礎階段。您可以透過使用模式 FROM image AS stage_name
為階段命名來實現。這允許您在另一個階段的 FROM
指令中引用階段名稱(FROM stage_name
)。
您還可以為二進位制檔案構建階段命名,並在將二進位制檔案複製到最終的 scratch
映象時,在 COPY --from=stage_name
指令中引用階段名稱。
# syntax=docker/dockerfile:1
- FROM golang:1.21-alpine
+ FROM golang:1.21-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
+
+ FROM base AS build-client
RUN go build -o /bin/client ./cmd/client
+
+ FROM base AS build-server
RUN go build -o /bin/server ./cmd/server
FROM scratch
- COPY --from=0 /bin/client /bin/server /bin/
+ COPY --from=build-client /bin/client /bin/
+ COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
現在,build-client
和 build-server
階段不再是依次構建二進位制檔案,而是同時執行。


構建目標
最終映象現在很小,並且您使用並行性有效地構建了它。但是這個映象有點奇怪,因為它在同一個映象中包含了客戶端和伺服器二進位制檔案。它們不應該分別為兩個不同的映象嗎?
可以使用單個 Dockerfile 建立多個不同的映象。您可以使用 --target
標誌指定構建的目標階段。將未命名的 FROM scratch
階段替換為兩個名為 client
和 server
的單獨階段。
# syntax=docker/dockerfile:1
FROM golang:1.21-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
FROM base AS build-client
RUN go build -o /bin/client ./cmd/client
FROM base AS build-server
RUN go build -o /bin/server ./cmd/server
- FROM scratch
- COPY --from=build-client /bin/client /bin/
- COPY --from=build-server /bin/server /bin/
- ENTRYPOINT [ "/bin/server" ]
+ FROM scratch AS client
+ COPY --from=build-client /bin/client /bin/
+ ENTRYPOINT [ "/bin/client" ]
+ FROM scratch AS server
+ COPY --from=build-server /bin/server /bin/
+ ENTRYPOINT [ "/bin/server" ]
現在您可以構建客戶端和伺服器程式作為單獨的 Docker 映象(標籤)
$ docker build --tag=buildme-client --target=client .
$ docker build --tag=buildme-server --target=server .
$ docker images "buildme*"
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme-client latest 659105f8e6d7 20 seconds ago 4.25MB
buildme-server latest 666d492d9f13 5 seconds ago 4.2MB
現在映象更小了,每個大約 4MB。
此更改還避免了每次都要構建兩個二進位制檔案。當選擇構建 client
目標時,Docker 只構建通向該目標的階段。如果不需要 build-server
和 server
階段,則會跳過它們。同樣,構建 server
目標會跳過 build-client
和 client
階段。
總結
多階段構建對於建立佔用空間更小、更精簡的映象非常有用,還有助於加快構建速度。
相關資訊
下一步
下一節將介紹如何使用檔案掛載來進一步提高構建速度。