Docker Composeを再起動後に自動起動する方法
再起動後にDocker Composeを復旧する設定を解説します。restart policyの違い、on-failureが再起動後に復旧しない理由、systemdが必要な条件を確認できます。
簡単な答え
Docker Composeのサービスは、2つの条件が同時に満たされると起動時に開始します。Dockerデーモンがシステムサービスとして有効になっていることと、ファイル内の各サービスにunless-stoppedまたはalwaysの再起動ポリシーが設定されていることが必要です。すべてのサービスにrestart: unless-stoppedを追加し、docker compose up -dを1回実行すると、再起動後にコンテナが自動的に再起動します。一般的なケースでは、これ以外の設定は必要ありません。
マウントしたディスク、VPNインターフェース、またはDockerデーモンの起動時にまだ準備できていないネットワーク共有にスタックが依存するなど、起動順序が重要な場合にのみsystemdユニットが必要です。このケースも実際にあり、このガイドの後半で説明します。サービス定義とボリュームの扱いにまだ慣れていない場合は、まずVPSでのDocker Composeの基本を確認してから戻ってきてください。
compose.yamlで再起動ポリシーを設定する
ポリシーはサービスごとに1行で指定します。全体に適用するスイッチはありません。そのため、設定を忘れたサービスは再起動後に停止したままになり、スタックの他のサービスだけが起動します。
services:
app:
image: nginx:1.27
restart: unless-stopped
ports:
- "8080:80"
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_PASSWORD: change-me
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:設定を適用したら、実行中のコンテナからポリシーを読み取ります。
docker compose up -d
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app)これにより unless-stopped が出力されます。no が出力された場合は、ファイルは編集されていますが、コンテナは再作成されていません。
これは最もよくある失敗です。再起動ポリシーはYAMLファイルではなく、コンテナに保存されます。既存のコンテナについては、compose.yaml を編集しても何も変わりません。docker compose restart も役に立ちません。これは同じコンテナオブジェクトを停止して起動するだけで、設定を変更しないためです。docker compose up -d だけがファイルと実行中のコンテナを比較し、ポリシーの変更を検出してコンテナを再作成します。
すぐに再作成したくないコンテナの場合は、ポリシーをその場で変更します。
docker update --restart unless-stopped my-containerYAMLファイルも必ず編集してください。docker update は実行中のコンテナを変更しますが、次回の docker compose up -d でファイルが読み込まれ、以前の値に戻ります。
各再起動値の実際の動作
Docker は4つの値を定義しています。これらの違いは、マシンの再起動時または daemon の再起動時にのみ現れます。
noがデフォルトです。どのような状況でも、container は自動的に再起動されません。alwaysは、container が停止するたびに再起動します。手動で停止した場合でも、次回 Docker daemon が起動すると再び起動します。これは意図しない動作になりやすい点に注意してください。先週、意図的に停止した container が、再起動後に再び実行されています。unless-stoppedはalwaysと同じように動作します。ただし、手動で停止した container は daemon の再起動後も停止したままです。メンテナンスのためにサービスを一時的に停止することがある場合は、この値を使用します。on-failureは、container が0以外の終了コードで終了した場合にのみ再起動します。restart: on-failure:3のように、再試行回数の上限を設定できます。
サーバーの起動中は常に実行しておきたい stack には、unless-stopped が適切なデフォルトです。停止状態の container をそのままにしないようにする場合のみ、always を選択してください。
再起動: on-failure は再起動後も維持されません
慎重そうに見えるため on-failure を選ぶ人は多いですが、最初の再起動後にすべてのコンテナが停止していることに気付きます。理由は定義にあります。on-failure が反応するのは、コンテナプロセスがエラーコードで終了した場合だけです。
再起動はエラーではありません。ホストをシャットダウンすると、systemd は docker.service を停止し、デーモンは各コンテナを意図的に停止します。コンテナは異常終了していないため、ポリシーが反応する対象になりません。起動すると、デーモンは再開が必要なコンテナを確認します。正常に停止した on-failure コンテナはその対象に含まれません。そのまま exited 状態になります。
動作を直接確認できます。サービスに restart: on-failure を設定し、docker compose up -d を実行してから再起動します。その後、次を実行します。
docker compose ps -aサービスは Exited 状態として表示され、Exited (0) 2 minutes ago のようなステータスになります。問題は発生しておらず、エラーとして記録もされません。そのため、原因の特定が難しくなります。ポリシーは定義どおりに動作しています。
on-failure にも用途はあります。ジョブを実行し、異常終了する可能性があるコンテナで、再試行回数を制限し、再起動ループを避けたい場合に適しています。長時間稼働するサービスを再起動後も維持する目的には適していません。
再起動ポリシーが機能するのは、Docker サービスがブート時に起動する場合だけです
再起動ポリシーは Docker デーモンによって適用されます。デーモンが起動しなければ、何も適用されません。確認してください。
systemctl is-enabled docker
systemctl is-enabled containerdどちらも enabled を出力するはずです。Docker 公式リポジトリのパッケージは、インストール時にこれらを有効にします。そのため、新しいサーバーでは通常この確認に合格します。どちらかが disabled を出力する場合は、修正してください。
sudo systemctl enable --now docker containerdここには、理解しておくべき注意点があります。Ubuntu には docker.socket も含まれています。これは、何かが Docker API に初めてアクセスしたときに、オンデマンドでデーモンを起動します。docker.socket が有効になっているのを見て、デーモンも起動対象になっていると判断し、メモリを節約するために docker.service を無効にする人がいます。ブート時には API を呼び出すものがないため、ソケットはアクセスされず、デーモンも起動しません。そのため、最初の docker コマンドを入力するまでコンテナは起動しません。ソケットアクティベーションは、docker.service を有効にする代わりにはなりません。
systemd unitが適切な場合
再起動ポリシーには、システムの他の部分との起動順序を指定する概念がありません。デーモンが起動すると、可能になった時点でコンテナを起動します。スタックが別のボリューム、NFS(ネットワークファイルシステム)共有、または暗号化ディスク上のディレクトリをbind mountしている場合、そのパスが存在する前にコンテナが起動することがあります。Dockerはマウントポイントに空のディレクトリを作成して、そのディレクトリを使ってコンテナを起動します。その結果、データベースはデータなしで起動します。
次のいずれかに該当する場合は、systemd unitを作成してください。スタックの起動前に、マウント、VPNインターフェース、または別のunitを準備する必要がある場合です。systemctl stop myappとsystemctl start myappを、システム上の他のサービスと同じように動作させたい場合です。または、シャットダウン時にデーモンとともに強制終了するのではなく、スタックを正常に停止したい場合です。systemd unitに慣れていない場合は、systemd serviceとtimerの作成でファイル形式を詳しく説明しています。
systemd ユニットの作成
スタックは、ホームディレクトリの外にある固定パスに配置します。/srv/myapp が適しています。ログインするユーザーがまだいない段階で実行されるユニットが、/home を読み取る必要はないためです。
/etc/systemd/system/myapp.service を作成します。
[Unit]
Description=myapp docker compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
RequiresMountsFor=/srv/myapp/data
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target有効化して起動します。
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.service正常なユニットでは Active: active (exited) と表示されます。初めて見ると誤っているように見えますが、これは正しい状態です。Type=oneshot と RemainAfterExit=yes の組み合わせは、ユニットがコマンドを実行して完了し、systemd がユニットを active のまま保持していることを示します。これにより、シャットダウン時に ExecStop が実行されます。
各行には役割があります。Requires=docker.service により、デッドソケットに対して docker compose を実行せず、ユニットはすぐに失敗します。After= は起動順序を設定します。Requires= だけでは順序は設定されません。RequiresMountsFor= により、systemd はそのパスの mount unit を取り込み、マウント完了まで待機します。これが、単なる restart policy ではなくユニットを使用する理由です。TimeoutStartSec=0 により、大きなイメージを pull している間に systemd が start job を強制終了することを防ぎます。
2つの仕組みを組み合わせる場合の注意点です。Docker のドキュメントでは、restart policy とホストのプロセスマネージャーを組み合わせないよう推奨しています。この警告は、コンテナプロセス自体を監視し、daemon と同時に再起動しようとするプロセスマネージャーを対象としています。Type=oneshot ユニットは何も監視しないため、このユニットと併用して compose ファイルに restart: unless-stopped を残しても問題ありません。むしろ、それが適切です。systemd は起動時の順序を処理し、daemon は午前3時にクラッシュしたコンテナを処理します。
実際に再起動して確認する
実際のテストに代わるものはありません。systemctl restart docker ではマウントの順序を検証できません。また、docker compose down の後に docker compose up -d を実行しても、ブートに関する検証は一切できません。
sudo reboot待機してから再接続し、次の順序で確認します。
uptime
systemctl is-active docker
docker compose psuptime により、実際に再起動したマシンを確認できます。スタックのディレクトリから実行する docker compose ps では、すべてのサービスが running として表示され、稼働時間はマシンの稼働時間に近くなるはずです。Exited と表示されるサービスを確認してください。
起動しなかったものがある場合は、デーモンのログでブート時の期間を確認できます。
journalctl -u docker.service -b --no-pager | tail -50unit で管理されるスタックでは、journalctl -u myapp.service -b --no-pager により、ブート時の正確な docker compose 出力を確認できます。これには、失敗したイメージの pull や、存在しない .env ファイルに関する情報も含まれます。
自動起動をひそかに妨げる要因
docker compose run で作成したコンテナには、ファイルの再起動ポリシーが適用されません。Compose はこれらを一時的なコンテナとして扱います。サービスがポリシーを無視しているように見える場合は、up ではなく run で起動されていないか確認してください。
volume または env_file エントリ内の相対パスは、compose ファイルがあるディレクトリを基準に解決されます。これはシェルから実行した場合に機能します。また、WorkingDirectory を設定した unit から実行した場合にも機能します。WorkingDirectory を設定していない unit から実行すると失敗します。この場合、作業ディレクトリは / になるためです。
Rootless Docker は別のケースです。daemon は user service として実行されます。user service は、そのユーザーの最後のセッションが終了すると停止します。ユーザー用に有効化し、ログインしているユーザーがいなくても実行を継続できるようにします。
systemctl --user enable docker
sudo loginctl enable-linger $USERenable-linger がない場合、ログアウトすると rootless daemon が停止し、コンテナも停止します。これは、再起動ポリシーが壊れている場合とまったく同じように見えます。
最後に、セキュリティ更新の自動適用によって、サーバーが指定した時刻に再起動されることがあります。これは、stack が自動的に復旧する場合に限って有効です。新しいマシンでこれを設定する作業は、新しい VPS で最初の10分間に行う作業 に含まれます。
FAQ
restart: always と restart: unless-stopped の違いは何ですか?
どちらも、コンテナが自動的に停止した場合は再起動します。手動でコンテナを停止した後の動作が異なります。always では、次回 Docker daemon が起動したときにコンテナも再起動するため、再起動すると手動停止が解除されます。unless-stopped では、daemon がコンテナの意図的な停止を記憶し、そのまま停止状態を維持します。特に停止状態を維持する必要がなければ、unless-stopped を使用してください。
restart: unless-stopped を追加したのに、再起動後もコンテナが起動しないのはなぜですか?
ポリシーはファイルではなくコンテナに保存されます。既存のコンテナは YAML を編集しても更新されません。docker compose up -d を実行して Compose にコンテナを再作成させ、その後 docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app) で確認してください。出力が no の場合、そのコンテナは編集前に作成されたものです。もう1つの一般的な原因は docker.service が有効になっていないことです。systemctl is-enabled docker で確認できます。
restart ポリシーを使用している場合、systemd unit は必要ですか?
通常は必要ありません。ネットワークだけを必要とする stack では、restart ポリシーで十分です。これは多くの stack に当てはまります。Docker daemon の起動時点で準備できていないものにコンテナが依存する場合は、unit を追加してください。たとえば、外部ディスク、暗号化された volume、NFS share、VPN interface などです。unit を使用すると、restart ポリシーでは指定できない After= と RequiresMountsFor= による起動順序を設定できます。
次回の再起動後に stack が復帰しないよう、完全に停止するにはどうすればよいですか?
unless-stopped では、手動で停止したコンテナは daemon の再起動時に再開されないため、docker compose stop だけで十分です。always では、停止するだけでは不十分で、再起動後にコンテナが復帰します。docker compose down を実行してコンテナを削除するか、先に docker update --restart no my-container でポリシーを変更してください。systemd unit が stack を管理している場合は、sudo systemctl disable myapp.service も実行してください。実行しないと、unit が stack を再度起動します。