多階段構建是一個新特性,須要 Docker 17.05 或更高版本的守護進程和客戶端。對於那些努力優化 Dockerfiles 並使其易於閱讀和維護的人來講,多階段構建很是有用。html
構建鏡像時最具挑戰性的事情之一就是縮小鏡像大小。Dockerfile 中的每一條指令都會在鏡像中添加一個層,在進入下一層以前,您須要記住清除全部不須要的工件。要編寫一個真正高效的 Dockerfile,傳統上須要使用 shell 技巧和其餘邏輯來保持層儘量小,並確保每一層都有它須要的來自前一層的工件,而沒有其餘東西。linux
實際上,有一個 Dockerfile 用於開發環境(包含構建應用程序所需的全部內容),同時有一個精簡的 Dockerfile 用於生產環境(僅包含應用程序和運行應用程序所需的內容)是很是常見的。這被稱爲「建造者模式」。維護兩個 Dockerfiles 並不理想。nginx
這裏有一個例子 Dockerfile.build
文件以及符合上述建造者模式的 Dockerfile
:git
Dockerfile.build
:github
FROM golang:1.7.3 WORKDIR /go/src/github.com/alexellis/href-counter/ COPY app.go . RUN go get -d -v golang.org/x/net/html \ && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
請注意,此示例還使用 Bash 操做符 &&
將兩個 RUN
命使人爲壓縮在一塊兒,以免在鏡像中建立額外的層。這很容易發生故障,也很難維護。例如,很容易插入另外一個命令而忘記使用 \
字符繼續行。golang
Dockerfile
:docker
FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY app . CMD ["./app"]
build.sh
:shell
#!/bin/sh echo Building alexellis2/href-counter:build docker build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \ -t alexellis2/href-counter:build . -f Dockerfile.build docker container create --name extract alexellis2/href-counter:build docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app docker container rm -f extract echo Building alexellis2/href-counter:latest docker build --no-cache -t alexellis2/href-counter:latest . rm ./app
當你運行 build.sh
腳本,它須要構建第一個鏡像,從中建立一個容器來複制工件,而後構建第二個鏡像。這兩個鏡像在您的系統上佔用空間,而且您的本地磁盤上仍然有 app
工件。app
多階段構建極大地簡化了這種狀況!工具
對於多階段構建,能夠在 Dockerfile 中使用多個 FROM
語句。每一個 FROM
指令均可以使用不一樣的基鏡像,而且它們都開始了構建的新階段。您能夠選擇性地將工件從一個階段複製到另外一個階段,捨棄在最終鏡像中您不想要的全部內容。爲了說明這是如何工做的,讓咱們使用多階段構建調整前一節中的 Dockerfile。
Dockerfile
:
FROM golang:1.7.3 WORKDIR /go/src/github.com/alexellis/href-counter/ RUN go get -d -v golang.org/x/net/html COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=0 /go/src/github.com/alexellis/href-counter/app . CMD ["./app"]
您只須要一個 Dockerfile。您也不須要單獨的構建腳本。只要運行 docker build
。
$ docker build -t alexellis2/href-counter:latest .
最終的結果是與前面相同的微小生產鏡像,而且顯著下降了複雜性。您不須要建立任何中間鏡像,也不須要將任何工件提取到本地系統中。
它是如何工做的?第二個 FROM
指令用 alpine:latest
鏡像做爲基礎,開始一個新的構建階段。COPY --from=0
行只將前一階段的構建工件複製到這個新階段。Go SDK 和任何中間工件都會被留下,不會保存在最終的鏡像中。
默認狀況下,沒有對階段進行命名,能夠經過它們的整數來引用它們,FROM
指令的第一個整數從 0 開始。可是,您能夠經過添加一個 AS <NAME>
到 FROM
指令來命名階段。下面示例經過命名階段並在 COPY
指令中使用名稱改進了前面一個示例。這意味着,即便 Dockerfile 中的指令稍後被從新排序,COPY
也不會破壞。
FROM golang:1.7.3 AS builder WORKDIR /go/src/github.com/alexellis/href-counter/ RUN go get -d -v golang.org/x/net/html COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /go/src/github.com/alexellis/href-counter/app . CMD ["./app"]
在構建映像時,沒必要構建包括每一個階段的整個 Dockerfile。你能夠指定目標構建階段。如下命令假設你正在使用以前的 Dockerfile
,可是在名爲 builder
的階段中止:
$ docker build --target builder -t alexellis2/href-counter:latest .
這可能很是強有力的幾個場景是:
調試(debug)
階段和一個精益的 生產(production)
階段測試(testing)
階段,在這個階段你的應用會被測試數據填充,可是在構建產品時,使用一個使用真實數據的不一樣階段。當使用多階段構建時,您不受限於從 Dockerfile 中先前建立的階段進行復制。您可使用 COPY --from
指令從單獨的鏡像中進行復制,可使用本地鏡像名稱、本地或 Docker 註冊表上可用的標籤或標籤 ID。Docker 客戶端會在必要時拉取鏡像並從中複製工件。語法是:
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
在使用 FROM
指令時,您能夠引用前一階段的內容。例如:
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