Pythonで開発したWebアプリをDockerコンテナとして本番環境にデプロイするための実践的な手順を解説します。開発環境では動いたのに本番でトラブルが起きる問題をDockerで根本から解決し、マルチステージビルドで本番用の軽量イメージを作る方法まで徹底解説します。
本番デプロイで考慮すること
開発環境と本番環境では求められることが異なります。本番環境では以下の点を特に意識する必要があります。
| 観点 | 開発環境 | 本番環境 |
|---|---|---|
| セキュリティ | 緩め | 非rootユーザー・最小権限 |
| イメージサイズ | 開発ツール込みでOK | できるだけ軽量に |
| パフォーマンス | デバッグしやすさ優先 | 本番用WSGIサーバー使用 |
| 設定管理 | ハードコードOK | 環境変数で外部化 |
| ログ | コンソール出力 | 構造化ログ・外部転送 |
Flask + Gunicornの本番デプロイ構成
開発時はflask runで動かしますが、本番ではGunicornなどの本番用WSGIサーバーを使います。
アプリ構成
myapp/
├── app.py
├── requirements.txt
├── Dockerfile
└── docker-compose.yml
# app.py
from flask import Flask
import os
app = Flask(__name__)
@app.route("/")
def hello():
return f"Hello from Docker! ENV={os.getenv('APP_ENV', 'development')}"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
# requirements.txt
flask==3.0.0
gunicorn==21.2.0
マルチステージビルドによる本番Dockerfile
# ===== ステージ1: 依存関係ビルド =====
FROM python:3.11-slim AS builder
WORKDIR /build
# pipをアップグレードして依存関係をインストール
COPY requirements.txt .
RUN pip install --upgrade pip \
&& pip install --no-cache-dir --user -r requirements.txt
# ===== ステージ2: 本番実行環境 =====
FROM python:3.11-slim AS production
# セキュリティアップデートを適用
RUN apt-get update \
&& apt-get upgrade -y \
&& rm -rf /var/lib/apt/lists/*
# 非rootユーザーを作成
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 作業ディレクトリを設定
WORKDIR /app
# builderステージからインストール済みパッケージをコピー
COPY --from=builder /root/.local /home/appuser/.local
# アプリのソースコードをコピー
COPY --chown=appuser:appuser . .
# 非rootユーザーで実行
USER appuser
# PATHにローカルbinを追加
ENV PATH=/home/appuser/.local/bin:$PATH
# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8000/ || exit 1
EXPOSE 8000
# 本番用WSGIサーバー(Gunicorn)で起動
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "--timeout", "60", "app:app"]
Nginx + Gunicorn + PostgreSQLの本番構成
実際の本番環境では、Nginxをリバースプロキシとして前段に置き、GunicornがFlaskを動かし、PostgreSQLでデータを管理する3層構成が一般的です。
version: "3.8"
services:
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
restart: unless-stopped
app:
build:
context: .
target: production # マルチステージのproductionステージを使用
expose:
- "8000"
environment:
- APP_ENV=production
- DATABASE_URL=postgresql://appuser:dbpassword@db:5432/myapp
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: dbpassword
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
postgres_data:
Nginxのリバースプロキシ設定
# nginx.conf
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://app:8000;
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;
}
# 静的ファイルはNginxで直接配信
location /static/ {
alias /app/static/;
}
}
デプロイ手順
# 1. サーバーにコードをデプロイ
git pull origin main
# 2. イメージをビルド
docker compose build --no-cache
# 3. 本番環境を起動
docker compose up -d
# 4. ログを確認
docker compose logs -f app
# 5. 状態を確認
docker compose ps
# 6. ローリングアップデート(停止なしで更新)
docker compose up -d --no-deps --build app
本番運用のポイント
ログの管理
# ログドライバーをjson-fileに設定してローテーション
services:
app:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
リソース制限
services:
app:
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.5"
memory: 256M
まとめ
- 本番環境ではGunicornなどの本番用WSGIサーバーを使う(flask runは開発用)
- マルチステージビルドでビルドツールを含まない軽量な本番イメージを作る
- Nginx + Gunicorn + DBの3層構成が本番の定番パターン
- 認証情報は環境変数で管理し、docker-compose.ymlに直書きしない
- ログはドライバー設定でローテーションさせてディスクを圧迫しないようにする



コメント