Flask Application Factories and Production Setup

Flask's minimal "hello world" — a module-level app = Flask(__name__) — is perfect for learning and painful for production. Module-level apps make configuration depend on import order, make it hard to create separate apps for tests, and invite global state that breaks under multiple workers. The application factory pattern fixes this: a create_app() function builds and configures a fresh app, initializes extensions, and registers blueprints, so every environment and test gets an app configured exactly as intended.

Production Flask also needs decisions the framework leaves to you: where configuration and secrets come from, how per-request state is stored and cleaned up, which WSGI server and worker model run the app, how it sits behind a reverse proxy, and how migrations and health checks fit into deployment. Documenting these choices is as important as the code.

TL;DR

Quick Example

An application factory (app/__init__.py):

Configuration classes and a test that builds its own app:

Running it in production, with the worker model stated explicitly:

Core Concepts

The Application Factory

A factory returns a configured app. It enables multiple apps with different settings (tests, CLI tools, workers), avoids circular imports by importing blueprints inside the function, and makes configuration explicit. The Flask CLI and WSGI servers can call it directly: flask --app "app:create_app()" run --debug for development, gunicorn "app:create_app()" for production.

Layered Configuration

Never ship with DEBUG = True or a default SECRET_KEY in production. See Twelve-Factor App and Configuration and Environment Management.

Extensions and Blueprints

Extensions such as Flask-SQLAlchemy, Flask-Migrate, and Flask-Login follow the init_app pattern: create the object at module level without an app, then bind it inside the factory. Blueprints group routes, templates, and error handlers by feature and are registered with URL prefixes. See SQLAlchemy.

Contexts and Request State

Flask pushes an application context (current_app, g) and a request context (request, session) for each request. Store per-request objects — the current user, a database session, a request ID — on g, and release resources in teardown_request or teardown_appcontext handlers, which run even when the request fails. Module-level mutable state is shared across requests within a worker and is not shared across workers, so it is wrong in both directions.

Workers and Deployment

Put Nginx or a load balancer in front for TLS, buffering slow clients, and static files, and tell Flask how many proxies to trust with ProxyFix so request.remote_addr and URL schemes are correct. See Nginx.

Operations

Log to stdout in a structured format with request IDs; expose /healthz (process alive) and optionally a readiness check (dependencies reachable); run database migrations as a deploy step before switching traffic, never on import; and serve static assets from Nginx or a CDN.

Best Practices

Fail Fast on Configuration

Validate required settings and secrets in the factory so misconfigured deployments never start.

Keep the Factory Side-Effect Free

Do not connect to databases, run migrations, or start threads at import or factory time beyond what is needed to register components.

Document the Worker Model

Record worker class, count, timeouts, and thread-safety assumptions alongside the deployment configuration.

Trust Proxies Precisely

Configure ProxyFix with the exact number of proxies; trusting more lets clients spoof IP addresses and schemes.

Common Mistakes

Module-Level App and Config

Global Mutable State

Development Server in Production

FAQ

What is a Flask application factory?

A function, usually create_app(), that creates, configures, and returns a new Flask app — initializing extensions and registering blueprints — instead of defining a global app at import time.

How should Flask load configuration?

From layered sources: default config classes, an instance folder for host-specific settings, and environment variables for secrets, validated at startup.

Why use init_app with extensions?

It separates creating the extension object from binding it to an app, so one extension instance works with multiple apps created by the factory, including test apps.

How many Gunicorn workers should I run?

Start with about 2 × CPU cores + 1 for sync workers, then adjust based on memory per worker, latency, and whether requests are CPU- or I/O-bound.

Why do I need ProxyFix?

Behind a reverse proxy, Flask sees the proxy's address and scheme. ProxyFix reads forwarded headers from a configured number of trusted proxies so client IPs and HTTPS detection are correct.

Related Topics

References