Dockerfile の ENV と ARG はどっちも環境変数を設定する
はじめに
Dockerfile には環境変数を設定するための ENV と言うまんまの名前の命令があるが、実は ARG 命令も環境変数を設定する。
これは割と良く知られた話だと思っていたんだが、世の中の Dockerfile を見ていると実は思ったほど知られていないんじゃないかと思うことが度々あったので、極めて基本的な事ではあるが記事としてまとめておこうと思う。
ENV と ARG はどっちも環境変数を設定する
論より証拠。まずは ENV で試してみる。Dockerfile はこんな感じ。
FROM alpine
ENV MSG=message
RUN printenv MSG
これでビルドしてみる。
$ docker build --no-cache --progress plain -f Dockerfile.env -t test:env .
#0 building with "default" instance using docker driver
...中略...
#6 [2/2] RUN printenv MSG
#6 0.276 message
#6 DONE 0.3s
...後略...
想定通り、printenv MSG で message と出力されている。
ちなみに、引数の --no-cache は RUN printenv MSG が毎回実行されるようにするため、--progress plain はビルドログを見やすくするために付けた。
では本命(?)の ARG を使って同じ事をやってみよう。Dockerfile はこんな感じ。
FROM alpine
ARG MSG=message
RUN printenv MSG
ENV が ARG になってる以外は完全に同一だ。
これでビルドしてみる。
$ docker build --no-cache --progress plain -f Dockerfile.arg -t test:arg .
#0 building with "default" instance using docker driver
...中略...
#6 [2/2] RUN printenv MSG
#6 0.235 message
#6 DONE 0.3s
...後略...
見ての通り、ENV の時と同じように printenv MSG で message と出力されている。
と言う訳で、ARG でも ENV と同じように環境変数を設定することが出来た。
ENV と ARG の違い
ENV と ARG のどっちでも環境変数を設定することが出来ることは分かった。では ENV と ARG は同じものかと言うと当然そんな訳はない。ちゃんと意味があって 2 種類あるのだ。
ENV はイメージに残るが ARG は残らない
最も大きな違いは、ENV で設定した環境変数はビルドしたイメージにも残るが、ARG で設定した環境変数はビルド中にしか使えない、と言うことだろう。これは先程ビルドしたイメージを使うと分かる。
まずは ENV を使った方のイメージを実行してみる。
$ docker run --rm test:env sh -c 'echo MSG=$MSG'
MSG=message
見ての通り、環境変数 MSG に Dockerfile で設定した値が残っている。
一方 ARG を使った方のイメージで同じ事をしてみる。
$ docker run --rm test:arg sh -c 'echo MSG=$MSG'
MSG=
こちらは環境変数 MSG が残っていないことが分かる。
つまり、設定した環境変数をビルドしたイメージの実行時にも使いたいのであれば ENV で設定しておく必要があると言うことだ。
逆に言えば、もしその環境変数がビルド時にしか必要ないものなのであれば、指定した環境変数が無意味にイメージに残ってしまう ENV よりも ARG の方が良いんじゃないだろうか。
もちろん RUN MSG=message printenv のように RUN 命令で直接環境変数を設定するのでも良いんだが、複数の RUN 命令に同じ環境変数を設定したい場合には ARG(や ENV)の方が便利じゃないかと思う。
ARG はビルド時にコマンドラインから値を上書きできる
もう一つの違いは、ARG はビルド時のコマンドライン引数で値を上書きできるが、ENV はそれができない、と言うことだ。と言うより、そもそも ARG は argument の略だろうから、この使い方こそ ARG 本来の正しい(?)使い方だろう。
ARG に引数を与えるにはコマンドラインで --build-arg オプションを指定すれば良い。
$ docker build --no-cache --progress plain -f Dockerfile.arg -t test:arg2 . \
--build-arg MSG='hello arg'
#0 building with "default" instance using docker driver
...中略...
#6 [2/2] RUN printenv MSG
#6 0.286 hello arg
#6 DONE 0.3s
...後略...
見ての通り、MSG 環境変数が引数で指定した hello arg になっている。
一方で、ENV はそんなことはできない。
$ docker build --no-cache --progress plain -f Dockerfile.env -t test:env2 . \
--build-arg MSG='hello env'
#0 building with "default" instance using docker driver
...中略...
#6 [2/2] RUN printenv MSG
#6 0.266 message
#6 DONE 0.3s
当たり前だが --build-arg で指定しても反映されてない。
ビルド時に値を指定したいしイメージにも残したいんだけど…
じゃあビルド時にコマンドラインから値を指定したいけど、イメージに環境変数を残したい場合はどうすればいいかと言えば、↓のように単純に併用すればいいだけだ。
FROM alpine
ARG MSG=message
ENV MSG=$MSG
RUN printenv MSG
ビルド時はこんな感じ。
$ docker build --no-cache --progress plain -f Dockerfile.arg-env -t test:arg-env . \
--build-arg MSG='hello arg & env'
#0 building with "default" instance using docker driver
...中略...
#6 [2/2] RUN printenv MSG
#6 0.248 hello arg & env
#6 DONE 0.3s
...後略...
実行時はこんな感じ。
$ docker run --rm test:arg-env sh -c 'echo MSG=$MSG'
MSG=hello arg & env
目的は達せられた。
おまけ:ENV は実行時にコマンドラインから値を上書きできる
これはオマケだが、ENV は実行時のコマンドライン引数で値を上書きできる。
$ docker run --rm -e MSG='hello env' test:env sh -c 'echo MSG=$MSG'
MSG=hello env
まぁより正確に言えば、そもそも実行時には ENV で設定していない環境変数を渡すこともできるので、こんなことだってできる。
$ docker run --rm -e MSG='hello env' test:arg sh -c 'echo MSG=$MSG'
MSG=hello env
ARG で設定した環境変数はイメージには残っていないが、見ての通りコマンドライン引数で環境変数を設定することができた。
ENV はあくまでもイメージ実行開始時の環境変数にデフォルト値を設定するだけだ。
おわりに
と言う訳で、ENV と ARG はどちらも環境変数を設定できること、および、それらの違いについて説明した。
それでは、よい Dockerfile ライフを。
Discussion