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