本番運用の決定版!FastAPIのDockerデプロイ徹底ガイド――Uvicorn/Gunicorn・Nginx・HTTPS・ヘルスチェック・ログ監視まで
✅ まずは結論(サマリー)
- この記事でできること
FastAPIアプリをDockerで本番運用するための最短ルートを、設計意図とともに一気通貫で構築できます。Uvicorn+Gunicornでの起動、NginxリバースプロキシによるHTTP/2・静的配信・WebSocket対応、ヘルスチェックとグレースフルシャットダウン、構造化ログとメトリクス(OpenTelemetry入門)まで、実務でそのまま使える雛形を提供します。 - 主なチェックポイント
- 12-Factorを意識した環境変数+pydantic-settings構成
- Dockerfile(非root・軽量・キャッシュ効率・ヘルスチェック)
- **Gunicorn(UvicornWorker)**で並列ワーカー&シグナル制御
- NginxでTLS終端/HTTP/2/WebSocket/静的ファイル
- ヘルスチェック(liveness/readiness)とゼロダウンに近い再起動
- ログ(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
が定番。HEALTHCHECK
でreadinessを監視。
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. グレースフルシャットダウン(ゼロダウンに近づくコツ)
- Gunicornは
TERM
で新規受付停止→処理中のリクエスト完了後に停止。 - 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ログなら集約基盤(後日導入)でクエリが容易。
level
はinfoを起点に、負荷に応じて調整。
9. OpenTelemetryで“トレースの入口”を作る
トレースは「どのリクエストが、どの処理にどれだけ時間を使ったか」を可視化します。最小の始め方:
- 依存追加:
opentelemetry-distro
、opentelemetry-exporter-otlp
、opentelemetry-instrumentation-fastapi
、opentelemetry-instrumentation-requests
。 - 実行:
opentelemetry-instrument --traces_exporter otlp --metrics_exporter otlp --service_name fastapi-app gunicorn -k uvicorn.workers.UvicornWorker ... app.main:app
- 送信先は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
- ローリングアップデート:新コンテナの
readiness
がOK→旧コンテナを降ろす順。 - レート制限:Nginx側(軽量)かアプリ側(柔軟)で導入。
- リクエストID:Nginxで生成→上流に渡す→アプリログに出力。障害時の相関が取りやすい。
- DB接続:接続プール・タイムアウトを適切に。readinessでDB疎通を検証。
- 障害演習:わざとアプリを落とし、復旧とログ追跡が本当にできるか定期的に確認。
要点:監視と復旧の導線を先に作ると、いざという時に慌てません。
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移行が怖くない。