Docker Compose 本番デプロイ:ヘルスチェック、再起動ポリシー、ログ管理
深夜3時、サーバーのアラート通知が携帯に次々と届きます。ターミナルを開いて確認すると、ディスク使用率が99%——コンテナのログが50GBも占拠していました。
これでさえ最悪ではありません。昨年あるプロジェクトで、APIコンテナのステータスは「running」と表示されているのに、実はデータベース接続がとっくに切れていて、リクエストが全て500エラーを返すという事態がありました。原因特定まで丸3時間かかりました。Last9の調査データによると、コンテナの「フェイルオーバー」は平均して1回あたり3.2時間の調査時間を浪費するそうです。
正直なところ、多くのチームが初めて本番環境にDocker Composeをデプロイする際、単純にポートマッピングとボリュームマウントを設定して、コンテナをそのまま本番に投入します。ヘルスチェックは設定せず、ログローテーションも追加せず、再起動ポリシーも適当に restart: always と書くだけ。結果として、コンテナは動いているように見えて実はとっくに停止している、ログファイルが際限なく増加してディスクを圧迫する、クラッシュしたサービスが無限に再起動してCPUとメモリを消費し尽くす、といった事態に陥ります。
この記事では、本番環境の3つのコア設定——ヘルスチェック、再起動ポリシー、ログ管理——を丁寧に解説します。設定例だけでなく、一般的なサービスのヘルスチェックコマンド、トラブルシューティングの手順、そしてそのままコピーして使える完全なdocker-compose.ymlテンプレートも提供します。
ヘルスチェック — コンテナを本当に稼働させる
コンテナのステータスが running と表示されていても、アプリケーションが実際に動作しているとは限りません。データベースに接続できない、ポートがリッスンしていない、プロセスがフリーズしている——こうした状況はDockerには判断できません。ヘルスチェックは、コンテナに「心拍モニター」を取り付けるようなもので、定期的にアプリケーションが正常に応答しているかどうかを確認します。
設定構文
docker-compose.ymlで、healthcheckの設定は次のようになります:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s # 10秒ごとにチェック
timeout: 5s # 各チェックの最大待機時間は5秒
retries: 5 # 連続5回失敗でunhealthyと判定
start_period: 30s # コンテナ起動後に30秒のウォームアップ時間
これらのパラメータは適切に組み合わせる必要があります。timeout は interval より大きくしてはいけません。そうしないと、チェックが終わる前に次のチェックが始まってしまいます。start_period は省略できません——データベースのようなサービスは起動に時間がかかるため、ウォームアップ時間が短すぎると、ヘルスチェックが誤ってコンテナが停止していると判断してしまいます。
一般的なサービスのヘルスチェックコマンド
サービスごとにチェック方法は異なります。ここではよく使われるものをいくつか紹介します:
PostgreSQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d mydb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
pg_isready はPostgreSQLに組み込まれているチェックツールで、データベースが接続を受け付ける準備ができているかどうかを判断するために使用します。
MySQL / MariaDB
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
パスワードは $$ でエスケープすることに注意してください。そうしないと、YAMLが $ を変数参照として解釈してしまいます。
Redis
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 10s
timeout: 3s
retries: 3
Redisの ping コマンドは PONG を返します。grep でフィルタリングして、結果が正しいことを確認します。
Web Server(HTTPチェック)
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
-f パラメータは、HTTPステータスコードが2xx以外の場合、curlが非ゼロの終了コードを返すようにし、ヘルスチェックの失敗をトリガーします。
注意点:Alpineのような最小限のイメージにはcurlが含まれていない場合があります。インストールする(apk add curl)か、wgetを使用するように変更してください:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1"]
起動順序の制御
データベースの準備ができていないのにAPIコンテナが起動し、接続に失敗してエラーを吐き、クラッシュする——こうした光景は何度も見てきました。depends_on と condition: service_healthy を組み合わせることで解決できます:
services:
postgres:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
api:
build: ./api
depends_on:
postgres:
condition: service_healthy # postgresのヘルスチェックが通るまで待機
こうすることで、Docker Composeはpostgresのヘルスチェックが healthy を返してから、apiコンテナを起動します。「データベースがまだ準備できていないのにAPIが接続しようとする」という問題は起こりません。
再起動ポリシー — 障害後の優雅な復旧
コンテナがクラッシュしたらどうすればよいでしょうか?自動再起動させるのは良いアイデアに思えます。しかし、クラッシュの根本原因が解決されていない場合、再起動は無限ループになり、CPUとメモリを浪費し、実際の障害を隠してしまいます。
設定構文
再起動ポリシーは deploy ブロック内で設定します:
deploy:
restart_policy:
condition: on-failure # 失敗時のみ再起動
delay: 5s # 再起動前に5秒待機
max_attempts: 3 # 最大3回再起動を試行
window: 120s # 120秒以内に再起動成功で復旧とみなす
condition には3つの選択肢があります:
none:再起動しない。コンテナが停止したらそのままon-failure:コンテナが異常終了(終了コードが0以外)した場合のみ再起動any:どのような状況でも再起動
本番環境での推奨
本番環境では always ではなく on-failure を推奨します。
なぜでしょうか?restart: always はどのような状況でもコンテナを再起動します。アプリケーションのコードにバグがあってクラッシュ?再起動。データベースに接続できなくてプロセスが終了?再起動。設定ファイルが間違っていて起動に失敗?やはり再起動。結果としてクラッシュループになり、ログが大量に出力され、CPUが繰り返し消費されます。
on-failure に max_attempts を組み合わせると、最大3回再起動して、それでも失敗すれば停止します。運用担当者はコンテナが最終的に停止したことを確認でき、本当の問題を調査できます。
パラメータの調整
delay は再起動の間隔です。短すぎると、コンテナが完全にクリーンアップされないうちに再起動してしまいます。長すぎると復旧時間が延びます。一般的には5〜10秒が適切です。
window は見落とされがちなパラメータです。これは「再起動後、どれだけの時間内に再び失敗しなければ、再起動成功とみなすか」を定義します。例えば window: 120s と設定すると、コンテナが再起動後に120秒以内にまた停止した場合、max_attempts のカウントはリセットされません。これにより、「再起動成功後1秒でまたクラッシュ」という誤判定を防げます。
ヘルスチェックと再起動ポリシーの連携
ヘルスチェックと再起動ポリシーは独立して動作するのではなく、連携します:
- ヘルスチェックが連続して
retries回失敗 → コンテナがunhealthyとマークされる restart_policyが設定されている場合、Dockerはコンテナの再起動を試みる- 再起動後、ヘルスチェックのカウントがリセットされる
- 再起動後にヘルスチェックが通れば、コンテナは正常に復旧。失敗した場合、
max_attemptsを使い切るまで再起動を続ける
この連鎖により、障害に対して「自動復旧」の能力を持たせつつ、無限再起動のリスクを制限できます。
ログ管理 — ディスク容量の圧迫を防ぐ
冒頭で触れた深夜3時のアラート——ディスク使用率99%、ログが50GBを占拠——こうした状況は一度や二度ではありません。Dockerのデフォルトのログドライバーであるjson-fileは古いログを自動的に削除しないため、ファイルが際限なく増大します。ログローテーションを設定しないと、早晚ディスクを圧迫してしまいます。
ログローテーションの設定
docker-compose.ymlに logging 設定を追加します:
logging:
driver: "json-file"
options:
max-size: "10m" # 単一ログファイルの最大サイズは10MB
max-file: "3" # 最大3つのログファイルを保持
compress: "true" # 古いログを圧縮してスペースを節約
この設定で、コンテナのログは最大30MB(10MB × 3)を占有します。10MBを超えると、Dockerは新しいファイルを作成します。3つのファイルを超えると、最も古いものが削除または圧縮されます。
ログファイルは /var/lib/docker/containers/<container-id>/<container-id>-json.log に保存されます。du コマンドで実際の使用量を確認できます:
du -sh /var/lib/docker/containers/*/*-json.log
ドライバーの選択
Dockerは複数のログドライバーをサポートしています:json-file、syslog、fluentd、journald、localなど。多くのユースケースでは、json-fileまたはlocalで十分です。
Dockerの公式ドキュメントによると、local ドライバーはjson-fileよりも効率的で、ログローテーションが組み込まれており、max-size/max-fileを手動で設定する必要がありません。ログ量が多い場合(例えば毎日数十GB)、localの使用を検討できます:
logging:
driver: "local"
ただし、localドライバーには欠点があります:docker logs でログの内容を直接確認できません。設定に mode: "non-blocking" を追加して互換性を持たせる必要があります。
集中型ログ収集(オプション)
単一サーバーのデプロイではjson-fileまたはlocalで十分です。しかし、数十台のサーバー、数百のコンテナがある場合、ログが各所に散在すると管理が難しくなります。この場合、集中型ログソリューションを検討できます:
- Fluentd:軽量なログ収集、小規模クラスターに適している
- ELK Stack(Elasticsearch + Logstash + Kibana):機能が強力だが、デプロイコストが高い
- Loki + Grafana:クラウドネイティブなソリューション、Prometheusエコシステムとの統合が良好
これらのソリューションの設定は比較的複雑で、本記事の範囲外です。Fluentdの設定アイデアを簡単に紹介します:
logging:
driver: "fluentd"
options:
fluentd-address: "localhost:24224"
tag: "docker.{{.Name}}"
Fluentdはログを指定されたアドレスに転送し、別のサーバーで一元管理・分析できます。
完全な設定テンプレート
ヘルスチェック、再起動ポリシー、ログ管理を組み合わせると、本番レベルのdocker-compose.ymlになります。以下に、PostgreSQLデータベース、Redisキャッシュ、APIサービスを含む完全な例を示します:
version: '3.8'
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
deploy:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
compress: "true"
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
deploy:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
api:
build:
context: ./api
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
DATABASE_URL: postgres://myuser:mypassword@postgres:5432/mydb
REDIS_URL: redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
deploy:
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
window: 120s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "5"
compress: "true"
volumes:
postgres_data:
設定のポイント
起動順序:apiコンテナの depends_on はpostgresとredisの両方のヘルスチェックが通るのを待ちます。データベースとキャッシュの両方が準備できてからAPIが起動し、起動時の接続エラーを回避します。
ログサイズの違い:postgresとredisのログ量は通常多くないため、10MB × 3で十分です。APIサービスのログは多くなる可能性があるため、50MB × 5に設定します。実際のログ量に応じて調整し、一律に設定しないでください。
再起動遅延の違い:postgresは起動に時間がかかり、再起動後の復旧に時間が必要なため、delayを5秒に設定しています。APIは起動が速いため、delayを10秒に設定し、ヘルスチェックに余裕を持たせています。
起動ウォームアップ時間:postgresは start_period: 30s で、データベースに十分な初期化時間を与えます。redisは start_period: 5s で、Redisは元々起動が速いためです。APIは start_period: 10s で、アプリケーションの起動は通常数秒で済みます。
このテンプレートはそのままコピーして使用でき、環境変数とイメージを自分のものに置き換えるだけです。プロジェクトに他のサービス(MongoDB、MinIOなど)がある場合、同じパターンでヘルスチェック、再起動ポリシー、ログ設定を追加してください。
よくある落とし穴とトラブルシューティング
設定を書いてデプロイしても、問題は発生する可能性があります。ここではよくある落とし穴とトラブルシューティングの手順を紹介します。
ヘルスチェックが常に失敗する
症状:コンテナのステータスが常に unhealthy だが、アプリケーションは正常に動作しているように見える。
トラブルシューティング:
-
まずヘルスチェックコマンドで使用するツールが存在するか確認:
docker exec <container> which curl docker exec <container> which pg_isreadyAlpineイメージにはcurlが含まれていないことがよくあるため、手動でインストールするかwgetに変更する必要があります。
-
ヘルスチェックコマンドを手動で実行して、出力を確認:
docker exec <container> curl -f http://localhost:8080/healthエラーが返される場合、ヘルスチェックエンドポイント自体に問題がある可能性があります。
-
ヘルスチェックの詳細なステータスを確認:
docker inspect --format='{{json .State.Health}}' <container> | jq直近の数回のチェック結果、失敗理由、タイムスタンプが確認できます。
コンテナが繰り返し再起動する
症状:コンテナが数秒で起動した後に停止し、ログに再起動記録が続いている。
トラブルシューティング:
-
コンテナの終了理由を確認:
docker inspect --format='{{.State.ExitCode}}' <container> docker inspect --format='{{.State.Error}}' <container>終了コードからおおよその問題がわかります(1 = 一般エラー、137 = OOMで強制終了、139 = セグメンテーション違反)。
-
再起動回数を確認:
docker inspect --format='{{.RestartCount}}' <container>回数が非常に多い場合、
max_attemptsが有効かどうか確認してください。 -
コンテナのログを確認して具体的なエラーを見つける:
docker logs --tail 100 <container>
ディスクがログで満杯になる
症状:ディスク容量のアラート、/var/lib/docker/containers ディレクトリが大きく占拠している。
トラブルシューティング:
-
最も大きなログファイルを特定:
du -sh /var/lib/docker/containers/*/*-json.log | sort -rh | head -5 -
ログローテーション設定が有効かどうか確認:
docker inspect --format='{{.HostConfig.LogConfig}}' <container>出力が
Config: {}の場合、ログローテーションが設定されていません。 -
ログを手動でクリーンアップ(一時的な対策):
truncate -s 0 /var/lib/docker/containers/<id>/<id>-json.logこれは一時的な対策です。長期的にはログローテーション設定を追加する必要があります。
クイックトラブルシューティングコマンドリスト
問題が発生した場合、これらのコマンドですばやく特定できます:
# 全コンテナのヘルスステータスを確認
docker ps --format "table {{.Names}}\t{{.Status}}"
# 特定のコンテナのヘルスチェック履歴を確認
docker inspect --format='{{json .State.Health}}' <container>
# コンテナの終了コードと再起動回数を確認
docker inspect --format='ExitCode: {{.State.ExitCode}}, RestartCount: {{.RestartCount}}' <container>
# ログファイルのサイズを確認
du -sh /var/lib/docker/containers/*/*-json.log | sort -rh
# コンテナの直近100件のログを確認
docker logs --tail 100 <container>
まとめ
本番環境でのDocker Composeデプロイにおいて、これら3つの設定はオプションではなく必須です:ヘルスチェックはコンテナが「見た目だけ動いている」状態を防ぎ、再起動ポリシーは障害時に自動復旧の機会を与えつつ無限ループを制限し、ログ管理はディスク容量の圧迫を防ぎます。
コア設定チェックリスト:
- ヘルスチェック:
test+interval+timeout+retries+start_period - 再起動ポリシー:
condition: on-failure+max_attempts: 3 - ログローテーション:
max-size: 10m+max-file: 3+compress: true
3ステップアクションプラン:
- 既存のdocker-compose.ymlを確認し、これら3つの設定があるかチェック。ない場合、少なくともヘルスチェックとログローテーションを追加。
- 上記の完全なテンプレートでテストサービスをデプロイし、ヘルスチェックが有効か、ログがローテーションされているか確認。
- トラブルシューティングコマンドを記録。次回深夜3時にアラートが来た時、すばやく問題を特定できるように。
コンテナを本番環境で「裸」で走らせないでください。これら3つの「保護シールド」を適切に設定すれば、問題が発生した時も自動復旧し、すばやく調査でき、ディスクを圧迫することはありません。
FAQ
ヘルスチェックの interval と timeout はどう設定すればよいですか?
再起動ポリシーは restart: always と on-failure のどちらがよいですか?
ログファイルのサイズと保持数はどう設定すればよいですか?
コンテナのヘルスチェックが常に失敗するがアプリケーションは正常に動作している場合はどうすればよいですか?
depends_on の condition: service_healthy の役割は何ですか?
コンテナのログがどれだけディスク容量を占めているかすばやく確認するにはどうすればよいですか?
6 min read · 公開日: 2026年4月12日 · 更新日: 2026年4月12日
関連記事
Nginx パフォーマンスチューニング:gzip、キャッシュ、接続プール設定
Nginx パフォーマンスチューニング:gzip、キャッシュ、接続プール設定
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定

コメント
GitHubアカウントでログインしてコメントできます