⑥Docker・コンテナ

DockerでPythonアプリを本番デプロイする【マルチステージビルド実践】

⑥Docker・コンテナ
記事内に広告が含まれています。

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に直書きしない
  • ログはドライバー設定でローテーションさせてディスクを圧迫しないようにする

コメント

タイトルとURLをコピーしました