9月 2024 日更新: 今日、ほとんどの人がBuildKitを使用しており、ブログはこれを反映するように更新されています。
Dockerfiles のマルチステージ ビルド機能を使用すると、より優れたキャッシュとセキュリティ フットプリントを備えた小さなコンテナー イメージを作成できます。 このブログ記事では、ビルドステージとランタイムステージの間でファイルをコピーするだけでなく、機能を最大限に活用できる、より高度なパターンをいくつか紹介します。 マルチステージビルドを初めて使用する場合は、まず使用ガイドを読むことから始めることをお勧めします。
ビルドキットに関する注記
ここにあるすべてのパターンは古いビルダーでも機能しますが、それらの多くは BuildKit バックエンドを使用するとより効率的に実行されます (バージョン 23.0以降の Docker Engine のデフォルト)。 たとえば、BuildKit は未使用のステージを効率的にスキップし、可能な場合はステージを同時にビルドします。 これらのケースを個々の例の下にマークしました。 これらのパターンを使用する場合は、BuildKit を有効にすることを強くお勧めします。 他のすべての BuildKit ベースのビルダーもこれらのパターンをサポートしています。
• • •
ステージからの継承
マルチステージ ビルドでは、いくつかの新しい構文の概念が追加されました。 まず、コマンドで始まるFROM
AS stagename
ステージに名前を付け、コマンドでCOPY
オプションを使用して--from=stagename
、そのステージからファイルをコピーできます。実際、コマンドと--from
フラグにははるかに多くの共通点があり、FROM
それらが同じ名前であることは偶然ではありません。どちらも同じ引数を取り、それを解決してから、その時点から新しいステージを開始するか、ファイルコピーのソースとして使用します。
つまり、使用できる--from=stagename
のと同じ方法で、前のステージを現在のステージのソースイメージとして使用することもできますFROM stagename
。 これは、Dockerfile 内の複数のコマンドが同じ共通部分を共有している場合に便利です。 これにより、共有コードが小さくなり、子ステージを分離したままにして、子ステージを分離して、一方が再構築されたときに他のステージのビルドキャッシュが無効にならないようにします。 各ステージは、 を呼び出してdocker build
フラグを使用して--target
個別に構築することもできます。
FROM ubuntu AS base
RUN apt-get update && apt-get install git
FROM base AS src1
RUN git clone …
FROM base AS src2
RUN git clone …
BuildKit では、この例の 2 番目と 3 番目のステージが同時にビルドされます。
画像を直接使用する
以前は画像参照のみをサポートしていたコマンドでFROM
ビルドステージ名を使用するのと同様に、これを好転させて、フラグ付きの--from
画像を直接使用できます。 これにより、他の画像から直接ファイルをコピーできます。 たとえば、次のコードでは、image を使用して linuxkit/ca-certificates
TLS CA ルートを現在のステージに直接コピーできます。
FROM alpine
COPY --from=linuxkit/ca-certificates / /
共通イメージのエイリアス
ビルドステージにはコマンドを含める必要はなく、1FROM
行でもよい。 複数の場所でイメージを使用している場合、これは読みやすさを向上させ、共有イメージを更新する必要があるときに 1 行だけを変更する必要があることを確認するのに役立ちます。
FROM alpine:3.6 AS alpine
FROM alpine
RUN …
FROM alpine
RUN …
この例では、画像 alpine
を使用する場所は実際には alpine:3.6
そうではありません alpine:latest
。 に更新alpine:3.7
するときは、 変更する必要があるのは1行だけで、ビルドのすべての部分が更新されたバージョンを使用していることを確認できます。
これは、エイリアスで build 引数を使用するとさらに強力になります。 次の例は前の例と同じですが、ユーザーはオプションを設定する--build-arg ALPINE_VERSION=value
ことで、このビルドで使用されているすべてのインスタンスをオーバーライドできます。コマンドで使用されるFROM
引数は、最初のビルド段階の前に定義する必要があることに注意してください。
ARG ALPINE_VERSION=3.6
FROM alpine:${ALPINE_VERSION} AS alpine
FROM alpine
RUN …
'‐‐from' でのビルド引数の使用
コマンドの COPY
flag で指定された--from
値にビルド引数を含めることはできません。たとえば、次の例は無効です。
// THIS EXAMPLE IS INTENTIONALLY INVALID
FROM alpine AS build-stage0
RUN …
FROM alpine
ARG src=stage0
COPY --from=build-${src} . .
これは、ビルドを開始する前にステージ間の依存関係を決定する必要があるため、毎回すべてのコマンドを評価する必要がないためです。 たとえば、image でalpine
定義された環境変数は、値の評価--from
に影響を与える可能性があります。 コマンドの引数を評価できる理由は、FROM
これらの引数がステージを開始する前にグローバルに定義されているためです。 幸いなことに、前に学習したように、1 つのFROM
コマンドでエイリアス ステージを定義し、代わりにそれを参照することができます。
ARG src=stage0
FROM alpine AS build-stage0
RUN …
FROM build-${src} AS copy-src
FROM alpine
COPY --from=copy-src . .
ビルド引数src
をオーバーライドすると、最終COPY
要素のソースステージが切り替わるようになりました。 これにより一部のステージが使用されなくなった場合、BuildKit ベースのビルダーのみがこれらのステージを効率的にスキップして実行されないようにする機能を持っていることに注意してください。
ビルド引数を使用する条件
Dockerfiles にスタイル条件のサポートを追加するIF/ELSE
要求がありました。 このようなものが将来追加されるかどうかはまだ不明です — BuildKit のカスタムフロントエンドサポートの助けを借りて、将来それを試すかもしれません。 一方、いくつかの計画では、現在の多段階の概念を使用して同様の動作を得る可能性があります。
// THIS EXAMPLE IS INTENTIONALLY INVALID
FROM alpine
RUN …
ARG BUILD_VERSION=1
IF $BUILD_VERSION==1
RUN touch version1
ELSE IF $BUILD_VERSION==2
RUN touch version2
DONE
RUN …
前の例は、 を使用して条件を記述する方法を擬似コードで示IF/ELSE
しています。 現在のマルチステージビルドで同じ動作を行うには、異なるブランチ条件を個別のステージとして定義し、引数を使用して正しい依存関係パスを選択する必要があります。
ARG BUILD_VERSION=1
FROM alpine AS base
RUN …
FROM base AS branch-version-1
RUN touch version1
FROM base AS branch-version-2
RUN touch version2
FROM branch-version-${BUILD_VERSION} AS after-condition
FROM after-condition
RUN …
この Dockerfile の最後のステージは、ビルド引数によってBUILD_VERSION
解決されるイメージのエイリアスであるステージに基づいていますafter-condition
。の値に応じてBUILD_VERSION
、異なる中間セクション ステージが選択されます。
BuildKit ベースのビルダーのみが未使用のブランチをスキップできることに注意してください。 以前のビルダーでは、すべてのステージが引き続きビルドされていましたが、最終的なイメージを作成する前にその結果が破棄されていました。
最小限の生産段階のための開発/テストヘルパー
最後に、前のパターンを組み合わせて、最小限の運用イメージを作成し、その内容を使用してテストの実行や開発イメージを作成できる Dockerfile を作成する方法を示す例を紹介します。 基本的な Dockerfile の例から始めます。
FROM golang:alpine AS stage0
…
FROM golang:alpine AS stage1
…
FROM scratch
COPY --from=stage0 /binary0 /bin
COPY --from=stage1 /binary1 /bin
これは、最小限の運用イメージを作成する場合によく発生します。 しかし、代替の開発者イメージを取得したり、最終段階でこれらのバイナリを使用してテストを実行したりする場合はどうでしょうか。 明らかな方法は、同じバイナリをテスト段階と開発者段階にもコピーすることです。 問題は、すべての本番バイナリを同じ組み合わせでテストするという保証がないことです。 最終段階で何かが変更され、他のステージに同じ変更を加えるのを忘れたり、バイナリがコピーされるパスを間違えたりする可能性があります。 結局のところ、個々のバイナリではなく、最終的なイメージをテストしたいと思います。
別のパターンは、運用ステージの後に開発者とテスト ステージを定義し、運用ステージの内容全体をコピーすることです。 その後、実動ステージの 1 つのFROM
コマンドを使用して、実動ステージを最後のステップとして再びデフォルトにすることができます。
FROM golang:alpine AS stage0
…
FROM scratch AS release
COPY --from=stage0 /binary0 /bin
COPY --from=stage1 /binary1 /bin
FROM golang:alpine AS dev-env
COPY --from=release / /
ENTRYPOINT ["ash"]
FROM golang:alpine AS test
COPY --from=release / /
RUN go test …
FROM release
デフォルトでは、このDockerfileはデフォルトの最小イメージの構築を続行します。
--target=dev-env
オプションは、常に完全なリリースバイナリを含むシェルでイメージをビルドするようになりました。
• • •
これがお役に立てば幸いで、より効率的な多段階Dockerfileを作成するためのアイデアが得られました。 BuildKit リポジトリを使用して、より効率的なビルドと新しい Dockerfile 機能のために新しい開発を追跡できます。サポートが必要な場合は、 Docker コミュニティ Slack の #buildkit チャンネルに参加できます。