【Docker】Dockerfile RUNで、BASHな環境変数を展開してコマンド実行する

恐らく禁じ手

ハマった事象

DockerFileを書くとき、ARGやENVな変数をコマンド内で使うときは $ENVNAME で書くが、 少なくともDocker verions 17.05.0-ceでは、Dockerfile中の$ENVNAMEプリプロセッサ的に事前に展開してコマンド実行するようだ。

FROM alpine

ARG SOMEARG=1
ENV SOMEENV=hehehe
RUN CMDENV=foo

RUN echo \"$SOMEARG\" \"$SOMEENV\" \"$CMDENV\"

実行結果

yu@yu:~/test$ docker build --rm .
Sending build context to Docker daemon  4.096kB
Step 1/5 : FROM alpine
 ---> caf27325b298
Step 2/5 : ARG SOMEARG=1
 ---> Using cache
 ---> 1efffbdfdb5c
Step 3/5 : ENV SOMEENV hehehe
 ---> Using cache
 ---> 0b585450b6db
Step 4/5 : RUN CMDENV=foo
 ---> Using cache
 ---> 1e28e889dfdf
Step 5/5 : RUN echo \"$SOMEARG\" \"$SOMEENV\" \"$CMDENV\"
 ---> Running in f3083830fbf4
"1" "hehehe" ""
 ---> 5648473c7075
Removing intermediate container f3083830fbf4
Successfully built 5648473c7075
yu@yu:~/test$

echoした結果に注目。 RUN CMDENV=foo がきいていない。

"1" "hehehe" ""

恐らく?だが、RUN echo \"$SOMEARG\" \"$SOMEENV\" \"$CMDENV\" の箇所でコマンドラインで実行されるのはecho "1" "hehehe" "" になっているからだと思う。(Dockerのコード見る気力がないのでこれ以上は勘弁)

それでも実行したい

バージョン変わるとつぶされるかもしれないが、いったんは下記でhackできた。

RUN `echo 実行したいコマンド \$環境変数名`
yu@yu:~/test$ cat
Dockerfile        env.sh            LoadConfigScript
yu@yu:~/test$ cat Dockerfile
FROM alpine

ARG SOMEARG=1
ENV SOMEENV=hehehe

RUN CMDENV=hoo \
    && `echo echo \$SOMEARG \$SOMEENV \$CMDENV` \
    && `echo mkdir \$SOMEARG \$SOMEENV \$CMDENV` \
    && `echo ls -l \$SOMEARG  \$SOMEENV  \$CMDENV`
yu@yu:~/test$
yu@yu:~/test$ docker build --rm .
Sending build context to Docker daemon  4.096kB
Step 1/4 : FROM alpine
 ---> caf27325b298
Step 2/4 : ARG SOMEARG=1
 ---> Using cache
 ---> 1efffbdfdb5c
Step 3/4 : ENV SOMEENV hehehe
 ---> Using cache
 ---> 0b585450b6db
Step 4/4 : RUN CMDENV=hoo     && `echo echo \$SOMEARG \$SOMEENV \$CMDENV`     && `echo mkdir \$SOMEARG \$SOMEENV \$CMDENV`     && `echo ls -l \$SOMEARG  \$SOMEENV  \$CMDENV`
 ---> Running in f482c9fbcd9c
1 hehehe hoo
1:
total 0

hehehe:
total 0

hoo:
total 0
 ---> f19485b7afb9
Removing intermediate container f482c9fbcd9c
Successfully built f19485b7afb9
yu@yu:~/test$

\はDockefileのエスケープ文字だが、$に対して実施してもコマンドラインで渡しても$はただの文字として機能する。 しかし、などで一度コマンド置換すると、環境変数として評価するみたいだ。

制約

DockefileのbuildはStepごとに中間結果をキャッシュし、Dockefileを修正したときキャッシュのある部分から再ビルドする。 ・・・が、上記例でいうところのRUN CMDENV=hoo環境変数が残留になるスコープは、Step実行して評価されたbuild回でのみ。 (プロセスが一旦落ちるから?)

下記のDockerFileがあったとして、RUN FOO=var のStepがキャッシュによりスキップされた場合、Step2の出力は環境変数FOOが未設定の扱いとなる。

FROM alpine

RUN FOO=var
RUN `echo echo \$FOO`

多分Dockerの中の人たちには、どうしようもないバグとして思われているんだろうなぁ。