Dockerfile 構文の最新の更新の概要 (v1.7.0)


Dockerfile は、Dockerを使用する開発者にとって基本的なツールであり、Dockerイメージを作成するための青写真として機能します。 これらのテキストドキュメントには、ユーザーがコマンドラインで呼び出して画像を組み立てることができるすべてのコマンドが含まれています。 Dockerfile を理解して効果的に活用することで、開発プロセスが大幅に合理化され、イメージ作成の自動化が可能になり、開発のさまざまな段階で一貫した環境を確保できます。 Dockerfileは、プロジェクト環境、依存関係、およびDockerコンテナ内のアプリケーションの構成を定義する上で極めて重要です。

新しいバージョンの BuildKit ビルダーツールキットDocker Buildx CLI、および BuildKit の Dockerfile フロントエンド (v1.7.0) では、開発者は、強化された Dockerfile 機能にアクセスできるようになりました。 このブログ記事では、これらの新しい Dockerfile 機能について掘り下げ、それらをプロジェクトで活用して Docker ワークフローをさらに最適化する方法について説明します。

2400x1260 dockerfile イメージ

バージョン管理

始める前に、Dockerfile のバージョン管理方法と、更新するために何をすべきかを簡単に説明します。 

ほとんどのプロジェクトでは Dockerfile を使用してイメージをビルドしますが、BuildKit はその形式だけに限定されません。 BuildKit は、BuildKit が処理するビルドステップを定義するために、複数の異なるフロントエンドをサポートしています。 これらのフロントエンドは誰でも作成でき、通常のコンテナー イメージとしてパッケージ化し、ビルドを呼び出すときにレジストリから読み込むことができます。

新しいリリースでは、そのような 2 つのイメージ docker/dockerfile:1.7.0 (と .docker/dockerfile:1.7.0-labs

これらのフロントエンドを使用するには、ファイルの先頭にディレクティブを指定し #syntax て、ビルドに使用するフロントエンドイメージを BuildKit に指示する必要があります。 ここでは、最新の 1.x.x メジャーバージョンを使用するように設定しています。 例えば:

#syntax=docker/dockerfile:1

FROM alpine
...

これは、BuildKit が Dockerfile フロントエンド構文から切り離されていることを意味します。 使用している BuildKit のバージョンを気にすることなく、新しい Dockerfile 機能をすぐに使い始めることができます。 この記事で説明するすべての例は、Dockerfile の先頭に正しい #syntax ディレクティブを定義している限り、BuildKit ( Docker の時点での既定のビルダー ) をサポートするすべてのバージョンの Docker で動作します。23

Dockerfile フロントエンドのバージョンの詳細については、ドキュメントをご覧ください。 

変数展開

Dockerfile を記述する場合、ビルド ステップには、ビルド引数 ()ARG と環境変数 ()ENV の命令を使用して定義された変数を含めることができます。 ビルド引数と環境変数の違いは、環境変数は結果のイメージに保持され、そこからコンテナーが作成されたときに保持されることです。

このような変数を使用する場合は、ほとんどの場合、OR、より簡単に言えば、 $NAME inCOPYRUN、、およびその他のコマンドを使用します${NAME}

Dockerfile が Bash のような変数展開の 2 つの形式をサポートしていることを知らないかもしれません。

  • ${variable:-word}: 変数が 設定されていない場合 に値 word を設定します
  • ${variable:+word}: 変数が 設定されている 場合に値 word を設定します

この時点までは、命令の ARG 既定値を直接設定できるため、これらの特殊な形式は Dockerfile ではあまり役に立ちませんでした。

FROM alpine
ARG foo="default value"

さまざまなシェルアプリケーションの専門家であれば、Bashやその他のツールには通常、スクリプトの開発を容易にするための変数拡張の多くの追加形式があることをご存知でしょう。

Dockerfile v1.7、 以下を追加しました。

  • ${variable#pattern} 変数 ${variable##pattern} の値から最短または最長の接頭辞を削除します。
  • ${variable%pattern} 変数 ${variable%%pattern} の値から最短または最長の接尾辞を削除します。
  • ${variable/pattern/replacement} パターンの出現箇所を最初に置き換えるには
  • ${variable//pattern/replacement} パターンのすべての出現箇所を置き換えるには

これらのルールがどのように使用されるかは、最初は完全には明らかではないかもしれません。 それでは、実際のDockerfileで見られるいくつかの例を見てみましょう。

たとえば、依存関係をダウンロードするためのバージョンに "v" プレフィックスを付けるかどうかについて、プロジェクトが合意できないことがよくあります。 以下を使用すると、必要な形式を取得できます。

# example VERSION=v1.2.3
ARG VERSION=${VERSION#v}
# VERSION is now '1.2.3'

次の例では、同じプロジェクトで複数のバリアントが使用されています。

ARG VERSION=v1.7.13
ADD https://github.com/containerd/containerd/releases/download/${VERSION}/containerd-${VERSION#v}-linux-amd64.tar.gz / 

マルチプラットフォームビルドで異なるコマンド動作を設定するために、BuildKit には や TARGETARCHなどの便利な組み込み変数TARGETOSが用意されています。残念ながら、すべてのプロジェクトが同じ値を使用しているわけではありません。 たとえば、コンテナーと Go エコシステムでは、 64ビット ARM アーキテクチャ arm64を と呼びますが、代わりに必要な aarch64 場合もあります。

ADD https://github.com/oven-sh/bun/releases/download/bun-v1.0.30/bun-linux-${TARGETARCH/arm64/aarch64}.zip /

この場合、URL には AMD64 アーキテクチャのカスタム名も使用されます。 変数を複数の展開で渡すには、前の値から展開された別の ARG 定義を使用します。 また、複数のパラメータを使用できるため、すべての定義を 1 行に記述することもできますが、 ARG 読みやすさを損なう可能性があります。

ARG ARCH=${TARGETARCH/arm64/aarch64}
ARG ARCH=${ARCH/amd64/x64}
ADD https://github.com/oven-sh/bun/releases/download/bun-v1.0.30/bun-linux-${ARCH}.zip /

上記の例は、ユーザーが独自の --build-arg ARCH=valueを渡した場合、その値がそのまま使用されるように記述されていることに注意してください。

それでは、新しい拡張パックがマルチステージビルドでどのように役立つかを見てみましょう。

「高度なマルチステージ ビルド パターン」で説明されている手法の 1 つは、ビルド引数を使用して、build-arg の値に応じてさまざまな Dockerfile コマンドを実行する方法を示しています。たとえば、マルチプラットフォーム イメージをビルドし、特定のプラットフォームに対してのみ追加の COPY OR RUN コマンドを実行する場合に、このパターンを使用できます。 この方法が初めての場合は、 その投稿から詳細を学ぶことができます。

要約すると、グローバルなビルド引数を定義してから、ビルド引数名でビルド引数の値を使用し、ビルド引数名を使用してターゲットステージのベースを指すビルドステージを定義するという考え方です。

古い例:

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 …

このパターンをマルチプラットフォーム ビルドに使用する場合、制限の 1 つは、build-arg に指定できるすべての値を Dockerfile で定義する必要があることです。 Dockerfileは、特定のセットに制限するのではなく、任意のプラットフォームでビルドできるようにビルドする必要があるため、これは問題です。 

Dockerfileの他の例は すべてのアーキテクチャに対してダミーのステージエイリアスを定義する必要があり、他のアーキテクチャを構築できない他の例をここと こちら で見ることができます。 代わりに、特別な振る舞いを持つアーキテクチャが 1 つあり、他のすべてが別の共通の振る舞いを共有するというパターンを使用します。

新しい拡張では、RISC-Vでのみ特別なコマンドを実行することを示すためにこれを記述できますが、これはまだやや新しく、カスタム動作が必要になる場合があります。

#syntax=docker/dockerfile:1.7

ARG ARCH=${TARGETARCH#riscv64}
ARG ARCH=${ARCH:+"common"}
ARG ARCH=${ARCH:-$TARGETARCH}

FROM --platform=$BUILDPLATFORM alpine AS base-common
ARG TARGETARCH
RUN echo "Common build, I am $TARGETARCH" > /out

FROM --platform=$BUILDPLATFORM alpine AS base-riscv64
ARG TARGETARCH
RUN echo "Riscv only special build, I am $TARGETARCH" > /out

FROM base-${ARCH} AS base

これらの ARCH 定義を詳しく見てみましょう。

  • 1 つ目は ARCH 、値に設定され TARGETARCH ますが、 riscv64 値から削除されます。
  • 次に、前述したように、実際には他のアーキテクチャが独自の値を使用するのではなく、すべてのアーキテクチャが共通の値を共有するようにします。 そこで、前のriscv64ルールからクリアされた場合を除いて設定ARCHcommonしました。 
  • ここで、まだ空の値がある場合は、デフォルトで $TARGETARCHに戻ります。
  • 最後の定義は、どちらの場合もすでに一意の値を持っているため、オプションですが、最終的なステージ名 base-riscv64 が読みやすくなります。

共有条件に複数の条件を含める場合や、アーキテクチャのバリエーションに基づく条件を含めるその他の例については、こちらの GitHub Gist ページを参照してください。

この例をステージ間の条件の最初の例と比較すると、新しいパターンはビルドのプラットフォームの違いを制御するだけでなく、任意の build-arg で使用できます。 以前にこのパターンを使用したことがある場合は、以前は "if" 句のみに制限されていましたが、"else" 句を効果的に定義できるようになりました。

親ディレクトリを保持したままコピーする

次の機能が "labs" チャネルでリリースされました。 この機能を使用するには、Dockerfile の先頭で以下を定義します。

#syntax=docker/dockerfile:1.7-labs

たとえば、Dockerfile 内のファイルをコピーする場合は、次のようにします。

COPY app/file /to/dest/dir/

この例は、ソースファイルがコピー先ディレクトリに直接コピーされることを意味します。 ソースパスがディレクトリの場合、そのディレクトリ内のすべてのファイルが宛先パスに直接コピーされます。

次のようなファイル構造の場合はどうなりますか?

.
├── app1
│   ├── docs
│   │   └── manual.md
│   └── src
│       └── server.go
└── app2
    └── src
        └── client.go

内のapp1/srcファイルのみをコピーしますが、コピー先の最終ファイルは ではなく になります/to/dest/dir/app1/src/server.go/to/dest/dir/server.go

新しい COPY --parents フラグを使用すると、次のように記述できます。

COPY --parents /app1/src/ /to/dest/dir/  

これにより、ディレクトリ内の src ファイルがコピーされ、これらのファイルのディレクトリ構造が再作成されます app1/src

ワイルドカードパスを使い始めると、より強力になります。 両方のアプリのディレクトリを src それぞれの場所にコピーするには、次のように記述します。

COPY --parents */src/ /to/dest/dir/ 

これにより、 と /to/dest/dir/app2の両方/to/dest/dir/app1が作成されますが、ディレクトリはdocsコピーされません。以前は、この種のコピーは 1 つのコマンドでは不可能でした。 ( この例に示すように)個々のファイルに対して複数のコピーが必要だったか、代わりに命令で RUN --mount 回避策を使用しました。

また、二重スターのワイルドカード ()** を使用して、任意のディレクトリ構造のファイルと照合することもできます。 たとえば、Go ソース コード ファイルのみをビルド コンテキストの任意の場所にコピーするには、次のように記述します。

COPY --parents **/*.go /to/dest/dir/

すべての COPY ./ ファイルをコピーするのではなく、特定のファイルをコピーする必要がある理由を考えている場合は、ビルドに新しいファイルを含めるとビルドキャッシュが無効になることに注意してください。 すべてのファイルをコピーすると、ファイルが追加または変更されるとキャッシュが無効になりますが、Go ファイルのみをコピーすると、これらのファイルの変更のみがキャッシュに影響します。

新しい --parents フラグは、ビルド コンテキストからの命令だけでなく COPY 、明らかに、を使用してステージ COPY --from間でファイルをコピーするときに、マルチステージ ビルドでも使用できます。 

構文では COPY --from 、すべてのソースパスは絶対パスであると想定され、フラグがそのようなパスで使用されると --parents 、ソースステージと同様に完全に複製されることに注意してください。 それは必ずしも望ましいとは限らず、代わりに、一部の親を保持し、他の親を捨てて置き換えたいと思うかもしれません。 その場合は、ソースパスで特別な /./ 相対ピボットポイントを使用して、コピーする親と無視する親をマークできます。 この特別なパスコンポーネントは、 rsync フラグの扱い --relative 方と似ています。

#syntax=docker/dockerfile:1.7-labs
FROM ... AS base
RUN ./generate-lot-of-files -o /out/
# /out/usr/bin/foo
# /out/usr/lib/bar.so
# /out/usr/local/bin/baz

FROM scratch
COPY --from=base --parents /out/./**/bin/ /
# /usr/bin/foo
# /usr/local/bin/baz

上記の例は、中間ステージが生成したファイルのコレクションからディレクトリのみが bin コピーされるが、すべてのディレクトリはディレクトリに対する out 相対パスを保持する方法を示しています。 

除外フィルター

次の機能が "labs" チャネルでリリースされました。 この機能を使用するには、Dockerfile の先頭で以下を定義します。

#syntax=docker/dockerfile:1.7-labs

手順ADDを使用して COPY Dockerfile 内のファイルを移動する場合の別の関連するケースは、ファイルのグループを移動したいが、特定のサブセットを除外する場合です。以前は、 RUN --mount 除外されたファイルを .dockerignore ファイル。 

.dockerignore ただし、ファイルはクライアント側のビルド コンテキストから除外されたファイルのみを一覧表示し、リモートの Git/HTTP URL からのビルドからは除外されず、 Dockerfile ごとに 1 つに制限されるため、この問題の適切な解決策ではありません。 これらは、プロジェクトの一部ではないファイルをマークする場合と同様に .gitignore 使用する必要がありますが、アプリケーション固有のビルド ロジックを定義する方法としては使用しないでください。

新しい --exclude=[pattern] フラグを使用すると、このような除外フィルター COPY ADD を Dockerfile で直接定義できるようになりました。 このパターンは、 と同じ .dockerignore形式を使用します。

次の例では、ディレクトリ内の Markdown ファイルを除くすべてのファイルをコピーします。

COPY --exclude=*.md app /dest/

フラグを複数回使用して、複数のフィルターを追加できます。 次の例では、Markdown ファイルと という READMEファイルを除外します。

COPY --exclude=*.md --exclude=README app /dest/

二重スター付きワイルドカードは、コピーされたディレクトリ内のMarkdownファイルだけでなく、サブディレクトリ内のMarkdownファイルも除外します。

COPY --exclude=**/*.md app /dest/

のように .dockerignore ファイルでは、除外の例外を次のように定義することもできます。 ! 接頭辞。 次の例では、コピーされたディレクトリ内のすべての Markdown ファイルを除外しますが、ファイルが呼び出され important.md た場合 (その場合でもコピーされます)。

COPY --exclude=**/*.md --exclude=!**/important.md app /dest/

この二重否定は最初は混乱するかもしれませんが、これは前の除外ルールの逆転であり、「インクルードパターン」は命令の COPY sourceパラメータによって定義されることに注意してください。

前述の--parentsコピー・モードと一緒に使用する--exclude場合、除外パターンは、コピーされた親ディレクトリーまたはピボット・ポイント /./ (定義されている場合) を基準にしていることに注意してください。例については、次のディレクトリ構造を参照してください。

assets
├── app1
│   ├── icons32x32
│   ├── icons64x64
│   ├── notes
│   └── backup
├── app2
│   └── icons32x32
└── testapp
    └── icons32x32
COPY --parents --exclude=testapp assets/./**/icons* /dest/

このコマンドにより、以下のディレクトリ構造が作成されます。 プレフィックスを持つ icons ディレクトリのみがコピーされ、ルートの親ディレクトリ assets は相対ピボットポイントの前にあるようにスキップされ、さらに、 testapp 除外フィルターで定義されたためコピーされないことに注意してください。

dest
├── app1
│   ├── icons32x32
│   └── icons64x64
└── app2
    └── icons32x32

結論

この投稿が Dockerfile を改善するためのアイデアを提供し、ここに示したパターンがビルドをより効率的に記述するのに役立つことを願っています。 Dockerfile は、まだ最新の Docker に更新していなくても、一番上の行を定義する #syntax ことで、これらすべての機能を今すぐ使い始めることができることに注意してください。

新しい BuildKit、Buildx、Dockerfile リリースのその他の機能の完全なリストについては、変更ログを確認してください。

これらの機能の実装に協力してくれたコミュニティメンバーの @tstenner@DYefimov@leandrosansilva に感謝します。

共有したい問題や提案がある場合は、課題トラッカーでお知らせください。

さらに詳しく