docker runで-itオプションをつけてもコンテナが終了する理由
はじめに
Dockerコンテナ内でコマンドを打ちたい時は、コンテナを立ち上げるときのdocker container run
(docker run
)コマンドに-it
オプションをつけると思います。
ではこの-it
とは何をしているのか?
そして-it
をつけてもコンテナが終了してしまい、コンテナに接続できないことがあるのはなぜ?
というのが気になったので、ChatGPTに聞いてみました。
この記事はChatGPTの解答をまとめ、それにいくつか補足をしたものになります。
少しでも理解の助けになれば幸いです。
環境
- Docker 27.2.0
- MacOS
参考
結論
そもそも-it
オプションをつけるとコンテナが持続するようになるケースは結構限られているみたいです。
それがどんなケースなのかというと、実行されるコマンドが対話型のときだと思われます。
対話型のコマンドはたくさんあるのですが、その一つにbash
やsh
などのシェルを立ち上げる系のコマンドがあります。
もっと言うならば、docker container run
にbash
を入れると終了しなくなります。
例えばこんな感じになります。
docker container run -it node:latest bash
これを実行すると、コンテナ内で通常のLinuxコマンドが打てると思います。
コンテナが終了するタイミング
そもそも、Dockerのコンテナはいつ終了するのでしょうか?
そのあたりはこの本が非常にわかりやすかったのでぜひ読んで欲しいのですが、この記事でも触れておこうと思います。
上の本によると、コンテナは一つのプロセスを起動するためにあります。
ここでは便宜上それをメインプロセスと呼ぶことにします。
コンテナはメインプロセスを実行するために起動する という点を押さえておくと、コンテナのことをよりスムーズに理解できるようになります。
ではメインプロセスとはどのように指定するのでしょうか。
それはDockerfile
のCMD
句だったり、docker container run
(docker run
)コマンドの引数だったりします。
そして、メインプロセスが終了すると、コンテナも自動で停止します。
--rm
オプションをつけていた場合は削除もされます。
また、メインプロセスが終了したコンテナは自動で停止する という重要な点を理解できるようになります。
コンテナでプロセスを実行する
では、一度メインプロセスの指定方法を確認しておこうと思います。
前述した通り、メインプロセスの指定方法はいくつかあります。
-
Dockerfile
のCMD
句に書く -
docker container run
の引数に書く - (Docker Composeの場合、
compose.yaml
のcommand
フィールドに書く)
ではここで、Ubuntuをベースとしたコンテナを使って、以下のコマンドを実行してみようと思います。
echo 'Hello World!'
Dockerfile
のCMD
句に書く
Dockerfile
を使う場合、Ubuntuのイメージをベースとして、新しくイメージを作ることになります。
そしてそのイメージでCMD
を指定し、メインプロセスを上書きします。
例えばこのようになります。
# ubuntuのイメージをベースとして使う
FROM ubuntu:24.04
# メインプロセスを上書きする
CMD ["echo", "Hello World!"]
このイメージをビルドし、コンテナを起動してみます。
docker image build --tag my-ubuntu:latest
docker container run my-ubuntu:latest
すると、ターミナルにHello World!
と表示され、コンテナが停止すると思います。
コンテナ一覧を見て確認してみてください。
docker container ls --all
# 実行結果
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d174f2a3bbf8 my-ubuntu:latest "echo 'Hello World!'" About a minute ago Exited (0) About a minute ago jolly_jackson
STATUS
がExited (0)
になっているのが確認できると思います。
Exited
なので停止していて、0
なのでメインプロセスは正常に終了したみたいです。
docker container run
の引数に書く
run
コマンドの引数にコンテナのメインプロセスとなるコマンドを書くこともできます。
その場合は、例えばこのようになります。
docker container run \
ubuntu:24.04 \
echo Hello World!
# 実行結果
Hello World!
無事にecho
コマンドが実行できたようです。
そして、コンテナが停止したのが確認できると思います。
docker container ls --all
# 実行結果
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a76ded1d33a2 ubuntu:24.04 "echo Hello World!" 2 minutes ago Exited (0) 2 minutes ago relaxed_leakey
Dockerfile
の時と同じく、Status
がExited (0)
のようになっているはずです。
ここでコンテナが停止したのは、メインプロセスの実行が終了したからです。
先ほども言った通りコンテナはプロセスを実行するためにあり、そのプロセスが終了すると停止します。
そのため、与えられたコマンドを実行し終わって役目を終えたコンテナは、自動的に停止されます。
また、今回実行したecho
コマンドは即座に実行が終了するコマンドです。
そのため、コンテナが起動するとecho
コマンドが実行されて終了し、同時にコンテナも停止してしまいました。
-it
オプションとは
docker container run
(docker run
)コマンドの-it
とは、主にコンテナをシェルで操作するときに使われるオプションです。
これは-i
オプションと-t
オプションを組み合わせたものであり、それぞれに役割があります。
--interactive
(-i
)
--interactive
(-i
のロング版)オプションは、コンテナの標準入力に接続します。
コンテナの標準入力とホストの標準入力を接続することで、ホストのターミナルに入力した内容がコンテナに反映されるようになります。
コンテナの標準入力は、後述する「対話型のコマンド」が終了するタイミングに影響を与えます。
--tty
(-t
)
--tty
(-t
のロング版)オプションは、コンテナに仮想端末(TTY)を割り当てます。
仮想端末を割り当てることで、ターミナルのような環境を提供するそうです。
ただし、仮想端末が割り当てられていなくてもコマンドを実行することはできます。
仮想端末とは仮想的にターミナルを作ること?みたいです。
詳しくはこちらやこちらが参考になると思います。
仮想端末の有無は、後述する「対話型のコマンド」が終了するタイミングに影響を与えることがあります。
ChatGPTによると与えないこともあるみたいです。
--it
をつけるとコンテナが永続する例
では、--it
オプションをつけることによってコンテナが永続する例を見てみたいと思います。
ここでは先ほどに続き、Ubuntuのイメージを使おうと思います。
まずはオプションなしで普通にコンテナを起動してみます。
docker container run ubuntu:24.04
すると起動はできるものの、すぐにコンテナが停止するのがわかると思います。
では次に、-it
オプションをつけて実行してみます。
docker container run -it ubuntu:24.04
するとプロンプトが変わり、コンテナ内でコマンドが打てるようになったと思います。
先ほどはコンテナがすぐに停止してしまいましたが、これは一体どういうことなのでしょうか?
それを理解するには、まず先にUbuntuのデフォルトコマンドであるbash
、そして対話型のコマンドの特性について知る必要があります。
対話型のコマンドとは
ここで言う「対話型のコマンド」とは、標準入力を待機するコマンドです。
もっと言うなら、起動すると何か入力できるコマンドです。
対話型のコマンドはたくさんありますが、ここではそのうちのいくつかを紹介します。
-
bash
やsh
などのシェルを立ち上げるコマンド -
vim
などのテキストエディタ -
node
やpython
(オプションや引数なし)コマンドで立ち上がるREPL -
mysql
やpsql
などの対話型のツール
なお、対話で使うことはほとんどないと思われるgrep
も、標準入力を受け取れるので対話型のコマンドです。
コマンドの終了タイミング
コンテナ起動時に実行されるコマンドの終了タイミングは、そのままコンテナの生存期間に影響します。
では、対話型のコマンドはいつどのように終了するのでしょうか?
それは、コマンドをどのように実行したかで変わります。
ここではbash
コマンドを例に挙げて解説します。
引数なしで実行する
まずは特に引数やオプションをつけず、通常通り実行してみようと思います。
ターミナル(ホストでもUbuntuのコンテナ内でも可)で、以下のようにコマンドを実行しました。
host$ bash
# プロンプトが変わる
bash-3.2$ echo Hello World!
Hello World! # コマンドが実行できる
見ての通りプロンプトが変わり、bash
のプロセス内で(?)コマンドが実行できるようになりました。
この時exit
コマンドを入力するまで bash
のプロセスは終了していない 点に注目してください。
標準入力でコマンドを入れて実行する
bash
コマンドは、標準入力にコマンドを入れることでそのコマンドを実行してくれます。
ということで、echo
コマンドを使って、echo Hello World!
というコマンドをbash
の標準入力に与えてみようと思います。
echo echo Hello World! | bash
# 実行結果
Hello World!
この時、bash
コマンドは echo Hello World!
を実行し終わった時点で終了している点に注目してください。
また、bash
コマンドは引数にファイルパスを入れることで、そのファイルを実行することができますが、ここでは扱いません。
標準入力に何も入れずに実行する
ChatGPTに聞いたところ、どうやら/dev/null
というのを使うと「空」が表現できるみたいです。
ということで、これをbash
コマンドの標準入力に渡してみます。
bash < /dev/null
すると、bash
コマンドは何もせず終了してしまいました。
プロンプトも変わらず、すぐに終了したみたいです。
対話型のコマンドとDockerコンテナ
前述した通り、対話型のコマンドはユーザーからの入力を受け取るのに標準入力を使用します。
では、Dockerコンテナにおいて標準入力の扱いはどうなっているのでしょうか。
対話型のコマンドは標準入力が空だと終了してしまうので、コンテナのメインプロセスが対話型のコマンドである場合は標準入力の有無がコンテナの停止に直結することになります。
つまり、メインプロセスが対話型のコマンドであるコンテナに、永続するタイプの標準入力を与えることができれば、そのコンテナは永続するということになります。
そんな方法は果たしてあるのでしょうか。いや待て、どこかで似たような言葉を聞いたような...
そう、それがdocker container run
コマンドの-i
(と-t
)オプションです。
流れはこのようになります。
-
-it
オプションによってホストターミナルの標準入力とコンテナの標準入力を結びつける - 対話型のコマンドのプロセスが終了しなくなる
- プロセスを手動で終了するまでコンテナが停止しなくなる
つまり、-it
オプションそのものにコンテナを永続させる効果はなく、対話型のコマンドを永続させることによって間接的にコンテナを永続させることがある、ということになります。
対話型以外ののコマンド
では、対話型以外のコマンドはいつ終了するのでしょうか?
これは標準入力に左右されないため、元々のコマンド固有の動きをすることになります。
例えばls
コマンドを見てみます。
このコマンドは指定されたディレクトリの中身を表示し、すぐに終了します。
ls
のようにすぐに終了するコマンドをコンテナのメインプロセスとして指定した場合、そのコンテナはすぐに終了します。
例えば以下のようなDockerfile
が該当します。
FROM ubuntu:24.04
CMD ["ls"]
これをビルドし、コンテナとして起動してみました。
すると、以下のようにls
コマンドの結果が表示され、コンテナはすぐに終了すると思います。
docker image build --tag my-ubuntu:latest .
docker container run my-ubuntu:latest
# 実行結果
bin
boot
# 略
usr
var
では、ここで-it
オプションをつけてコンテナを起動してみます。
docker container run -it my-ubuntu:latest
# 実行結果
bin boot dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
-i
によってls
コマンドに標準入力が与えられるのですが、ls
は標準入力を受け取らず、標準入力はプロセスの終了タイミングに影響しません。
そのため、-it
オプションをつけてもコンテナはすぐに停止するはずです。
出力が変わる理由
-it
オプションをつけたときとつけなかったときで、ls
コマンドの出力結果が少し変わったことにお気づきでしょうか。
違いは区切りが改行かスペースかの些細なものですが、スペース区切りの方が読みやすいと思います。
これは-it
のうち-t
(--tty
)オプションに由来されるものです。
--tty
オプションの効果は、コンテナに仮想端末を割り当てるというものでした。
そして、仮想端末が割り当てられていると、コマンドの出力がフォーマットされることがあります。
(コマンドが内部で仮想端末を使っている?)
今回は仮想端末によってls
コマンドの結果がフォーマットされ、スペース区切りで表示されるようになったのだと思われます。
結局どうすればいいの?
少し長くなってしまったので、コンテナを停止させず中に入って作業する方法をまとめていきます。
対話型のコマンドを使う
まずは、この記事で紹介した対話型のコマンドを使う方法です。
対話型のコマンドはいろいろありますが、内部でコマンドを叩いて作業をする場合はbash
を使っておけばいいと思います。
また、対話型のコマンドで永続化させる場合は起動時に-it
オプションが必要になります。
例えば、oven/bunというイメージをそのまま起動した場合、-it
をつけてもコンテナはすぐに終了してしまいます。
これはCMD
に指定されているコマンドがすぐに終了するものであることが原因です。
対処方法は簡単で、メインプロセスをbash
に上書きするだけです。
bash
以外の対話型のコマンドでもいけますが、コンテナ内でコマンドを打ちたい場合はシェルを起動するコマンドを使います。
docker container run -it oven/bun:latest bash
やったことは最後にbash
をつけただけです。
一生終わらないコマンドを使う
上で紹介した方法は、標準入力があるか=-it
オプションがあるかどうかでコンテナが永続するかどうかが変わっていました。
それでもいいですが、そもそも対話型のコマンドを使わなくても一生終わらないプロセスをメインプロセスに設定すればいいだけの話でもあります。
今回は一生終わらないプロセスとして、以下のコマンドを使います。(こちらを参考にしました)
tail -F /dev/null
このコマンドをメインプロセスに設定するには、Dockerfile
でCMD
にこれを設定します。
FROM oven/bun:latest
# 上書き
CMD ["tail", "-F", "/dev/null"]
そしてこれをビルドします。
docker image build --tag my-bun .
そしたらコンテナを起動するのですが、この手順には注意が必要です。
- デタッチモード(
-d
)で実行すること- 最悪ターミナルが操作できなくなり、別の場所からコンテナを終了する羽目になる
-
-it
はつけなくてOK- この何もしないプロセスに標準入力は不要
-
-it
をつけるとCtrl+C * 3で停止できなくなる
ということで、早速起動してみます。
docker container run -d my-bun:latest
# 実行結果
97fc7483127fdc1なんたら
デタッチモードで起動したので、コンテナは今バックグラウンドで動いています。
そのためプロンプトは変わっていませんが、これは正しい動作です。
コンテナが停止しなくなったので、次はこのコンテナに接続してみます。
接続にはdocker container exec
という、コンテナ内でコマンドを実行するコマンドを使います。
コマンドを実行する前に、先ほど出力されたコンテナIDをコピーしておいてください。
コマンドはこちらです。
docker container exec -it <コピーしたコンテナID> bash
実行した結果、プロンプトが変わってbun
コマンドが打てるようになれば成功です。
ちなみに、exec
の-it
はrun
の-it
とだいたい同じです。
コンテナに接続するというのはつまり、コンテナ内でbash
コマンドを実行し、そこに
- 現在のターミナルの標準入力をコンテナの標準入力と結びつける
- 仮想端末を割り当てる
ということになります。
結局対話型のコマンド...というかbash
を使っている時点で1つ目の方法と何も変わっていないような気はしますが、一応書いてみました。
Discussion