Docker Compose 本番デプロイ:ヘルスチェック、再起動ポリシー、ログ管理
サーバーのアラートSMSが、次から次へとスマホに届く。ターミナルを開いて見ると、ディスク使用率99%——コンテナのログが50GBを占めていた。
これでもまだ最悪ではない。昨年、あるプロジェクトで API コンテナのステータスが「running」と表示されていたのに、データベース接続はとっくに切れていて、リクエストはすべて 500 を返していた。原因を突き止めるのにまるまる3時間かかった。Last9 の調査データによれば、コンテナの「フェイルオーバー(見かけ上は動いている状態)」では、平均で毎回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秒ごとに1回チェック
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 で kill された、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 は何に使いますか?
コンテナのログがどれだけディスクを使っているかをすばやく確認するには?
5分で読めます · 公開日: 2026年4月12日 · 更新日: 2026年6月8日
Docker 実践ガイド
検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。
前の記事
Docker インストールの落とし穴ガイド 2025:permission denied から正常起動までの完全解決策
Windows では WSL 2、Mac ではチップ版の選び方、Linux では権限と依存パッケージ——三大プラットフォームでよく出る Docker インストールエラー 10 件以上を整理。permission denied から正常起動まで、そのまま試せる手順付き。
第 3 / 37 記事
次の記事
Docker マルチステージビルド実践:本番イメージを 1GB から 10MB へ
Docker マルチステージビルドの実践テクニックを習得し、本番イメージを 1GB から 10MB まで削減。Go・Node.js・Python の 3 言語テンプレート、Alpine と Distroless の比較、よくある 5 つの落とし穴の回避策を解説します。
第 5 / 37 記事
関連記事
Dockerfile入門:ゼロから最初の Docker イメージを作る(実例付き)
Dockerfile入門:ゼロから最初の Docker イメージを作る(実例付き)
Docker vs 仮想マシン:5分で理解する性能差とシーン別選び方ガイド
Docker vs 仮想マシン:5分で理解する性能差とシーン別選び方ガイド
Docker Compose 複数サービス連携:ローカル開発環境をワンコマンドで起動
コメント
GitHubアカウントでログインしてコメントできます