green snake
Photo by Pixabay on Pexels.com
目次

本番運用の決定版!FastAPIのDockerデプロイ徹底ガイド――Uvicorn/Gunicorn・Nginx・HTTPS・ヘルスチェック・ログ監視まで


✅ まずは結論(サマリー)

  • この記事でできること
    FastAPIアプリをDockerで本番運用するための最短ルートを、設計意図とともに一気通貫で構築できます。Uvicorn+Gunicornでの起動、NginxリバースプロキシによるHTTP/2・静的配信・WebSocket対応、ヘルスチェックグレースフルシャットダウン構造化ログメトリクス(OpenTelemetry入門)まで、実務でそのまま使える雛形を提供します。
  • 主なチェックポイント
    1. 12-Factorを意識した環境変数+pydantic-settings構成
    2. Dockerfile(非root・軽量・キャッシュ効率・ヘルスチェック)
    3. **Gunicorn(UvicornWorker)**で並列ワーカー&シグナル制御
    4. NginxでTLS終端/HTTP/2/WebSocket/静的ファイル
    5. ヘルスチェック(liveness/readiness)とゼロダウンに近い再起動
    6. ログ(JSON)・トレース(OTel)・メトリクスの初手
  • 得られる効果
    デプロイが再現可能になり、スケールと保守がラクに。障害時の観測性(見える化)が上がるため、復旧までの時間を短縮できます。

🎯 誰が一番得をする?(対象読者を具体化)

  • 学習者Aさん(21歳・個人開発)
    ローカルでは動くけど、本番デプロイの作法がわからない。Dockerを使って同じ手順でどこでも立ち上がる状態にしたい。
  • 小規模チームBさん(3〜5名)
    機能追加のたびに手順がブレて、デプロイが怖い。Nginx/Gunicorn/ヘルスチェックを定番化して事故ゼロ運用にしたい。
  • SaaS Cさん(スタートアップ)
    スケール前提で観測性を高めたい。ログ・トレース・メトリクスを最小コストで導入し、将来はKubernetesにも拡張したい。

1. 全体像:これが“本番の骨格”

役割分担を明確にします。

  • FastAPI(アプリ):ビジネスロジック。ASGIサーバはUvicorn
  • Gunicorn:プロセスマネージャ。UvicornWorkerマルチワーカーシグナル再起動を司る。
  • Nginx:リバースプロキシ。TLS終端、HTTP/2、WebSocket、静的配信。
  • Docker:同一のアーティファクトをどこでも動かす。
  • ヘルスチェック:liveness(生存)/readiness(準備完了)で再起動やローリングを安定化。
  • 観測性:構造化ログ(JSON)・OTelトレース・メトリクス。

要点:アプリは“軽く速く”、プロセス管理はGunicorn、外部公開はNginxに任せる。Dockerで再現性を担保。


2. まずはアプリ:設定・エンドポイント・ライフサイクル

2.1 設定(pydantic-settings)

# app/core/settings.py
from pydantic import Field
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str = "My FastAPI"
    env: str = Field("dev", description="環境: dev/stg/prod")
    host: str = "0.0.0.0"
    port: int = 8000
    log_level: str = "info"
    # DBや外部APIキーもここに。…例:
    database_url: str = "sqlite:///./app.db"
    secret_key: str = Field("change-me", description="JWTなどで使用")

    class Config:
        env_file = ".env"
        extra = "ignore"

def get_settings() -> Settings:
    return Settings()

2.2 アプリ本体(ヘルスチェックとライフサイクル)

# app/main.py
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from app.core.settings import get_settings

app = FastAPI(title="Production-Ready FastAPI")

@app.get("/health/liveness")
def liveness():
    return {"status": "alive"}

@app.get("/health/readiness")
def readiness():
    # ここでDBや外部依存の疎通チェックを行う(簡略化)
    return {"status": "ready"}

@app.get("/")
def root(settings = get_settings()):
    return {"app": settings.app_name, "env": settings.env}

# 参考:起動・終了フック(接続プールの用意/解放など)
@app.on_event("startup")
async def on_startup():
    pass

@app.on_event("shutdown")
async def on_shutdown():
    pass

要点/health/liveness/health/readiness必ず用意。本番では外形監視・ローリング再起動に使います。


3. Dockerfile:軽量・非root・キャッシュ効率が命

# Dockerfile
FROM python:3.11-slim AS base

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1

# セキュリティのため非rootユーザーを用意
RUN useradd -m appuser

WORKDIR /app

# 依存を先にコピーしてキャッシュを効かせる
COPY pyproject.toml poetry.lock* requirements.txt* /app/
# どちらの管理でも動くよう柔軟に(ある方を使う)
RUN if [ -f "pyproject.toml" ]; then \
      pip install --upgrade pip && pip install poetry && poetry config virtualenvs.create false && poetry install --no-root; \
    elif [ -f "requirements.txt" ]; then \
      pip install --upgrade pip && pip install -r requirements.txt; \
    else \
      pip install --upgrade pip; \
    fi

# アプリコード
COPY app /app/app

# 権限を調整
RUN chown -R appuser:appuser /app
USER appuser

# Gunicorn(UvicornWorker)で起動。ワーカー数などは環境変数で調整
ENV WORKERS=2 \
    BIND=0.0.0.0:8000 \
    TIMEOUT=60 \
    KEEPALIVE=5

EXPOSE 8000

# ヘルスチェック(readinessに向ける)
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health/readiness')" || exit 1

CMD ["bash","-lc","exec gunicorn -k uvicorn.workers.UvicornWorker -w ${WORKERS} -b ${BIND} --timeout ${TIMEOUT} --keep-alive ${KEEPALIVE} app.main:app"]

ポイント

  • 非rootで実行(最低限の権限)。
  • 依存のインストールを先に置き、キャッシュを効かせる。
  • 起動は gunicorn -k uvicorn.workers.UvicornWorker が定番。
  • HEALTHCHECKreadinessを監視。

4. docker-compose:Nginxとアプリを並べる(ローカル〜小規模本番)

# docker-compose.yml
version: "3.9"
services:
  app:
    build: .
    image: my-fastapi:latest
    environment:
      APP_NAME: "My FastAPI"
      ENV: "stg"
      LOG_LEVEL: "info"
      WORKERS: "2"          # CPUに応じて調整(後述)
      BIND: "0.0.0.0:8000"
    expose:
      - "8000"
    networks: [net]
    restart: unless-stopped

  nginx:
    image: nginx:1.27-alpine
    volumes:
      - ./deploy/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./static:/usr/share/nginx/html:ro   # 任意(静的配信)
    ports:
      - "80:80"
      # 443を使う場合は証明書設定とともに開ける
      # - "443:443"
    depends_on:
      - app
    networks: [net]
    restart: unless-stopped

networks:
  net: {}

要点:Nginxを前段におき、アプリのスケールアウトやTLS終端、HTTP/2、静的配信を担当させます。


5. Nginx設定例:HTTP/2・WebSocket・タイムアウト最適化

# deploy/nginx.conf
user  nginx;
worker_processes auto;
events { worker_connections 1024; }

http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;
  sendfile      on;
  tcp_nopush    on;
  tcp_nodelay   on;
  keepalive_timeout  65;
  types_hash_max_size 4096;

  # 圧縮(任意)
  gzip on;
  gzip_types text/plain text/css application/json application/javascript application/xml;

  # 上流(FastAPI/Gunicorn)
  upstream fastapi_upstream {
    server app:8000;  # docker-compose のサービス名:ポート
    keepalive 32;
  }

  server {
    listen 80 default_server;
    # listen 443 ssl http2;  # TLSを使うなら証明書の設定を追加

    # WebSocket対応
    map $http_upgrade $connection_upgrade {
      default upgrade;
      ''      close;
    }

    # 静的配信(任意)
    location /static/ {
      alias /usr/share/nginx/html/;
      add_header Cache-Control "public, max-age=31536000";
    }

    # すべてアプリへ
    location / {
      proxy_http_version 1.1;
      proxy_set_header Host              $host;
      proxy_set_header X-Real-IP         $remote_addr;
      proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;

      # WebSocket
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection $connection_upgrade;

      # タイムアウト(長い処理に合わせて調整)
      proxy_read_timeout  75s;
      proxy_connect_timeout 5s;
      proxy_send_timeout  60s;

      proxy_pass http://fastapi_upstream;
    }
  }
}

要点

  • Upgrade/Connection ヘッダでWebSocketに対応。
  • proxy_read_timeoutバックグラウンド処理の長さに合わせて伸ばす。
  • TLSはサーバ証明書を設定してHTTP/2化(ここでは割愛)。

6. ワーカー数・同時接続の目安(Gunicornチューニング)

  • ワーカー数CPUコア × 2 + 1 が古典的目安。I/O待ちが多いなら増やす。
  • UvicornWorker:非同期ASGIに最適。--workers を増やすとプロセスが増え、安定性が上がる。
  • スレッド:ASGIでは基本不要。同期ブロッキングが多い場合のみ--threadsを検討。
  • タイムアウト--timeout は少し余裕を持たせ、ハング検知の役割も。

要点:まずはワーカーを少なめに。遅延のボトルネック(CPU/メモリ/DB)を見て調整します。


7. グレースフルシャットダウン(ゼロダウンに近づくコツ)

  • GunicornTERM新規受付停止→処理中のリクエスト完了後に停止。
  • Nginxはkeep-aliveで接続を維持し、新旧コンテナの切り替えをスムーズに。
  • ヘルスチェックを使ったローリング:新コンテナがreadiness OKになるまでLBに載せない。

要点:シャットダウン時の後片付け(DB接続・キュー)をshutdownイベントで確実に解放。


8. 構造化ログ(JSON)とレベル設計

8.1 Pythonロガー設定例

# app/core/logging.py
import json, logging, sys
from typing import Any

class JsonFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        payload: dict[str, Any] = {
            "level": record.levelname,
            "logger": record.name,
            "msg": record.getMessage(),
            "time": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
        }
        if record.exc_info:
            payload["exc"] = self.formatException(record.exc_info)
        return json.dumps(payload, ensure_ascii=False)

def setup_logging(level: str = "INFO"):
    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(JsonFormatter())
    root = logging.getLogger()
    root.setLevel(level.upper())
    root.handlers[:] = [handler]
# app/main.py(先頭で)
from app.core.logging import setup_logging
from app.core.settings import get_settings
setup_logging(get_settings().log_level)

要点:JSONログなら集約基盤(後日導入)でクエリが容易。levelinfoを起点に、負荷に応じて調整。


9. OpenTelemetryで“トレースの入口”を作る

トレースは「どのリクエストが、どの処理にどれだけ時間を使ったか」を可視化します。最小の始め方:

  1. 依存追加:opentelemetry-distroopentelemetry-exporter-otlpopentelemetry-instrumentation-fastapiopentelemetry-instrumentation-requests
  2. 実行:opentelemetry-instrument --traces_exporter otlp --metrics_exporter otlp --service_name fastapi-app gunicorn -k uvicorn.workers.UvicornWorker ... app.main:app
  3. 送信先はOTLPでコレクタ(後日導入)へ。ローカルでは無効でもOK(導線だけ作る)。

要点:いまは**“ハーネス”だけ**先に敷いておく。将来コレクタやAPMを導入しても、アプリ側の改修を最小にできます。


10. セキュリティの初手(運用で必ず効く基本)

  • 非rootユーザー実行(Dockerfileで実施)。
  • ポートの最小公開:Nginxのみ外部公開。アプリは内部ネットワークだけに。
  • 環境変数の秘匿.envやSecret管理(マネージドKVS等)を利用し、イメージに埋め込まない
  • CORS:本番は許可オリジンを限定
  • 依存更新:ベースイメージとPythonパッケージを定期更新

要点:まずは「最小権限」「境界の分離」「機密の外出し」。この3点でリスクが大きく下がります。


11. 実践サンプル:ビルド → 起動 → 動作確認

# 1) ビルド
docker compose build

# 2) 起動
docker compose up -d

# 3) ヘルスチェック
docker compose ps     # health: starting → healthy になるのを確認
curl http://localhost/health/liveness
curl http://localhost/health/readiness

# 4) 動作
curl http://localhost/

要点healthy になるまでLBに載せない。readiness依存先の可用性をチェックするのがコツ。


12. 静的ファイル・ファイルアップロードの扱い

  • 静的:Nginxの/static/で直接配信 → アプリのCPUを節約。
  • アップロード:サイズ上限(client_max_body_size)とタイムアウトをNginx側で調整。大容量はストレージ直PUT(署名URL)を検討。

要点:静的や大容量はNginxやストレージに任せると、APIが軽く保てます。


13. バックグラウンド処理とワーカー

  • FastAPIのBackgroundTasks軽い後処理向け。
  • 画像変換・バッチ等は専用ワーカー(例:Celery/RQ)とメッセージブローカーに分離。
  • Docker Composeでapp(API)worker(非同期)を別サービスにするとスケール粒度がよくなります。

要点:長時間処理はAPIプロセスから外す。可用性とスケールがよくなります。


14. 本番で効く“運用まわり”のTips

  1. ローリングアップデート:新コンテナのreadinessがOK→旧コンテナを降ろす順。
  2. レート制限:Nginx側(軽量)かアプリ側(柔軟)で導入。
  3. リクエストID:Nginxで生成→上流に渡す→アプリログに出力。障害時の相関が取りやすい。
  4. DB接続:接続プール・タイムアウトを適切に。readinessでDB疎通を検証。
  5. 障害演習:わざとアプリを落とし、復旧とログ追跡が本当にできるか定期的に確認。

要点:監視と復旧の導線を先に作ると、いざという時に慌てません。


15. さらに先へ(Kubernetesに持ち上げる準備)

  • Helm化:環境差分はvaluesで吸収。
  • Liveness/Readiness:この記事のエンドポイントをそのまま利用。
  • HPA:CPU/メモリ指標+アプリメトリクスでスケール。
  • Ingress:Nginxの設定はIngressアノテーションに置き換え可能。

要点:Docker Composeで整えた“役割分担”は、そのままK8sに移し替えやすい設計です。


16. よくある落とし穴と回避策

症状 原因 回避策
ローカルはOK、本番でWebSocketが切れる NginxでUpgrade/Connection未設定/タイムアウト短すぎ 本記事のNginx例を適用し、proxy_read_timeoutを延長
再起動で瞬断が発生 readiness未考慮、Graceful未対応 readinessをLB判定に使い、Gunicornのグレースフル停止を前提にローリング
たまに応答が遅い ワーカー不足/DB接続枯渇 workers増加・DBプール調整・ボトルネックを計測(トレース導入)
ログが読みづらい テキストログで可視化が難 JSONログへ変更し、構造化クエリで分析
秘密情報が漏れそう イメージにベタ書き 環境変数+Secret管理。.envは取り扱いに注意、リポジトリに含めない

17. まとめ(今日から“壊れない”デプロイへ♡)

  • Uvicorn+Gunicorn速くて安定Nginx守って捌く
  • Dockerfile非root・キャッシュ効率・ヘルスチェックを押さえる。
  • ヘルスチェックでローリングを安定化、JSONログOTel導線で観測性を確保。
  • 「静的はNginx」「長時間はワーカー」「機密は外出し」――役割を分ければ、運用はぐっと楽になります。

この雛形を土台に、スケールも監視も段階的に足していきましょう。わたしもずっと応援していますね♡


付録A:必要パッケージ(例・requirements.txt)

fastapi
uvicorn[standard]
gunicorn
pydantic
pydantic-settings

(OpenTelemetryやDB等は必要に応じて追加)

付録B:環境変数の例(.env)

APP_NAME=My FastAPI
ENV=prod
LOG_LEVEL=info
WORKERS=4
BIND=0.0.0.0:8000
SECRET_KEY=change-me
DATABASE_URL=sqlite:///./app.db

付録C:対象読者とインパクト(詳細)

  • 個人開発者:コピペで本番っぽい形が作れます。エラー時の切り分けがしやすく学習効率もUP。
  • 小規模チーム:手順が標準化され、属人化を防止。レビューと引き継ぎがスムーズに。
  • 成長中SaaS:観測性とヘルスチェックで安定運用の土台が整い、K8s移行が怖くない

投稿者 greeden

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)