The number one Flask hosting mistake
Let's get the most common error out of the way first: never run flask run or app.run() in production. Flask's built-in server is a development tool — single-threaded by default, no security hardening, not built for load. Flask's own docs say this plainly.
In production you run Flask under a WSGI server, almost always Gunicorn (or uWSGI, or Waitress on Windows):
gunicorn 'app:create_app()' \
--workers 4 \
--bind 0.0.0.0:8000 \
--timeout 60If your Flask app does async work, you can run Gunicorn with gevent/eventlet workers, but classic sync workers cover most Flask apps fine.
What a good Flask host provides
- Runs a long-lived WSGI process with configurable workers.
- Managed Postgres/MySQL nearby (Flask-SQLAlchemy is the common ORM).
- Easy env vars and secrets (
SECRET_KEY,DATABASE_URL). - Automatic HTTPS and custom domains.
- A place to run migrations (Flask-Migrate / Alembic).
The platforms
Render and Railway
The path of least resistance for Flask. Both detect a Python app, you set a Gunicorn start command, attach managed Postgres, and you're live. Great defaults, good docs. Check current pricing tiers directly.
PythonAnywhere
A long-running, Python-focused host that's beginner-friendly and handles WSGI config for you. It's a comfortable on-ramp for learners and small apps, though it's less git-push-native than the modern PaaS crowd.
Google Cloud Run
Wrap Flask in a container and Cloud Run gives you serverless scale-to-zero. Flask works well here because Gunicorn-in-a-container is exactly what Cloud Run expects. Good for spiky traffic and pay-per-use economics.
PandaStack
My platform, noted for fairness. PandaStack auto-detects Python via buildpacks (or uses your Dockerfile), so a normal Flask repo deploys on git push. A minimal container:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["gunicorn", "app:create_app()", "--bind", "0.0.0.0:8000", "--workers", "4"]Add a managed PostgreSQL or MySQL and DATABASE_URL is injected automatically — Flask-SQLAlchemy reads it directly. Run migrations as a cronjob or one-off deploy step. You get live logs, automatic SSL, and cronjobs for scheduled Flask tasks.
Honest limits: newer platform; free-tier databases are dev/hobby sized; free-tier apps cold-start on preemptible nodes after idle. For a low-traffic Flask side project that's perfect; for steady production traffic, move to a tier that doesn't scale to zero.
Worker count, the practical version
The Gunicorn docs suggest (2 * cores) + 1 workers as a starting heuristic. The nuance for Flask:
- Workers are processes, each a full copy of your app in memory. On a 512MB instance, 4 heavy workers may OOM.
- If your app is I/O-bound (lots of DB/API calls), consider threaded workers (
--threads) or gevent to handle more concurrency per worker. - Measure memory per worker (
--max-requestshelps reclaim leaks) before scaling worker count.
| Instance | Reasonable workers |
|---|---|
| 0.25 vCPU / 512MB | 2 (sync) |
| 1 vCPU / 1GB | 3-4 |
| 2 vCPU / 2GB | 5 + threads |
Migrations and config
# Deploy step, once
flask db upgradeKeep SECRET_KEY in env vars, never in code. Use ProxyFix middleware when behind a reverse proxy/load balancer so Flask sees the real client IP and scheme:
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)References
- [Flask deployment options](https://flask.palletsprojects.com/en/stable/deploying/)
- [Gunicorn documentation](https://docs.gunicorn.org/en/stable/)
- [Flask-Migrate](https://flask-migrate.readthedocs.io/)
- [Werkzeug ProxyFix](https://werkzeug.palletsprojects.com/en/stable/middleware/proxy_fix/)
- [Cloud Run docs](https://cloud.google.com/run/docs)
---
Flask side project that needs a database and the occasional scheduled task? PandaStack's free tier has you covered with auto-wired Postgres and cronjobs. Start at [dashboard.pandastack.io](https://dashboard.pandastack.io).