Docker では各所で ENTRYPOINT と CMD を指定できます。
# Dockerfile
FROM alpine:latest
# []にすると /bin/sh -c がエントリーポイントになります
ENTRYPOINT []
CMD tail -f /dev/null
# docker-compose.yml
app:
build:
context: ./docker/alpine
entrypoint: "/bin/sh -c"
command: '"tail -f /dev/null"'
# docker in コマンドライン
docker run --entrypoint "/bin/sh" alpine:latest -c ls -la
# docker-compose in コマンドライン
docker-compose run --entrypoint /bin/sh app -c ls -la
コマンドライン上ではイメージ名(alpine:latest)やコンテナ名(app)の後にある引数が CMD です。Docker コンテナが動く時、実行されるコマンドは ENTRYPOINT と CMD を連結させたものになります。上記の例では /bin/sh -c teil -f /dev/null、あるいは /bin/sh -c ls -la を実行することになります。
なぜ二つに分かれているかというと CMD は気軽に変更され、ENTRYPOINT はなかなか変更されないメインコマンドとされることが想定されているからです。Dockerfile で定義される CMD はメインコマンドに続くデフォルトの値となっているわけです。このため多様な動作が必要になりやすいサーバ的なイメージの ENTRYPOINT ではなんやかんや最終的に自由なシェルに繋がる様に定義されることが多いです。こうすると動作に必要な準備が整ったシェルを外部から簡単に取り扱えます。また単一のアプリケーションを動かすことを目的としたイメージではメインコマンドがアプリケーションの実行ファイルとなり CMD はそのアプリケーションのデフォルト引数となる、といった場合もいくらかあります。ちなみに Docker 公式イメージの一つである mysql では mysqld のオプションを渡すか何も渡さない場合は mysqld が、そうでない場合はシェルが動くというユーザに優しいつくりになっています。
- Dockerfile 記述のベスト・プラクティス — Docker-docs-ja 17.06 ドキュメント#ENTRYPOINT
- Dockerfile 記述のベスト・プラクティス — Docker-docs-ja 17.06 ドキュメント#CMD
この仕組みを利用するとどの様なイメージでも最終実行コマンドを自在にできます。例えば、問答無用でコンテナを立ち上げ続けるために次のコンテナ定義を使えます。
# Dockerfile
FROM alpine:latest
# []にすると /bin/sh -c がエントリーポイントになります
ENTRYPOINT []
CMD tail -f /dev/null
# docker-compose.yml
app:
build:
context: ./docker/alpine
entrypoint: "/bin/sh -c"
command: '"tail -f /dev/null"'
まず ENTRYPOINT を /bin/sh -c にベースイメージから上書きします。これは -c 以下に渡された文字列を sh で実行するコマンドです。この -c の後、つまり CMD に "tail -f /dev/null" を渡すことで tail -f /dev/null を実行してコンテナを永続的に実行状態にします。
tail の f オプションは対象のファイルを監視し、変更があればそれを表示するオプションです。今回重要なのは監視する、という部分でこれを利用して永続的にコンテナのコマンドを実行状態にします。/dev/null 指定は無用な表示を行わないためです。余談ですが参照先がファイルでなくとも監視をしつづける F オプション(f は参照先がファイルでなければその旨を表示して終了します)を用いると次の様にも永続実行用定義を書けます。
# Dockerfile
ENTRYPOINT []
CMD tail -F dammy_filename
# docker-compose.yml
app:
build:
context: ./docker/alpine
entrypoint: "/bin/sh -c"
command: '"tail -F not-found-file"'
tail -F の後に使う予定のない名前を使用すれば、tail が無用な出力をせずに永続的に実行されます。
他にも ENTRYPOINT を次の様なシェルにすることで CMD をアプリケーションのオプションにするかシェルのコマンドにするかを分岐させられます。
# entry.sh
#!/bin/bash
if [ "${1:0:1}" = '-' ]; then
# 第 1 引数の 0 文字目から 1 文字目までの文字列が - と一致するならば
# 渡された引数(CMD)全体を ffmpeg のオプションとして実行
exec ffmpeg "$@"
else
# もし - で始まっていなければ
# 渡された引数(CMD)全体を単に実行
exec "$@"
fi
# Dockerfile
FROM jrottenberg/ffmpeg:4.3-alpine
WORKDIR /work
COPY ./entry.sh /work/entry.sh
ENTRYPOINT ["/bin/sh", "/work/entry.sh"]
CMD tail -f /dev/null