# 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のテストまでを、公式ドキュメント最新版に忠実な実コードで体系化します。

- 公開日: 2026-06-26
- 著者: 友田 陽大
- タグ: Python, Flask, WSGI, アーキテクチャ設計, 本番運用, バックエンド
- URL: https://tomodahinata.com/blog/flask-production-guide

## 要点

- Flaskは『最小核 + 拡張』のWSGIマイクロフレームワーク。ORM・フォーム・検証は内蔵せず、必要なものだけを選んで載せる。現行安定版は3.1系（最小Python 3.9）
- 本番設計の背骨はアプリケーションファクトリ（create_app）。グローバルなappを捨て、設定・拡張・Blueprintを関数内で組み立てることで、テスト容易性と複数環境を同時に解決する
- 設定はfrom_prefixed_env()とインスタンスフォルダで12-factor化し、SECRET_KEYやDB URLをコードから追い出す。current_app/gでアプリ参照を引き回さない
- 本番では開発サーバーを使わず、Gunicorn（-w CPU×2）＋リバースプロキシ＋ProxyFixで動かす。SECRET_KEY・HttpOnly/Secure/SameSite Cookie・CSRF(Flask-WTF)・Jinjaの自動エスケープでセキュリティ境界を固める
- 各論（大規模構成・コンテキスト・テスト・デプロイ・セキュリティ・可観測性・技術選定）は本ピラーから各スポーク記事へ。経済産業大臣賞B2B SaaSのFlaskバックエンド運用知見を根拠に解説

---

## **導入：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` | [大規模構成ガイド](/blog/flask-application-factory-blueprints-large-app-structure-guide) |
| 2 | 設定管理 | `config.from_*` / インスタンスフォルダ | 本記事 §4 |
| 3 | コンテキスト | `current_app` / `g` / `request` / `session` | [コンテキスト徹底解説](/blog/flask-application-request-context-g-current-app-guide) |
| 4 | エラー処理・ログ | `errorhandler` / `dictConfig` | [エラー処理・可観測性ガイド](/blog/flask-error-handling-logging-observability-guide) |
| 5 | セキュリティ | `SECRET_KEY` / Cookie / CSRF / エスケープ | [セキュリティ実装ガイド](/blog/flask-security-sessions-csrf-secure-cookies-guide) |
| 6 | デプロイ | Gunicorn / `ProxyFix` / Docker | [本番デプロイガイド](/blog/flask-deployment-gunicorn-docker-production-wsgi-guide) |
| 7 | テスト | `test_client` / pytest fixtures | [テスト実践ガイド](/blog/flask-testing-pytest-test-client-fixtures-guide) |

そして、Flask を採用すべきか（FastAPI / Django との比較）という上流の意思決定は [技術選定ガイド](/blog/flask-vs-fastapi-vs-django-comparison-guide) にまとめています。

---

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

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

```python
# hello.py
from flask import Flask

app = Flask(__name__)


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

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

```bash
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` を**関数の中で組み立てて返す**。これにより「いつ・どの設定で・どの部品を載せて」アプリを作るかを呼び出し側が制御できます。公式チュートリアルの正準形がこれです。

```python
# 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 がファクトリを自動検出します。

```bash
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 も避けられます。

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

db = SQLAlchemy()
migrate = Migrate()
```

```python
# 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 コマンドまでの深掘りは、[大規模構成ガイド](/blog/flask-application-factory-blueprints-large-app-structure-guide) に分けています。

---

## **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` で型付けします。

```python
# 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
```

```python
# create_app 内
app.config.from_object("myapp.config.ProductionConfig")
app.config.from_prefixed_env()  # FLASK_SECRET_KEY, FLASK_SQLALCHEMY_DATABASE_URI ...
```

```bash
# 本番の環境変数（コンテナの 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_FALLBACKS` | `None` | 3.1 | 鍵ローテーション（旧鍵で署名検証を継続） |
| `SESSION_COOKIE_PARTITIONED` | `False` | 3.1 | CHIPS（パーティション化 Cookie）。有効化で `SECURE` も強制 |
| `MAX_FORM_MEMORY_SIZE` | `500_000` | 3.1 | 非ファイルのフォーム値サイズ上限（DoS 緩和） |
| `MAX_FORM_PARTS` | `1_000` | 3.1 | フォームのパート数上限（DoS 緩和） |
| `TRUSTED_HOSTS` | `None` | 3.1 | ルーティング時に Host ヘッダを検証（Host ヘッダ攻撃対策） |

---

## **5. コンテキスト：`current_app` と `g` で「引き回さない」**

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

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

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

```python
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_appcontext` と `teardown_request` の違い、バックグラウンドタスクで請求書のように context を持ち出す `copy_current_request_context`、`RuntimeError: Working outside of application context` の正しい対処までは、[コンテキスト徹底解説](/blog/flask-application-request-context-g-current-app-guide) で深掘りします。

---

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

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

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

```python
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` の扱いは、[エラー処理・可観測性ガイド](/blog/flask-error-handling-logging-observability-guide) に分けています。

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

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

```python
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** に拡張します（`RequestFormatter` で `request.url` 等を注入）。可観測性（相関 ID・Sentry・ヘルスチェック）の設計は同じく[エラー処理・可観測性ガイド](/blog/flask-error-handling-logging-observability-guide)へ。

---

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

Flask の「micro」は、セキュリティでも「賢い既定 + あなたの選択」です。本番で最低限固める境界を挙げます（深掘りは[セキュリティ実装ガイド](/blog/flask-security-sessions-csrf-secure-cookies-guide)）。

- **`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` / `Markup` は **`markupsafe`** から import します（`flask` からではない）。
- **セキュリティヘッダは既定で付かない**。HSTS・CSP・`X-Content-Type-Options: nosniff`・`X-Frame-Options` は自分で付けるか、**Flask-Talisman** に任せます。

```python
# 本番の 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** です。

```bash
# 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` を送れてしまう**ため、ここは慎重に。

```python
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` の扱いは、[本番デプロイガイド](/blog/flask-deployment-gunicorn-docker-production-wsgi-guide) に集約しています。

> 💡 **`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()` で実サーバーを立てずにリクエストを往復でき、ファクトリ構成なら**テスト専用設定のアプリを毎回作れます**。

```python
# 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()
```

```python
# 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`）までは、[テスト実践ガイド](/blog/flask-testing-pytest-test-client-fixtures-guide) で深掘りします。

---

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

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

```text
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_app` と `extensions.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 技術選定ガイド](/blog/flask-vs-fastapi-vs-django-comparison-guide) から始めるのが近道です。
