
よくありがちなことなのですが、Dockerでアプリを動かす場合は、PID 1 のプロセスにしておいた方が良いという話です。
背景
Dockerコンテナ内でDjangoアプリを開発するときに、 migrate と runserver を実行するために、以下のようなDockerfileを作成することがあります。
Dockerfile:
FROM python:3.13 # 環境変数設定 ENV PYTHONUNBUFFERED=1 \ WORKDIR=/app # 作業ディレクトリの作成 RUN mkdir -p $WORKDIR # 作業ディレクトリの設定 WORKDIR $WORKDIR # 依存関係をインストール COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Djangoのmigrate実行後にrunserverを起動 CMD python manage.py migrate && python manage.py runserver
このDockerfileを使用してコンテナを起動すると、 migrate が実行された後に runserver が起動します。
これを以下のような compose.yaml で実行し、PIDの状態を確認してみます。
compose.yaml:
services: app: build: context: . dockerfile: Dockerfile ports: - "8000:8000" volumes: - .:/app
docker compose up -d docker compose exec app ps -ef
結果は以下のようになりました。
UID PID PPID C STIME TTY TIME CMD root 1 0 1 16:50 ? 00:00:00 /bin/sh -c python manage.py migrate && python manage.py runserver root 8 1 18 16:50 ? 00:00:00 python manage.py runserver root 9 8 41 16:50 ? 00:00:00 /usr/local/bin/python manage.py runserver root 11 0 80 16:50 pts/0 00:00:00 ps -ef
さて、ここでPID 1 のプロセスは /bin/sh になっていて、shからrunserverを起動しています。
runserverを実行しているPythonのPIDは 8 です。runserverは内部でDjangoのアプリをスレッドで実行しているので、PID 9 もPythonのプロセスです。
この状態で、 docker compose down を実行すると、コンテナが停止して破棄されるのですが、コマンドを実行してから停止するまでに時間がかかります。10秒ぐらい。
なぜか?
この時間がかかっているのは、PID 1にSIGTERMシグナルを送信してもプロセスが停止しないからです。 Docker composeのFAQに記載があります。
デフォルトではタイムアウトが10秒になっていて、10秒待ってもPID 1 のプロセスが終了しない場合は、強制終了されます。
少し時間が経った後に終了するのは、この強制終了によって終了されているからですね。
解決策
これをきれいに解決するには、 runserver をPID 1 で実行するとよいです。
Dockerfileを以下のように変更します。
FROM python:3.13 # 環境変数設定 ENV PYTHONUNBUFFERED=1 \ WORKDIR=/app # 作業ディレクトリの作成 RUN mkdir -p $WORKDIR # 作業ディレクトリの設定 WORKDIR $WORKDIR # 依存関係をインストール COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Djangoのmigrate実行後にrunserverをexecで起動 CMD python manage.py migrate && exec python manage.py runserver
変更したのは最後のCMDの部分です。execコマンドでrunserverを実行するようにしました。
こうすると、PID 1 のプロセスがrunserverになります。
docker compose buildでイメージをビルドしなおし、composeを起動して、PIDを確認してみます。
docker compose build docker compose up -d docker compose exec app ps -ef
結果は以下のようになりました。
UID PID PPID C STIME TTY TIME CMD root 1 0 20 17:10 ? 00:00:00 python manage.py runserver root 8 1 67 17:10 ? 00:00:00 /usr/local/bin/python manage.py runserver root 10 0 66 17:10 pts/0 00:00:00 ps -ef
PID 1 のプロセスが python manage.py runserver になっています。
これで、 docker compose down を実行すると、すぐにコンテナが停止、破棄されます。
まとめ
Dockerでアプリを動かすときは、PID 1 のプロセスにしておくと、コンテナの停止が早くなります。
exec コマンドを使うことで、起動したプロセスのPIDを起動元のPIDにできます。
補足
本番環境だとgunicornなどでアプリを起動することが多いですが、gunicornの場合も同様にしてPID 1 で起動しておかないと、graceful shutdownにならなかったりするので、注意が必要です。