言語を切り替える
テーマを切り替える

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秒のウォームアップ時間を与える

これらのパラメータはうまく組み合わせる必要がある。timeoutinterval より大きくしてはいけない。さもないとチェックが終わる前に次のチェックが始まってしまう。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_oncondition: 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-failuremax_attempts を加えると話は違う——最大3回まで再起動し、それでも失敗するなら停止する。運用担当者はコンテナが最終的に落ちたことを確認でき、本当の問題を調査しにいける。

パラメータのチューニング

delay は再起動の間隔だ。短すぎると、コンテナが完全にクリーンアップされる前にまた起動してしまうことがある。長すぎると復旧時間が延びる。一般的には 5〜10 秒が適切だ。

window というパラメータは見落とされがちだ。これは「再起動後、どれだけの時間ふたたび失敗しなければ再起動が成功したとみなすか」を定義する。例えば window: 120s と設定すると、コンテナが再起動後 120 秒以内にまた落ちた場合、max_attempts のカウントはリセットされない。これにより「再起動が成功した1秒後にまたクラッシュした」という誤判定を避けられる。

ヘルスチェックと再起動ポリシーの連携

ヘルスチェックと再起動ポリシーは独立して動くわけではなく、協調して連携する。

  1. ヘルスチェックが連続して retries 回失敗 → コンテナが unhealthy とマークされる
  2. restart_policy が設定されていれば、Docker はコンテナの再起動を試みる
  3. 再起動後、ヘルスチェックのカウントが再びスタートする
  4. 再起動後にヘルスチェックが通れば、コンテナは正常に復旧する。再起動後もまだ失敗するなら、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 なのに、アプリは正常に動いているように見える。

調査手順

  1. まずヘルスチェックコマンドが使うツールが存在するか確認する。

    docker exec <container> which curl
    docker exec <container> which pg_isready

    Alpine イメージには curl が入っていないことがよくあるので、手動でインストールするか wget に切り替える。

  2. ヘルスチェックコマンドを手動で実行して出力を見る。

    docker exec <container> curl -f http://localhost:8080/health

    エラーが返るなら、ヘルスチェックのエンドポイント自体に問題があるかもしれない。

  3. ヘルスチェックの詳細なステータスを見る。

    docker inspect --format='{{json .State.Health}}' <container> | jq

    直近数回のチェック結果、失敗原因、タイムスタンプが確認できる。

コンテナが繰り返し再起動する

症状:コンテナが起動して数秒でまた落ち、ログが再起動記録だらけになる。

調査手順

  1. コンテナの終了原因を見る。

    docker inspect --format='{{.State.ExitCode}}' <container>
    docker inspect --format='{{.State.Error}}' <container>

    終了コードからおおよその問題がわかる(1 = 一般的なエラー、137 = OOM で kill された、139 = セグメンテーション違反)。

  2. 再起動回数を見る。

    docker inspect --format='{{.RestartCount}}' <container>

    回数が非常に多い場合は、max_attempts が効いているか確認する。

  3. コンテナのログを見て具体的なエラーを探す。

    docker logs --tail 100 <container>

ログでディスクが満杯になる

症状:ディスク容量のアラートが出て、/var/lib/docker/containers ディレクトリの占有が大きいことがわかる。

調査手順

  1. 占有が最大のログファイルを探す。

    du -sh /var/lib/docker/containers/*/*-json.log | sort -rh | head -5
  2. ログローテーション設定が効いているか確認する。

    docker inspect --format='{{.HostConfig.LogConfig}}' <container>

    出力に Config: {} と表示されるなら、ログローテーションが設定されていない。

  3. ログを手動でクリーンアップする(一時的な対処)。

    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つの設定はオプションではなく必須だ。ヘルスチェックはコンテナを「動いているように見えるだけ」ではなくする。再起動ポリシーは障害に自動復旧の機会を与えつつ無限ループを制限する。ログ管理はディスクが埋まるのを防ぐ。

3.2時間
コンテナのフェイルオーバー平均調査時間

核心設定チェックリスト

  • ヘルスチェック:test + interval + timeout + retries + start_period
  • 再起動ポリシー:condition: on-failure + max_attempts: 3
  • ログローテーション:max-size: 10m + max-file: 3 + compress: true

3ステップ行動プラン

  1. 既存の docker-compose.yml を確認し、この3つの設定があるか見てみる。なければ、少なくともヘルスチェックとログローテーションは追加する。
  2. 上記の完全なテンプレートでテスト用サービスをデプロイし、ヘルスチェックが効いているか、ログがローテーションされているかを観察する。
  3. 調査コマンドを書き留めておく。次に深夜3時にアラートを受けたとき、すばやく問題を特定できる。

コンテナを本番ラインで丸裸のまま走らせてはいけない。この3つの「保護シールド」をきちんと設定すれば、問題が起きても少なくとも自動復旧でき、すばやく調査でき、ディスクを埋め尽くされずに済む。

FAQ

ヘルスチェックの interval と timeout はどう設定すれば適切ですか?
interval は 10〜30 秒、timeout は 3〜10 秒がおすすめです。重要なのは timeout が interval より大きくならないこと。さもないと次のチェックが始まるときに前回がまだ終わっていません。データベース系のサービスは長めの start_period(30〜60 秒)を設定し、初期化の時間を確保しましょう。
再起動ポリシーは restart: always と on-failure のどちらが良いですか?
本番環境では on-failure と max_attempts の組み合わせがおすすめです。always はどんな状況でも再起動するため、設定ミスやコードのバグまで含めて再起動し、クラッシュループを招きます。on-failure は異常終了したときだけ再起動し、max_attempts で回数を制限するので、運用側が問題に気づけます。
ログファイルはどのくらいのサイズで、いくつ保持するのが適切ですか?
一般的なサービスは max-size: 10m + max-file: 3 で合計 30MB がおすすめです。ログ量の多いサービス(API など)は 50m × 5 にできます。重要なのは実際のログ量に応じて調整することで、同時に compress: true を有効にして容量を節約しましょう。
コンテナのヘルスチェックがずっと失敗するのにアプリは正常に動いている場合はどうすればいいですか?
まずヘルスチェックコマンドが使うツール(curl、pg_isready など)が存在するか確認します。Alpine イメージはツールが不足しがちです。次にチェックコマンドを手動で実行して出力を確認し、最後に docker inspect でヘルスチェック履歴を見て具体的な失敗原因を特定します。
depends_on の condition: service_healthy は何に使いますか?
依存サービスのヘルスチェックが通ってから現在のコンテナを起動させます。単純な depends_on より信頼性が高く、データベースの準備が整う前に API が接続を試みて起動失敗するのを防げます。依存サービスに healthcheck を設定して初めて有効になります。
コンテナのログがどれだけディスクを使っているかをすばやく確認するには?
次のコマンドを使います:du -sh /var/lib/docker/containers/*/*-json.log | sort -rh。特定のコンテナのログが極端に大きい場合は、そのコンテナのログローテーション設定が効いているか確認しましょう。

5分で読めます · 公開日: 2026年4月12日 · 更新日: 2026年6月8日

関連記事

コメント

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