【Docker】マルチステージビルドにおけるFROM命令の挙動
はじめに
Dockerイメージをビルドする際、マルチステージビルドを用いることでイメージのサイズを削減することができます。マルチステージビルドのその他の用途として、ベースステージをもとに環境に応じた複数のイメージをビルドする用途にも有効です。
今回はFROM
命令で別ステージを参照した際の挙動について検証してみました。
前提条件
以下環境で検証しています。
- macOS Monterey(12.5.1)
- Docker Desktop 4.0.1
Dockerfileの構成
検証に使用するDockerfileは以下の通りです。
###################
# ベースステージ
###################
FROM alpine:latest AS base
ENV MSG="hello world"
RUN echo "### base ###"
###################
# 開発環境
###################
FROM base AS dev
RUN echo "### dev ###"
CMD ["sh", "-c", "echo dev && echo ${MSG}"]
###################
# 本番環境ビルダー
###################
FROM base AS prod-builder
RUN echo "### prod-builder ###"
RUN echo "${MSG} from prod" > /sample.txt
###################
# 本番環境
###################
FROM base AS prod
COPY --from=prod-builder /sample.txt sample.txt
RUN echo "### prod ###"
CMD ["sh", "-c", "echo prod && cat sample.txt"]
base
は全環境共通のベースステージです。後続のステージで使用する環境変数や実行環境などを定義します。
dev
は開発環境用のイメージです。FROM
命令でbase
を参照して開発に必要なライブラリやツールをインストールします。CMD
命令で開発用のサーバを起動します。
prod-builder
は、本番環境のコードを生成するビルダーです。FROM
命令でbase
を参照した後、ビルドに必要なライブラリやツールをインストールします。ソースコードをローカルからコピーしてビルド処理を実行します。
prod
は本番環境用のイメージです。FROM
命令でbase
を参照します。COPY
命令で、prod-builder
でビルドした成果物を自分のイメージにコピーし、CMD
命令で本番用のサーバを起動します。
開発環境のビルドと実行
--target
オプションにdev
ステージを指定して開発環境用のDockerイメージをビルドします。prod-builder
とprod
の処理は実行されず、base
とdev
の処理内容でDockerイメージがビルドされました。
$ docker image build \
--target dev \
--no-cache \
-t sample-dev:latest .
[+] Building 8.5s (7/7) FINISHED
=> [internal] load build definition from Dockerfile 0.4s
=> => transferring dockerfile: 684B 0.0s
=> [internal] load .dockerignore 0.5s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/alpine:latest 3.7s
=> CACHED [base 1/2] FROM docker.io/library/alpine:latest@sha256:bc41 0.0s
=> [base 2/2] RUN echo "### base ###" 1.3s
=> [dev 1/1] RUN echo "### dev ###" 1.5s
=> exporting to image 0.9s
=> => exporting layers 0.8s
=> => writing image sha256:aa37fe84da100e4842e4addde3221b1389fe8822e1 0.0s
=> => naming to docker.io/library/sample-dev:latest 0.0s
ビルドしたDockerイメージを使用してコンテナを実行します。開発環境用のメッセージとbase
から引き継いだ環境変数が表示されました。
$ docker container run --rm sample-dev:latest
dev
hello world
本番環境のビルドと実行
--target
オプションにprod
ステージを指定して開発環境用のDockerイメージをビルドします。今度はdev
の処理がスキップされ、base
、prod-builder
、prod
の処理内容でDockerイメージがビルドされました。
$ docker image build \
--target prod \
--no-cache \
-t sample-prod:latest .
[+] Building 9.8s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.4s
=> => transferring dockerfile: 683B 0.0s
=> [internal] load .dockerignore 0.5s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/alpine:latest 1.0s
=> CACHED [base 1/2] FROM docker.io/library/alpine:latest@sha256:bc41 0.0s
=> [base 2/2] RUN echo "### base ###" 1.3s
=> [prod-builder 1/2] RUN echo "### prod-builder ###" 1.5s
=> [prod-builder 2/2] RUN echo "hello world from prod" > /sample.txt 1.4s
=> [prod 1/2] COPY --from=prod-builder /sample.txt sample.txt 0.6s
=> [prod 2/2] RUN echo "### prod ###" 1.4s
=> exporting to image 1.2s
=> => exporting layers 1.1s
=> => writing image sha256:f8b2e2895b844537a3342173da1b8db3c8ef452cb3 0.0s
=> => naming to docker.io/library/sample-prod:latest 0.0s
--target
を指定しないでビルドした場合も前述と同様の挙動になります。最後に書かれたprod
のビルドに必要な処理のみ実行されます。
$ docker image build \
--no-cache \
-t sample-prod:latest .
[+] Building 17.8s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.7s
=> => transferring dockerfile: 683B 0.0s
=> [internal] load .dockerignore 0.4s
=> => transferring context: 2B 0.1s
=> [internal] load metadata for docker.io/library/alpine:latest 8.7s
=> CACHED [base 1/2] FROM docker.io/library/alpine:latest@sha256:bc41 0.0s
=> [base 2/2] RUN echo "### base ###" 1.3s
=> [prod-builder 1/2] RUN echo "### prod-builder ###" 1.5s
=> [prod-builder 2/2] RUN echo "hello world from prod" > /sample.txt 1.5s
=> [prod 1/2] COPY --from=prod-builder /sample.txt sample.txt 0.5s
=> [prod 2/2] RUN echo "### prod ###" 1.6s
=> exporting to image 1.2s
=> => exporting layers 1.1s
=> => writing image sha256:fcd7054aed2fc87f7399e70e1b4b9243a7d5ad7f91 0.0s
=> => naming to docker.io/library/sample-prod:latest 0.0s
ビルドしたDockerイメージを使用してコンテナを実行します。本番環境用のメッセージと、prod-builder
からコピーした成果物ファイルの内容が表示されました。
docker container run --rm sample-prod:latest
prod
hello world from prod
まとめ
環境ごとにステージを用意して、それぞれFROM
命令でベースイメージを参照することで、一つのDockerfileで複数環境のDockerイメージのビルドが実現できました。ベースステージからの参照ルートを環境ごとに分離することで不要なステージの処理をスキップでき、ビルド時間の短縮にも有効です。