LoginSignup
12
2

More than 3 years have passed since last update.

マルチステージビルドなDockerfileのARGでやらかしがちな件について

Last updated at Posted at 2020-10-12

TL;DR

マルチステージビルドな Dockerfile で ARG を使う場合は、スコープがステージ内に閉じていることに注意が必要です!ちゃんと各ステージごとに使う ARG を宣言しましょう!
また、複数のステージで使う ARG にデフォルト値があるのなら、グローバルな ARG を定義してそこにデフォルト値を記載しましょう!

こんな感じで作ればオッケーです!

Dockerfile
# 各ステージで共通して利用する ARG にデフォルト値があるのなら、最初の FROM より前に定義する。
ARG YOUR_ARG="Default value"

FROM alpine:latest as first_stage
# ARG は、利用する各ステージごとに宣言する必要がある。
ARG YOUR_ARG
RUN echo "1st stage: ${YOUR_ARG}"

FROM alpine:latest as second_stage
# ARG は、利用する各ステージごとに宣言する必要がある。
ARG YOUR_ARG
RUN echo "2nd stage: ${YOUR_ARG}"

マルチステージビルドと ARG の関係性について

マルチステージビルドでは、 ARG や ENV のスコープはステージごとに制限されているらしいです。
皆さんご存知でしたか?わたくしは作った Dockerfile のステージ内で ARG の値を参照できていないことに気づいて初めて知りました。

こちらのコメントによれば、

Correct, Dockerfile instructions, including ENV vars and ARG are scoped per build-stage, and will not be preserved in the next stage; this is by design.
You can, however, set a global ARG (set before the first build-stage), and use that value in each build-stage;
(意訳)ENV や ARG を含む Dockerfile の命令はビルドステージごとのスコープになっており、次のステージに持ち越されません。これは設計上そのようになっています。しかしながら、グローバルな ARG(最初のビルドステージより前で定義する)を使えば、ビルドステージごとにその値を利用することができます。

とのことです。マジか。

公式ドキュメントにもしっかり載ってましたわ。

ARG 命令の変数スコープは、それが定義されたビルドステージが終了するときまでです。 複数のビルドステージにおいて ARG を利用する場合は、個々に ARG 命令を指定する必要があります。

最初にドキュメント読んだときは、ビルドステージの意味も分からないまま「あーそーゆーことね完全に理解した」と思ったような気が。。。

ARG の動きを見てみる

では実際にマルチステージビルドでの ARG の動きを見ていきましょう。

本稿の動作確認環境

本稿の執筆にあたっては、下記のバージョンで動作を確認しています。

# docker --version
Docker version 18.09.1, build 4c52b90

Dockerfile

今回の検証で使う Dockerfile は次の通りです。

Dockerfile
# ARG1 は、グローバルに宣言し、グローバルなデフォルト値を設定する。
ARG ARG1="arg1 global default value"
# ARG2 は、グローバルに宣言するが、グローバルなデフォルト値は設定しない。
ARG ARG2
# ARG3 は、グローバルに宣言しない。
# ARG ARG3


FROM alpine:latest as first_stage

# first_stage では、各 ARG を宣言し、スコープ内のデフォルト値を設定する。
ARG ARG1="arg1 first stage value"
ARG ARG2="arg2 first stage value"
ARG ARG3="arg3 first stage value"
RUN echo -e "first_stage:\n\tARG1=${ARG1}\n\tARG2=${ARG2}\n\tARG3=${ARG3}"


FROM alpine:latest as second_stage

# second_stage では、各 ARG を宣言するが、スコープ内のデフォルト値は設定しない。
ARG ARG1
ARG ARG2
ARG ARG3
RUN echo -e "second_stage:\n\tARG1=${ARG1}\n\tARG2=${ARG2}\n\tARG3=${ARG3}"


FROM alpine:latest as third_stage

# third_stage では、すべての ARG を宣言しない。
# ARG ARG1
# ARG ARG2
# ARG ARG3
RUN echo -e "third_stage:\n\tARG1=${ARG1}\n\tARG2=${ARG2}\n\tARG3=${ARG3}"

各 ARG は下記のように設定しています。

ARG 名 設定内容
ARG1 グローバルな ARG として設定。デフォルト値も指定。
ARG2 グローバルな ARG として設定。デフォルト値は指定しない。
ARG3 グローバルな ARG として設定しない。

また、各ステージは次のように設定しています。

Stage 名 設定内容
first_stage 各 ARG を宣言。ステージ内のデフォルト値を設定。
second_stage 各 ARG を宣言。ステージ内のデフォルト値は設定しない。
third_stage ARG を宣言しない。

--build-arg 指定なしでビルド

Docker Image のビルド時に、 --build-arg による値の指定を行わずにビルドしてみます。

# docker build . --no-cache
Sending build context to Docker daemon  14.85kB
...snip...
Step 7/14 : RUN echo -e "1st stage:\n\tARG1=${ARG1}\n\tARG2=${ARG2}\n\tARG3=${ARG3}"
 ---> Running in 2bbe78634ee8
first_stage:
        ARG1=arg1 first stage value
        ARG2=arg2 first stage value
        ARG3=arg3 first stage value
...snip...
Step 12/14 : RUN echo -e "2nd stage:\n\tARG1=${ARG1}\n\tARG2=${ARG2}\n\tARG3=${ARG3}"
 ---> Running in 0c28af93ea9b
second_stage:
        ARG1=arg1 global default value
        ARG2=
        ARG3=
...snip...
Step 14/14 : RUN echo -e "3rd stage:\n\tARG1=${ARG1}\n\tARG2=${ARG2}\n\tARG3=${ARG3}"
 ---> Running in cbca9ed88691
third_stage:
        ARG1=
        ARG2=
        ARG3=
...snip...
  • first_stage では、いずれもステージ内のデフォルト値となりました。 ARG1 を見ると、グローバルなデフォルト値よりもステージ内のデフォルト値が優先されていることがわかります。これは、よりスコープが狭い方の値を優先的に使う動きになっており、理にかなっている動きと言えるでしょう。
  • second_stage では、 ARG1 のみグローバルなデフォルト値が表示されています。ステージ内で ARG を宣言はしたものの、何も値を指定しなければグローバルなデフォルト値を利用するということですね。 ARG2, ARG3 には何も表示されていませんが、これはその値を指定している場所がどこにもないからですね。
  • third_stage ではすべての値が表示されていません。 ARG1 のグローバルなデフォルト値すら表示されていないということは、 ARG を利用するためにはそのステージごとに宣言する必要があるということでしょう。

--build-arg 指定ありでビルド

今度は --build-arg による値の指定ありでビルドしてみます。

# docker build --build-arg ARG1="build arg1 value" --build-arg ARG2="build arg2 value" --build-arg ARG3="build arg3 value" . --no-cache
Sending build context to Docker daemon  14.85kB
...snip...
Step 7/14 : RUN echo -e "1st stage:\n\tARG1=${ARG1}\n\tARG2=${ARG2}\n\tARG3=${ARG3}"
 ---> Running in 10b37c5a524b
first_stage:
        ARG1=build arg1 value
        ARG2=build arg2 value
        ARG3=build arg3 value
...snip...
Step 12/14 : RUN echo -e "2nd stage:\n\tARG1=${ARG1}\n\tARG2=${ARG2}\n\tARG3=${ARG3}"
 ---> Running in e70e3ff9fe9b
second_stage:
        ARG1=build arg1 value
        ARG2=build arg2 value
        ARG3=build arg3 value
...snip...
Step 14/14 : RUN echo -e "3rd stage:\n\tARG1=${ARG1}\n\tARG2=${ARG2}\n\tARG3=${ARG3}"
 ---> Running in e675e8f648e8
third_stage:
        ARG1=
        ARG2=
        ARG3=
...snip...
  • first_stage, second_stage ともにすべての値が --build-arg で指定した値になりました。これは公式ドキュメントの ARG のデフォルト値で説明されている内容と一致しますね。 --build-arg で指定された値はグローバルなデフォルト値及びステージ内のデフォルト値のどちらよりも優先されます。
  • third_stage ではまたもやすべての値が表示されていません。 --build-arg で値が指定されていても、そのステージ内で宣言されていなければやはり利用できないようです。

まとめ

  • マルチステージビルドな Dockerfile で ARG を利用する際には、すべてのステージでそれぞれ利用する ARG を宣言する必要がある。
  • 複数のステージで利用している ARG のデフォルト値を定義したければ、グローバルな ARG を定義(最初の FROM よりも前に定義)してそこにデフォルト値を記載すると良い。
  • ARG の値の優先度は
    1. --build-arg での指定値 [優先度高]
    2. ステージ内のデフォルト値
    3. グローバルなデフォルト値 [優先度低]

おしまい。

宣伝

マルチステージビルドの ARG の挙動に気づくきっかけとなったプロジェクトの宣伝です。

Minecraft の Bedrock Server を楽に運用しようぜ!という趣旨のプロジェクトです。
Minecraft ってたまーにアップデートが入るのですが、クライアントは大体勝手にアップデートされるものの、 Bedrock Server は自動的にアップデートされず、手動で新しいバージョンをダウンロードして Zip ファイルを展開してバイナリファイルを置き換えてサービスを再起動して・・・とかやる必要がありました。
このプロジェクトは、一度セットアップだけやってしまえば、あとは全自動で Bedrock Server をアップデートしてくれます!ついでに Docker を使って Bedrock Server を構築するので環境を汚しません!

よろしくお願いします!

本当におしまい。

12
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
2