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

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

これらのパラメータは適切に組み合わせる必要があります。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で強制終了、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。特定のコンテナのログが特に大きい場合、そのコンテナのログローテーション設定が有効かどうか確認してください。

6 min read · 公開日: 2026年4月12日 · 更新日: 2026年4月12日

コメント

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

関連記事