Chuyển đến nội dung chính
友田 陽大
Flask in production
Python
Flask
WSGI
アーキテクチャ設計
本番運用
バックエンド

Flask 本番運用ガイド(3.1系):アプリケーションファクトリ・Blueprint・設定・コンテキスト・本番デプロイの全体像

Flask 3.1系を本番品質で設計・運用するための全体ガイド。WSGIマイクロフレームワークの思想、create_appのアプリケーションファクトリ、Blueprintによる分割、from_prefixed_envとインスタンスフォルダの設定管理、current_app/gのコンテキスト、拡張のinit_app、エラー処理・ロギング、SECRET_KEYと安全なCookie、Gunicorn+ProxyFixの本番デプロイ、pytestのテストまでを、公式ドキュメント最新版に忠実な実コードで体系化します。

Published
Reading time
17 min read
Author
友田 陽大
Chia sẻ

導入:Flask は「小さい」のではなく「核だけ」である

Flask は「小さなフレームワーク」と紹介されがちですが、その理解は本番運用では危険です。正確には、Flask は WSGI アプリケーションの核(ルーティング・リクエスト/レスポンス・テンプレート・設定・コンテキスト)だけを提供し、ORM もフォームも認証も内蔵しない、という設計思想のフレームワークです。公式が「micro」と呼ぶのは「機能が貧弱」という意味ではなく、「何を載せるかをあなたが決める」 という意味です。

この思想は両刃です。要件に最小フィットした構成を組めば、FastAPI や Django より薄く・速く・読みやすいバックエンドになります。一方で、核しかないがゆえに、構造(アプリケーションファクトリ・Blueprint・設定・コンテキスト・デプロイ)を自分で設計しなければ、小さなアプリがそのまま「グローバル変数とimport順序に支配されたレガシー」へ滑り落ちます。Flask 案件の失敗のほとんどは、Flask の機能不足ではなく、この構造設計の欠如から来ます。

本記事は、その構造を Flask 3.1 系(現行安定版)の公式ドキュメントに忠実に、本番品質で組み上げるためのピラー(全体地図)です。筆者は、経済産業大臣賞を受賞した B2B SaaS のバックエンドを Python / Flask / SQLAlchemy / PostgreSQL で設計・実装し、API Gateway → ALB → ECS(Fargate) 上で本番運用してきました。ここで示すのは、その実戦で必要だった設計だけです。

💡 この記事で扱うバージョンFlask 3.1 系(本稿執筆時点の最新安定版は 3.1.3、2026-02 リリース)を前提とします。Flask 3.1 は Python 3.9 以上が必要で、依存に Werkzeug(WSGI/HTTP 層)・Jinja(テンプレート)・MarkupSafe・ItsDangerous(署名)・Click(CLI)・Blinker(シグナル) を持ちます。本稿のコードは公式ドキュメントのパターンに基づきます。各論は本ピラーから個別のスポーク記事へリンクします。


1. 全体像:Flask アプリの「7 つの設計対象」

本番の Flask アプリで設計判断が必要になるのは、実は次の 7 つだけです。本ピラーはこの 7 つを俯瞰し、それぞれの深掘りを専用記事に渡します。

#設計対象中心 API / 概念深掘り記事
1アプリ構成create_app / Blueprint / init_app大規模構成ガイド
2設定管理config.from_* / インスタンスフォルダ本記事 §4
3コンテキストcurrent_app / g / request / sessionコンテキスト徹底解説
4エラー処理・ログerrorhandler / dictConfigエラー処理・可観測性ガイド
5セキュリティSECRET_KEY / Cookie / CSRF / エスケープセキュリティ実装ガイド
6デプロイGunicorn / ProxyFix / Docker本番デプロイガイド
7テストtest_client / pytest fixturesテスト実践ガイド

そして、Flask を採用すべきか(FastAPI / Django との比較)という上流の意思決定は 技術選定ガイド にまとめています。


2. 最小の Flask:まず「WSGI アプリ」であることを掴む

設計の話に入る前に、Flask が何であるかを 1 ファイルで確認します。公式クイックスタートの最小形です。

# hello.py
from flask import Flask

app = Flask(__name__)


@app.route("/")
def index():
    return "Hello, World!"

開発サーバーで起動します。

flask --app hello run --debug

ここで押さえるべきは 2 点です。

  1. app は WSGI アプリケーションである。公式の言葉を借りれば「Flask は WSGI アプリケーションであり、WSGI サーバーがそれを動かす」。つまり flask run の開発サーバーや本番の Gunicorn は 「サーバー」 であって、Flask 本体ではありません。この分離が、後述するデプロイ設計の出発点です。
  2. --debug は開発専用。公式は「開発サーバーを本番にデプロイしてはならない。安全でも、安定でも、効率的でもない」と明確に警告しています。本番起動は §8 で扱う Gunicorn です。

⚠️ アンチパターンif __name__ == "__main__": app.run() をエントリポイントにして、それを本番でそのまま起動する。app.run() は開発サーバーであり、本番では Gunicorn 等の WSGI サーバーから app オブジェクトを読み込ませます。app.run() は必ず if __name__ == "__main__": ブロックに置き、本番経路から隔離してください。

この最小形は学習には良いものの、グローバルな app をモジュールトップに置く点が、本番ではそのまま技術的負債になります。次節でそれを解消します。


3. アプリケーションファクトリ:本番 Flask の背骨

3.1 なぜグローバル app を捨てるのか

app = Flask(__name__) をモジュールトップに書くと、3 つの問題が構造的に発生します。

  • テストで設定を差し替えられないapp は import 時に確定するので、テスト用 DB やテスト用設定に切り替える隙がない。
  • 複数構成を持てない:本番・ステージング・テストで異なる設定のアプリを同時に作れない。
  • 循環 import を誘発する:ビューやモデルが app を import し、app 側もそれらを import するため、依存が循環する。

解決策が アプリケーションファクトリです。app関数の中で組み立てて返す。これにより「いつ・どの設定で・どの部品を載せて」アプリを作るかを呼び出し側が制御できます。公式チュートリアルの正準形がこれです。

# src/myapp/__init__.py
import os

from flask import Flask


def create_app(test_config: dict | None = None) -> Flask:
    # instance_relative_config=True で、設定をリポジトリ外の instance/ から読む
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        SECRET_KEY="dev",  # 本番では必ず上書き(§5・§7)
        DATABASE=os.path.join(app.instance_path, "myapp.sqlite"),
    )

    if test_config is None:
        # 本番/開発:instance/config.py があれば上書き(無ければ黙ってスキップ)
        app.config.from_pyfile("config.py", silent=True)
    else:
        # テスト:呼び出し側が渡した設定で上書き
        app.config.from_mapping(test_config)

    # instance フォルダは自動生成されない。明示的に作る
    os.makedirs(app.instance_path, exist_ok=True)

    @app.route("/health")
    def health():
        return {"status": "ok"}

    return app

起動も flask CLI がファクトリを自動検出します。

flask --app myapp run --debug

💡 ファクトリの自動検出flask --app は、対象モジュールに create_app または make_app という名前の関数があればファクトリとして自動的に呼び出します。引数を渡したいときは flask --app 'myapp:create_app("dev")' run のように、括弧内を Python リテラルとして書けます。本番の Gunicorn でも gunicorn 'myapp:create_app()' と同じ書式が使えます(§8)。

3.2 拡張は「未束縛 → init_app で束縛」

Flask-SQLAlchemy のような拡張も、ファクトリと整合させます。拡張オブジェクトはモジュールトップで app 抜きに生成し、ファクトリ内で init_app(app) を呼んで束縛します。これで 1 つの拡張オブジェクトが複数アプリ(本番・テスト)に再利用でき、循環 import も避けられます。

# src/myapp/extensions.py — どのアプリにも束縛されていない「裸」の拡張
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

db = SQLAlchemy()
migrate = Migrate()
# src/myapp/__init__.py(抜粋)
from .extensions import db, migrate


def create_app(test_config=None):
    app = Flask(__name__, instance_relative_config=True)
    # ...設定読み込み...

    db.init_app(app)              # ここで初めて app に束縛
    migrate.init_app(app, db)

    from .blueprints.api import bp as api_bp
    app.register_blueprint(api_bp, url_prefix="/api")

    return app

このパターンと、循環 import を避けるパッケージ構成、Blueprint の入れ子・CLI コマンドまでの深掘りは、大規模構成ガイド に分けています。


4. 設定管理:秘密情報をコードから追い出す

app.config は「大文字のキーだけを意味のある設定として扱う」辞書サブクラスです。本番では コードに秘密情報(SECRET_KEY・DB パスワード・API キー)を書かない——これは 12-factor の核であり、セキュリティの最低ラインです。Flask は複数の読み込み口を用意しています。

メソッド用途
from_mapping(**kw)コード内のデフォルト値app.config.from_mapping(SECRET_KEY="dev")
from_object(obj)環境別 Config クラスapp.config.from_object("myapp.config.Production")
from_pyfile(path, silent=True)インスタンスフォルダの Python 設定app.config.from_pyfile("config.py", silent=True)
from_prefixed_env()環境変数(FLASK_ 前綴り)app.config.from_prefixed_env()
from_file(path, load=...)JSON / TOML 設定app.config.from_file("config.toml", load=tomllib.load, text=False)

4.1 推奨:from_object(既定値)→ from_prefixed_env(環境上書き)

筆者が本番で使う基本形は、「Config クラスで安全な既定値を与え、環境変数で本番値を上書きする」 二段構えです。from_prefixed_env()Flask 3.0 で追加された、コンテナ/12-factor 時代に最適なメソッドで、FLASK_ で始まる環境変数を自動で読み込み、値は json.loads で型付けします。

# src/myapp/config.py
class BaseConfig:
    JSON_SORT_KEYS = False
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SAMESITE = "Lax"


class ProductionConfig(BaseConfig):
    SESSION_COOKIE_SECURE = True  # HTTPS 限定 Cookie(§7)


class TestConfig(BaseConfig):
    TESTING = True
# create_app 内
app.config.from_object("myapp.config.ProductionConfig")
app.config.from_prefixed_env()  # FLASK_SECRET_KEY, FLASK_SQLALCHEMY_DATABASE_URI ...
# 本番の環境変数(コンテナの Secrets から注入)
export FLASK_SECRET_KEY='...'                 # python -c 'import secrets; print(secrets.token_hex())'
export FLASK_SQLALCHEMY_DATABASE_URI='postgresql+psycopg://...'
export FLASK_MAX_CONTENT_LENGTH=10485760       # json.loads されて int になる

💡 from_prefixed_env の威力:値が json.loads で解釈されるため、FLASK_MAX_CONTENT_LENGTH=10485760 は文字列ではなく int 10485760 として読み込まれます。__(二重アンダースコア)でネストしたキーにも代入できます。「環境変数は全部文字列で扱いづらい」という典型的な悩みを、Flask 側が解消してくれます。

4.2 インスタンスフォルダ:リポジトリ外の設定置き場

Flask(__name__, instance_relative_config=True) を指定すると、app.instance_path(既定で <project>/instance/)が「リポジトリにコミットしない設定・SQLite・アップロードの置き場」になります。from_pyfile("config.py") はこの instance/ からの相対で解決されます。instance/ は必ず .gitignore に入れてください。

⚠️ DEBUG は設定ファイルに書かない。公式は「DEBUG をコードや設定で有効化すると期待通りに動かないことがある」と注意しています。デバッグは FLASK_DEBUG=1 環境変数か flask run --debug で、開発時のみ有効にします。本番で DEBUG=True はインタラクティブデバッガを晒す重大な脆弱性です。

4.3 Flask 3.1 で増えた「本番で効く」設定

Flask 3.1 系では、本番の堅牢化に直結する設定が追加されています。詳細は各スポークで扱いますが、ピラーとして一覧します。

設定キー既定値追加効果
SECRET_KEY_FALLBACKSNone3.1鍵ローテーション(旧鍵で署名検証を継続)
SESSION_COOKIE_PARTITIONEDFalse3.1CHIPS(パーティション化 Cookie)。有効化で SECURE も強制
MAX_FORM_MEMORY_SIZE500_0003.1非ファイルのフォーム値サイズ上限(DoS 緩和)
MAX_FORM_PARTS1_0003.1フォームのパート数上限(DoS 緩和)
TRUSTED_HOSTSNone3.1ルーティング時に Host ヘッダを検証(Host ヘッダ攻撃対策)

5. コンテキスト:current_appg で「引き回さない」

ファクトリでアプリを作ると、「app をどこからも import できない」状況が生まれます(import 時には存在しないため)。Flask はこれを コンテキストローカルで解決します。

  • current_app:いま処理中のアプリへのプロキシ。app を import せずに設定や拡張へ届く。
  • g:いまのコンテキスト(≒1 リクエスト)の間だけ生きる名前空間。DB 接続のキャッシュなどに使う。
  • request / session:いまのリクエスト/セッション。

公式が示す典型は「リクエスト中は同じ DB 接続を使い回し、終了時に閉じる」パターンです。

from flask import g, current_app


def get_db():
    if "db" not in g:                       # リクエスト内で初回だけ接続
        g.db = connect_to_database(current_app.config["DATABASE"])
    return g.db


@app.teardown_appcontext
def teardown_db(exception):                  # コンテキスト終了時に必ず閉じる
    db = g.pop("db", None)
    if db is not None:
        db.close()

💡 g は「グローバル変数」ではない。公式は明言しています——「g の "g" は global だが、それはコンテキスト内でグローバルという意味。データはコンテキスト終了で失われ、リクエストをまたぐ保存には使えない。リクエスト間の保存には session か DB を使う」。さらに重要な事実として、Flask のコンテキストローカルは Python の contextvars と Werkzeug の LocalProxy で実装されており、単なるスレッドローカルではありません。これが async ビューでも正しく機能する理由です。

コンテキストの押し込み/取り出しの仕組み、_get_current_object()teardown_appcontextteardown_request の違い、バックグラウンドタスクで請求書のように context を持ち出す copy_current_request_contextRuntimeError: Working outside of application context の正しい対処までは、コンテキスト徹底解説 で深掘りします。


6. エラー処理とロギング:本番で「沈黙しない」

6.1 エラーハンドラ:HTML ではなく JSON を返す API 設計

REST API では、例外を HTML エラーページではなく構造化 JSON で返すのが定石です。Flask は @app.errorhandler で例外クラス/ステータスコードごとにハンドラを登録できます。

from flask import jsonify
from werkzeug.exceptions import HTTPException


@app.errorhandler(HTTPException)
def handle_http_exception(e: HTTPException):
    """すべての HTTP エラーを JSON で返す。"""
    return jsonify(code=e.code, name=e.name, description=e.description), e.code or 500


@app.errorhandler(404)
def not_found(e):
    return jsonify(error="resource not found"), 404

ビューからは abort(404, description="...") でエラーを送出します。ハンドラの解決順序(コード → クラス階層 → 最も具体的なもの)、Blueprint 側ハンドラの優先と「Blueprint は 404 を捕捉できない」例外、InternalServerError.original_exception の扱いは、エラー処理・可観測性ガイド に分けています。

6.2 ロギング:dictConfig を「アプリ生成より前に」

Flask のロギングは標準 logging そのものです。公式の最重要注意は 「ロギングはアプリ生成より前に設定せよ」——app.logger を設定前に触ると既定ハンドラが付いてしまうためです。正準形は logging.config.dictConfig です。

from logging.config import dictConfig

dictConfig({
    "version": 1,
    "formatters": {"default": {
        "format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
    }},
    "handlers": {"wsgi": {
        "class": "logging.StreamHandler",
        "stream": "ext://flask.logging.wsgi_errors_stream",
        "formatter": "default",
    }},
    "root": {"level": "INFO", "handlers": ["wsgi"]},
})

app = Flask(__name__)  # ← dictConfig の後で生成する

本番では、これを JSON 構造化ログ + リクエスト ID に拡張します(RequestFormatterrequest.url 等を注入)。可観測性(相関 ID・Sentry・ヘルスチェック)の設計は同じくエラー処理・可観測性ガイドへ。


7. セキュリティ:境界を「設定」で固める

Flask の「micro」は、セキュリティでも「賢い既定 + あなたの選択」です。本番で最低限固める境界を挙げます(深掘りはセキュリティ実装ガイド)。

  • SECRET_KEY を必ず設定する。Flask の sessionクライアント側の署名付き Cookie(ItsDangerous で署名)です。改ざんは検知できますが、SECRET_KEY が漏れる・弱いと署名を偽造されます。生成は python -c 'import secrets; print(secrets.token_hex())'
  • Cookie 属性を固めるSESSION_COOKIE_HTTPONLY(既定 True)、SESSION_COOKIE_SECURE(既定 False本番は True)、SESSION_COOKIE_SAMESITE"Lax" 推奨)。
  • CSRF は内蔵されていない。公式は「フォーム検証フレームワークは Flask に存在しない」と明言。CSRF 対策は Flask-WTF の CSRFProtect で導入します。
  • XSS は Jinja の自動エスケープが既定.html / .htm / .xml / .xhtml テンプレートは自動エスケープされます。escape / Markupmarkupsafe から import します(flask からではない)。
  • セキュリティヘッダは既定で付かない。HSTS・CSP・X-Content-Type-Options: nosniffX-Frame-Options は自分で付けるか、Flask-Talisman に任せます。
# 本番の Cookie 既定(ProductionConfig 等で)
app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE="Lax",
)

8. デプロイ:開発サーバーを捨て、Gunicorn で動かす

本番では flask run(開発サーバー)を使いません。Flask は WSGI アプリなので、WSGI サーバーapp を読み込んで動かします。Linux の定番は Gunicorn です。

# app オブジェクトを直接指す場合
gunicorn -w 4 'myapp:app'

# アプリケーションファクトリの場合(§3)
gunicorn -w 4 'myapp:create_app()'
  • -w(ワーカー数):公式の出発点は CPU × 2。既定は 1 ワーカーで、本番には不足します。
  • リバースプロキシの背後では ProxyFix。nginx / ALB の背後で X-Forwarded-* を信頼するには、Werkzeug の ProxyFix を噛ませます。信頼するプロキシ段数を正しく設定しないと、クライアントが偽の X-Forwarded-For を送れてしまうため、ここは慎重に。
from werkzeug.middleware.proxy_fix import ProxyFix

app.wsgi_app = ProxyFix(
    app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
)

ワーカー種別(同期 vs gevent)の選び方、Docker のマルチステージ・非 root・ヘルスチェック、グレースフルシャットダウン、TRUSTED_HOSTS/SERVER_NAME の扱いは、本番デプロイガイド に集約しています。

💡 async def ビューの誤解:Flask は 2.0 から async def ビューに対応(pip install flask[async])しますが、公式は明確に注意しています——「各リクエストは依然 1 ワーカーを占有する。async ビューにしても同時に捌けるリクエスト数は変わらない。async が効くのは「1 ビュー内で複数の外部 API を並行呼び出しする」ような IO 並行であって、スループット向上ではありません。本格的な async が要件なら ASGI 前提の Quart を検討する、というのが公式の立場です。


9. テスト:test_client と pytest fixtures で契約を固定する

Flask はテスト容易性が高いフレームワークです。app.test_client() で実サーバーを立てずにリクエストを往復でき、ファクトリ構成ならテスト専用設定のアプリを毎回作れます

# tests/conftest.py
import pytest
from myapp import create_app


@pytest.fixture()
def app():
    app = create_app({"TESTING": True})
    yield app


@pytest.fixture()
def client(app):
    return app.test_client()


@pytest.fixture()
def runner(app):
    return app.test_cli_runner()
# tests/test_health.py
def test_health(client):
    res = client.get("/health")
    assert res.status_code == 200
    assert res.json == {"status": "ok"}

TESTING=True の意味、session_transaction() でのセッション操作、with client: でのリクエスト後検証、follow_redirects、CLI コマンドのテスト(test_cli_runner)までは、テスト実践ガイド で深掘りします。


10. 推奨パッケージ構成:すべてをつなぐ

ここまでの設計を 1 つのディレクトリ構成にまとめます。これが本番 Flask の「型」です。

myapp/
├── pyproject.toml
├── src/
│   └── myapp/
│       ├── __init__.py        # create_app(アプリケーションファクトリ)§3
│       ├── config.py          # 環境別 Config クラス §4
│       ├── extensions.py      # db = SQLAlchemy() など「裸」の拡張 §3.2
│       ├── logging.py         # dictConfig §6.2
│       ├── errors.py          # 共通エラーハンドラ §6.1
│       ├── models/            # SQLAlchemy モデル
│       └── blueprints/
│           ├── auth/          # 認証 Blueprint
│           └── api/           # API Blueprint
├── instance/                  # .gitignore(秘密・SQLite)§4.2
│   └── config.py
└── tests/
    ├── conftest.py            # app / client / runner fixtures §9
    └── test_*.py

💡 「3 つ目で抽出」の原則:最初から blueprints/ を細かく割る必要はありません。1 つのファイルで始め、機能が 2 つ・3 つと増えてから Blueprint に分割する——YAGNI です。ただし create_appextensions.py の分離だけは最初から入れてください。これは「将来の拡張」ではなく「テスト容易性という現在の要件」を満たすための分離だからです。


まとめ:Flask は「設計の自由」に責任を持つフレームワーク

Flask の本質は 「核だけを提供し、構造はあなたが決める」 ことです。だからこそ、本番品質は次の規律で決まります。

  1. アプリケーションファクトリcreate_app)でグローバル app を捨て、テスト容易性と複数環境を同時に得る。
  2. 設定を from_prefixed_env + インスタンスフォルダで 12-factor 化し、秘密をコードから追い出す。
  3. current_app / g でアプリ参照を引き回さず、コンテキストの寿命に資源を縛る。
  4. エラーは JSON で、ログは dictConfig、本番で沈黙しないようにする。
  5. SECRET_KEY・安全な Cookie・CSRF・自動エスケープでセキュリティ境界を設定で固める。
  6. 開発サーバーを捨て、Gunicorn + ProxyFixで動かす。
  7. test_client + pytest fixtures で境界の契約をテストに固定する。

この 7 つは、FastAPI でも Django でも形を変えて必要になる普遍的な設計対象です。Flask はそれを最も薄く、最も明示的に書かせてくれる——それが、適切に設計されたときの Flask の強さです。各論の深掘りは、本ピラーからリンクした各スポーク記事へお進みください。Flask を採用すべきかどうかの判断自体は、Flask vs FastAPI vs Django 技術選定ガイド から始めるのが近道です。

友田

友田 陽大

Người phát triển sản phẩm đoạt Giải thưởng Bộ trưởng (METI). Với TypeScript + Python + AWS, tôi một mình triển khai trọn gói SaaS, chuyển đổi số công nghiệp và AI tạo sinh (RAG) sẵn sàng cho production — từ phân tích yêu cầu đến hạ tầng và vận hành.

Got a challenge?

From design to implementation and operations — solo × generative AI

Implementation like this article's, end to end from requirements to production. Start with a free 30-minute technical consult and tell me about your situation.

Nhận cả dự án trọn gói lẫn vai trò cố vấn kỹ thuật. Hãy bắt đầu bằng buổi tư vấn miễn phí 30 phút.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Cũng đáng đọc