Docker Compose v2.17.3 から、 dockerfile_inline
構文で compose.yaml
の中に Dockerfile をインライン記述できるようになった。
compose.yaml
ファイル一つで複数のコンテナを立ち上げられるので、地味に便利。
さて、この機能における変数の扱いについての理解に関して…、
突然だが Docker クイズ!!
以下の compose.yaml
をビルドしたら、 /out.txt
には何が出力されているだろうか?
services:
inline:
build:
context: .
dockerfile_inline: |
FROM alpine
ARG arg3="dockerfile"
ARG argDupl="dockerfile"
RUN export arg2=buildcontainer \
&& export argDupl=buildcontainer \
&& cat <<EOS > /out.txt
--------
arg2=$arg2
arg3=$arg3
arg2=\$arg2
arg3=\$arg3
--------
EOS
RUN cat /out.txt
docker compose --progress=plain build --no-cache
オプションを付けてビルドし、 /out.txt
のダンプがどうなるか見てみよう。
果たして、正解は…?
ドロドロドロドロドロドロ (ドラムロール音)
\デーン!!!/
#6 [inline 3/3] RUN cat /out.txt
#6 0.496 --------
#6 0.496 arg2=
#6 0.496 arg3=
#6 0.496 arg2=arg3=--------
#6 DONE 0.5s
正解できただろうか?
解説
解説というほど複雑な話ではないが、もし間違えたなら compose.yaml
自身にも環境変数の展開機能があることを見落としていたのかもしれない。
.env
ファイルに変数を設定する、よく使うアレだ。
dockerfile_inline
構文内の Dockerfile で変数を使う場合、以下のように 3種類 の変数展開を考慮する必要がある。
compose.yaml
- Dockerfile
- ヒアドキュメントを実行するシェル
このうち、 RUN 構文内については Dockerfile レイヤーでの変数展開はされず、ビルドコンテナ内の環境変数経由で変数が渡される。
このため、 compose.yaml
と ヒアドキュメント の変数展開だけ気をつければ良い。
やっかいなエスケープの問題
まず、以下の出力結果を見てみよう。
#6 0.403 arg2=arg3=--------
なぜこのような出力になってしまうのか。その理由は、
services:
inline:
build:
context: .
dockerfile_inline: |
FROM alpine
RUN cat <<EOS > /out.txt
arg2=\$arg2
arg3=\$arg3
--------
EOS
RUN cat /out.txt
この部分が compose.yaml
の変数展開により
RUN export arg2=buildcontainer \
&& export argDupl=buildcontainer \
&& cat <<EOS > /out.txt
arg2=\
arg3=\
--------
EOS
RUN cat /out.txt
こう展開され、 末尾の \
がヒアドキュメント内の改行のエスケープと解釈されてしまったためだ。
正しいエスケープ方法
この問題を解決するには、変数展開のレイヤーごとに適切なエスケープ方法を使う必要がある。
具体的には:
compose.yaml
内の変数展開は$$
でエスケープ- Dockerfile の変数は
\$
でエスケープ(今回の例では関係しない) - ヒアドキュメントのシェルは
\$
でエスケープ
以下の例で確認してみよう。
services:
inline:
build:
context: .
dockerfile_inline: |
FROM alpine
ARG arg3="dockerfile"
ARG argDupl="dockerfile"
RUN export arg2=buildcontainer \
&& export argDupl=buildcontainer \
&& cat <<EOS > /out.txt
========
arg1=$arg1
arg2=$$arg2
arg3=$$arg3
argDupl=$$argDupl
escaped=\$$ESCAPED
========
EOS
RUN cat /out.txt
bash での実行
$ arg1=hostenv docker compose --progress=plain build --no-cache
PowerShell での実行
PS> $env:arg1='hostenv'; docker compose --progress=plain build --no-cache
出力:
...
#6 [inline 3/3] RUN cat /out.txt
#6 0.407 ========
#6 0.407 arg1=hostenv
#6 0.407 arg2=buildcontainer
#6 0.407 arg3=dockerfile
#6 0.407 argDupl=buildcontainer
#6 0.407 escaped=$ESCAPED
#6 0.407 ========
#6 DONE 0.4s
...
上記のように、
$arg
で、ホストマシンや.env
ファイルの環境変数$$arg
で、 Dockerfile の変数 および ビルドコンテナのシェル変数\$$arg
で、どちらもエスケープされた"$arg"
という文字列
がそれぞれ展開されることがわかる。
$argDupl
を見ればわかるように、 Dockerfile の変数よりも、ビルドコンテナ内で定義された変数のほうが優先される。
ヒアドキュメントの変数展開は <<'EOS'
などとすることで変数やコマンドの置換は行われなくなるが、compose.yaml
内の変数展開は当然回避されない。
このため、
services:
inline:
build:
context: .
dockerfile_inline: |
RUN cat <<'EOS' > /out.txt
foobar $$hoge
EOS
と $$
でのエスケープは回避できないとにも注意だ。
まとめ
Docker Compose の dockerfile_inline
の変数展開について重要な点をまとめると:
- 変数展開は3つのレイヤーで行われる
compose.yaml
の変数展開- Dockerfile の変数展開
- シェルの変数展開(ヒアドキュメント)
- 変数の記述方法と展開結果
$var
- ホストマシンや.env
ファイルの環境変数が展開される$$var
- Dockerfile の変数やビルドコンテナのシェル変数が展開される\$$var
- 文字列 "$var" として扱われる
- ビルドコンテナで設定された環境変数は Dockerfile での設定より優先度が高い
これらの挙動を理解しておけば、 dockerfile_inline
をもっと使いこなせるはずだ。