本番サーバーでの Docker Engine メジャーアップグレード
100 台以上のコンテナを稼働させているサーバーで Docker Engine をアップグレードすることは、即興が通用しない作業です。Hetzner 本番サーバーで Docker Engine 28 から 29、Docker Compose v2 から v5 への移行の時が来たとき、私たちはデータベース移行と同じ規律で臨みました:文書化された準備チェックリスト、テスト済みのロールバックパス、精密な実行手順、そして作業完了を宣言する前の移行後検証フェーズです。
以下は私たちのアプローチの完全な記録です — 各ステップの理由、実際に実行したコマンド、コンテナ数が多いときに設定ミスが困難なリカバリーに連鎖する可能性について注意すべきことです。
このアップグレードが正式なランブックを必要とした理由
Docker Engine 29 は containerd 統合パスに変更を加え、condition: service_healthy を持つ depends_on ブロックでの依存関係解決方法を調整しました。Docker Compose v5 は一方で大規模な内部リライトです — Go ベースの v2 バイナリから、並行起動順序に関する長年の問題を解決する新しいアーキテクチャへ移行しましたが、v2 が静かに無視していたいくつかの Compose ファイルディレクティブも非推奨にしました。
1 つの docker-compose.yml が 12 の相互依存サービス — データベース、リバースプロキシ、アプリケーションコンテナ、バックグラウンドワーカー — を管理するサーバーでは、依存関係解決における静かな動作変更はまさに即座には表面化しない類の問題です。それは 03:00 に、ヘルスチェックが決して実行されず依存コンテナが無限ループする時に表面化します。
実践的な答えは準備であり、それ自体のための慎重さではありません。
準備チェックリスト
パッケージに触れる前に、以下のチェックリストを確認しました。各項目には理由があります。
1. 実行中のすべてのコンテナとその再起動ポリシーを一覧化する。
docker ps --format "table {{.Names}} {{.Status}} {{.Image}}" | sort
docker inspect $(docker ps -q) --format '{{.Name}} restart={{.HostConfig.RestartPolicy.Name}}' | sort
これはベースラインを提供します。restart: always のコンテナは Docker デーモン再起動後に自動的に復帰しようとします — これが望ましい動作ですが、基礎となる compose ファイルとイメージが既知の良好な状態にある場合のみです。アップグレード時にクラッシュループしているコンテナは、その後もクラッシュループし続けます。それを今知っておく方が良いです。
2. アップグレード前にすべての compose ファイルを v5 スキーマに対して検証する。
docker compose config --quiet 2>&1 | grep -i warning
docker-compose.yml を含むすべてのディレクトリでこれを実行します。Compose v5 は非推奨キーについてより厳格です — 特に compose ファイルの先頭の version:(無視されますが警告が生成されます)、および非推奨の links: ディレクティブの使用。v5 の警告は後続のリリースでエラーになる可能性があります;今解決する価値があります。
3. 利用可能なディスクスペースを確認する。
df -h /var/lib/docker
docker system df
アップグレードは新しい containerd シムを取得し Docker Engine バイナリを置き換えます。古いバイナリとその依存関係は常に自動的にクリーンアップされるわけではありません。1 年間稼働しているサーバーでは、docker system df で数ギガバイトの回収可能なイメージレイヤーが明らかになることがよくあります。アップグレード中ではなく、前にこれをクリーンアップします。
docker system prune -f --volumes
匿名ボリュームに必要なデータが含まれていないと確認できる場合のみ --volumes を使用します。私たちのサーバーでは、すべての永続データは名前付きの明示的に宣言されたボリュームからマウントされているため、これは安全でした。
4. 現在の Docker Engine バージョンと compose バージョンを参照ファイルにエクスポートする。
docker version > /root/docker-pre-upgrade.txt
docker compose version >> /root/docker-pre-upgrade.txt
docker ps -a >> /root/docker-pre-upgrade.txt
これは 10 秒で完了し、何か問題が発生してアップグレード時に実行中だったコンテナを特定する必要がある場合の明確なロールバック参照を提供します。
5. 下流システムに通知する。
私たちのサーバーはセルフホスティングの Supabase スタックといくつかの n8n ワークフロー自動化インスタンスを実行しています。コンテナ再起動サイクル中に発火する Webhook は送信側で静かに失敗します。アップタイム監視ツールでメンテナンスウィンドウを設定し、期間中は n8n の着信 Webhook を無効にしました。
ロールバック計画
ロールバック計画は、どの条件がそれをトリガーするかを事前に決定している場合にのみ有用です。私たちのものはシンプルでした:アップグレード前に実行していたコンテナがアップグレード完了から 10 分後に実行されておらず、docker compose up -d で復旧できない場合、Docker Engine バイナリをロールバックします。
Debian ベースのシステムで Docker Engine をロールバックするには、以前のパッケージバージョンを固定します。開始前に正確なバージョン文字列を記録しました:
apt-cache policy docker-ce docker-ce-cli containerd.io docker-compose-plugin
アップグレード前の私たちのサーバーの出力:
docker-ce:
Installed: 5:28.1.1-1~debian.12~bookworm
Candidate: 5:29.0.1-1~debian.12~bookworm
docker-compose-plugin:
Installed: 2.35.1-1~debian.12~bookworm
Candidate: 2.36.0-1~debian.12~bookworm
必要な場合のロールバックコマンド:
apt-get install -y docker-ce=5:28.1.1-1~debian.12~bookworm docker-ce-cli=5:28.1.1-1~debian.12~bookworm containerd.io docker-compose-plugin=2.35.1-1~debian.12~bookworm
このコマンドをサーバーの /root/docker-rollback.sh にテキストファイルとして実行権限付きで保管し、プレッシャー下でタイピングなしに実行できる状態にしました。
アップグレードプロセス
準備が完了すれば、実際のアップグレードは簡単です。順序が重要です:まずパッケージインデックスを更新し、インストールされるものを確認し、それからインストールします。
ステップ 1:Docker リポジトリのみの apt インデックスを更新する。
apt-get update -o Dir::Etc::sourcelist="sources.list.d/docker.list" -o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"
これにより、すべてのソースをリフレッシュするのではなく、Docker リポジトリのみに更新を制限します。本番サーバーでは、Docker メンテナンスウィンドウ中の無関係なソースからの意図しないパッケージ更新は不要な変数です。
ステップ 2:候補バージョンを確認する。
apt-cache policy docker-ce docker-ce-cli containerd.io docker-compose-plugin
進める前に候補が期待したものと一致することを確認します。候補がステージング環境でテストしたバージョンより新しいバージョンを示している場合、一時停止して評価します。
ステップ 3:インストールする。
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
これは Docker デーモンを停止し、バイナリを置き換え、デーモンを再起動します。restart: always のコンテナはデーモン再起動時に起動されます。コンテナが実行されていないウィンドウは、通常サイズのサーバーで通常 30 秒未満です — ただし、containerd の初期化にかかる時間によって異なります。
別のターミナルで再起動を監視します:
watch -n 2 'docker ps --format "table {{.Names}} {{.Status}}" | sort'
ステップ 4:インストールされたバージョンを確認する。
docker version
docker compose version
私たちのサーバーでは、出力が以下を確認しました:
Docker Engine: 29.0.1
Docker Compose: v2.36.0
Docker Compose v5 は docker-compose-plugin パッケージのバージョン 2.36.x として配布されています — "v5" の呼称は内部リライトバージョンを指し、パッケージの semver ではありません。これはドキュメントで混乱を招くことがあります;apt インデックスが追跡するのはパッケージバージョンです。
アップグレード後の検証
アップグレードから 10 分後、構造化された検証パスを実行しました。これはオプションではありません — デーモン再起動はコンテナを起動しますが、ヘルスチェックに依存するコンテナが実際に通過したことは保証しません。
すべてのコンテナが期待された状態にあることを確認する。
docker ps -a --format "table {{.Names}} {{.Status}} {{.RunningFor}}" | sort
Restarting または Exited を示すコンテナは即座に調査が必要です。私たちのサーバーでは、1 つのコンテナ — Supabase analytics サービス — が Restarting (1) を示しました。アップグレード前にも低頻度のクラッシュループ状態でした;アップグレードが原因ではありませんでしたが、アップグレード後のスキャンでより明確に表面化しました。これはまさにアップグレード前のインベントリが捕捉すべき既存の問題の種類です。
ヘルスチェックが通過していることを確認する。
docker inspect $(docker ps -q) --format '{{.Name}} health={{.State.Health.Status}}' 2>/dev/null | grep -v "health=<no value>" | sort
明示的なヘルスチェックのないコンテナは <no value> を示します — これをフィルタリングします。重要なのは、他のコンテナが condition: service_healthy を通じて依存しているものです。
すべてのスタックで compose config 検証パスを実行する。
for dir in /opt/*/; do
if [ -f "${dir}docker-compose.yml" ]; then
echo "--- $dir ---"
docker compose -f "${dir}docker-compose.yml" config --quiet 2>&1
fi
done
これにより、Compose v5 が警告またはエラーとして扱う compose ファイルのディレクティブを検出します。私たちのサーバーでは、3 つのスタックが version キー非推奨警告を発しました。これらは外観上のものですが、次のメンテナンスサイクルで解決する価値があります。
Docker デーモンログのエラーを確認する。
journalctl -u docker --since "1 hour ago" | grep -i -E "error|warn|fatal"
クリーンなアップグレードではデーモンログにエラーは発生しません。非推奨設定に関する警告は記録する価値がありますが、すぐに対応する必要はありません。
コンテナ間のネットワーク接続を確認する。
docker network ls
docker network inspect bridge --format '{{range .Containers}}{{.Name}} {{end}}'
Docker Engine 29 はデフォルトのネットワークドライバの動作を変更しませんでしたが、カスタムブリッジネットワークがデーモン再起動後に期待されたコンテナメンバーシップを持つことを確認する価値があります。
観察したこと
アップグレードは 2 分以内に完了しました。アップグレード前に実行していたすべてのコンテナがその後も実行されていました。Docker Compose v5 の変更で最もすぐに気づいたのは、--dry-run 出力形式がよりクリーンになったことです — 新しいバージョンは構造化されたカラー出力を生成し、docker compose up が実行前に実際に行うことを確認しやすくなっています。
depends_on のヘルスチェック順序は私たちのスタックで v2 と同一に動作しました。事前に compose ファイルを検証していたので予想通りでしたが、確認できて安心しました。
1 つの実質的な変更:Compose v5 は scale と組み合わせた container_name フィールドを静かに無視しなくなりました。私たちには適切なレプリカに移行する前の古い名残として両方を使用しているスタックが 1 つありました。Compose v5 はそのスタックの docker compose up で明確なエラーを発しましたが、v2 は競合を静かに無視していました。修正は冗長な container_name フィールドを削除する 1 行の編集でした。