マルチステージ ビルドを使う¶
マルチステージ ビルドが出る以前¶
イメージ構築にあたり最もチャレンジングなものの1つに、イメージ容量を小さくし続けるというものがあります。Dockerfile 中の命令ごとにイメージにレイヤーを追加するため、次のレイヤーへと移る前に、不要なアーティファクト(訳者注:構築処理によって作成されたイメージ等)を忘れずクリーンアップし続ける必要があります。これまで本当に効率的な Dockerfile を書くためには、以降のレイヤーで必要になるアーティファクトのみを保持するために、シェル芸(shell tricks)を駆使する必要と、レイヤーを維持するロジックを用いる必要がありました。
実際にとても一般的になったのは、開発用途に1つのファイル(アプリケーションの構築に必要な全てを含む)を使うことです。そして、本番環境向けに、そのアプリケーションを実行するために必要なものだけを含む、1つのレイヤーへとスリムダウンすることです。これが従来はずっと「
以下の Dockerfile.build
と Dockerfile
は、この構築パターンを遵守した例です。
Dockerfile.build
:
# syntax=docker/dockerfile:1
FROM golang:1.16
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 .
また、この例では2つの RUN
コマンドを Bash の &&
演算子を使い1行にまとめ、イメージ中に追加レイヤーが増えないのを防いでいます。ですが、これは失敗しがちでメンテナンスが大変です。これは、他のコマンドの連結が簡単ですが、場合によっては行の最後で \
文字を使うのを忘れがちです。
Dockerfile
:
# syntax=docker/dockerfile:1
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app ./
CMD ["./app"]
build.sh
:
#!/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
スクリプトを実行するにあたり、まずイメージを構築する必要があります。コンテナを作成し、そこからアーティファクトをコピーし、2つめに構築するイメージにコピーします。ローカルディスク上で両イメージがシステム上で場所を取るだけでなく、 app
アーティファクトも同様に場所をとります。
マルチステージ ビルドは、この状況をとてもシンプルにします。
マルチステージ ビルドを使う¶
マルチステージ ビルドでは、 Dockerfile の中で複数の FROM
命令文を使います。各 FROM
命令は、異なるベースを使い、それを使って新しい構築ステージを始めます。あるステージから別のステージに対し、コピーするアーティファクトを選べるため、最終イメージで不要なすべてを残したままにできます。これがどのような挙動か確認するために、先ほどのセクションで使った Dockerfile をマルチステージ ビルドに対応させましょう。
Dockerfile
:
# syntax=docker/dockerfile:1
FROM golang:1.16
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"]
あなたが必要なのは1つの Dockerfile だけです。構築スクリプトを分ける必要はありません。 docker build
を実行するだけです。
$ docker build -t alexellis2/href-counter:latest .
最終結果は、先ほどと同じ小さなプロダクション・イメージですが、複雑さは極めて減少しました。もうこれで中間イメージを作成する必要はありませんし、ローカルシステム上にアーティファクトを展開する必要も、もうありません。
どのような挙動でしょうか? 2つめの FROM
命令は、 alpine:latest
をベースとして新しい構築ステージを開始します。 COPY --from=0
行が、以前のステージで構築したアーティファクトを、この新しいイメージの中にコピーします。Go SDK や他の中間アーティファクトは残したままであり、最終イメージの中に保存しません。
構築ステージに名前を付ける¶
デフォルトでは、ステージに名前がなく、ステージを 0 で始まる整数値で参照します。しかし、 FROM
命令の中で AS <名前>
を追加することにより、ステージに対して名前を付けられます。先ほどの例を改善し、ステージに対して名前を付け、その名前を COPY
命令で使います。つまり、Dockerfile に記述する( FROM )命令の順番を入れ替えたとしても、 COPY
命令は壊れません。
# syntax=docker/dockerfile:1
FROM golang:1.16 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 含まれる各イメージを全て構築する必要はありません。特定のターゲット(target)構築ステージを指定できます。以下のコマンドは、以前の 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`` 命令を使って参照できます。以下は例です。
# 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
バージョン互換性¶
マルチステージ ビルド構文は、 Docker Engine 17.05 で導入されました。
参考
- Use multi-stage builds
https://docs.docker.com/develop/develop-images/multistage-build/