この記事は、 Docker Advent Calendar 2022 の 23日目の記事だ。
空いていたので埋めちゃうよ。
この記事では、 bash や ash で任意のコードの実行後、ターミナルを終了せずに入力待ちにする方法について紹介する。
特に、 docker run
の実行後に、その環境を維持したまま入力待ちにすることを考える。
例えば、 Windows コマンドプロンプトや PowerShell であれば、 CMD /K ***
オプションや、 -NoExit -Command ***
オプションで実現できるような内容だ。
本来なら、 docker build にてその任意コードの実行後の内容をイメージにするべきだが、 わざわざ build するまでもないとか、 build できない事情などもあるかもしれない。
ということで、 bash の場合と、 alpine などで使われる BusyBox ash それぞれについて、 docker run
実行時に任意コード実行後、ターミナルの入力待ちにする方法を紹介する。
bash の場合
bash の場合、 --rcfile
オプションにて、起動時に実行するコマンドを指定できる。
ただし、 --rcfile
はファイルを指定する必要があるため、 替わりにプロセス置換で実行コードを与えてやる方法をとる。
user@hostmachine:~$ docker run --rm -it debian:bullseye bash -c "bash --rcfile <(echo 'ls && export '\''FOO=B A R'\'' && MY_TIME=\$(date)')"
bin dev home lib64 mnt proc run srv tmp var
boot etc lib media opt root sbin sys usr
root@container:/# echo $FOO : $MY_TIME
B A R : Thu Dec 22 15:00:00 UTC 2022
root@container:/#
プロセス置換はコンテナ内で実行される必要があるため、一旦 bash -c
にてコンテナ内で bash 実行させ、その中で改めて --rcfile
オプションを指定した bash を起動する流れとなる。
実際に実行したいコマンドは、 echo で文字列として書き出す。
上記例では、 ls && export 'FOO=B A R' && MY_TIME=$(date)
と言う文字列を echo させている。
引用符が二重三重になっていて、エスケープが非常に難しくなっているので注意。
BusyBox ash
alpine 3.15 以降に含まれる BusyBox の ash であれば、意外にもプロセス置換が使える。
しかし、 --rcfile
に相当するオプションは残念ながら無い。
替わりに、 ash には ENV
という環境変数に記載されたファイルを ash 起動時に実行する機能がある。
これを使おう。
user@hostmachine:~$ docker run --rm -it alpine:3.15 ash -c "ash -c 'export ENV=\$1;ash' -s <(echo 'ls && export '\''FOO=B A R'\'' && MY_TIME=\$(date)')"
bin etc lib mnt proc run srv tmp var
dev home media opt root sbin sys usr
/ # echo $FOO : $MY_TIME
B A R : Thu Dec 22 15:00:00 UTC 2022
/ #
ash
の -c
のコマンドに対して引数を与える -s
オプションを使ってプロセス置換のファイルを与え、 それを $1
経由で ENV
環境変数にセット。 その状態で再度 ash を起動させれば、 bash と同様のことが行える。
ENV 環境変数に直接プロセス置換のファイルを指定せず、わざわざ一旦引数を経由させているのは、 入力を受け付ける ash プロセスが動いている間、 プロセス置換のファイルにアクセス可能にする必要があるためだ。
例えば、 export ENV=<(echo 'command');ash
と実行しても、 ash 実行の段階ではプロセス置換のファイルが閉じられているので、コマンドは実行されない。
もうちょっと複雑で実用的な例
エスケープが何重にもなっていてややこしいが、書き方さえ気をつければ基本的にどんな内容でも実行可能だ。
apt パッケージマネージャーのリポジトリを書き換えた状態で、 bash を起動する方法 (Ubuntu <= 22.04):
docker run --rm -it ubuntu:22.04 bash -c "bash --rcfile <(echo 'sed -i -E '\''s%^(deb(-src|)\s+)https?://archive\.ubuntu\.com/ubuntu/%\1http://ftp.udx.icscoe.jp/Linux/ubuntu/%'\'' /etc/apt/sources.list && apt update && FooBar=`date -uIs`')"
〃 (Ubuntu 24.04):
docker run --rm -it ubuntu:24.04 bash -c "bash --rcfile <(echo 'sed -i -E '\''s%^(URIs:\s+)https?://archive\.ubuntu\.com/ubuntu%\1http://ftp.udx.icscoe.jp/Linux/ubuntu%'\'' /etc/apt/sources.list.d/ubuntu.sources && apt update && FooBar=`date -uIs`')"
apk パッケージマネージャーのリポジトリを書き換えた状態で、 ash を起動する方法 (Alpine):
docker run --rm -it alpine:3.15 ash -c "ash -c 'export ENV=\$1;ash' -s <(echo 'sed -i -E '\''s%^https?://dl-cdn\.alpinelinux\.org/alpine/%http://ftp.udx.icscoe.jp/Linux/alpine/%'\'' /etc/apk/repositories && apk update && FooBar=`date -uIs`')"
参考: https://stackoverflow.com/questions/74094552/how-not-to-terminate-after-carried-out-commands-in-bash