Best Flask Hosting Platforms (2026)
Flask has a peculiar deployment problem: it is so easy to run locally that the gap between 'works on my machine' and 'works in production' catches people off guard. You type flask run, see the dev-server warning in the log, ignore it, and eventually ship it. Then you find out what that warning meant when your third concurrent user arrives.
So this roundup is organised around the things that actually decide whether a Flask deployment is any good — the WSGI server, how many workers you get for your money, what happens to background work, and how the platform handles a long-running process — rather than which dashboard looks nicest.
What actually matters for a Flask app
- Does the platform run a real WSGI server? You want gunicorn or uwsgi in front of your app, not the Werkzeug development server, which is single-threaded by default and explicitly not for production.
- How many workers can you afford? Flask is synchronous by default — a request that blocks on a slow database query occupies a worker for its whole duration. Concurrency is worker count, and worker count is memory.
- Is there a place for background work? The classic Flask stack pairs the web process with Celery or RQ and a broker. If the platform makes a second process type awkward, you will end up doing background work in a request handler, and that goes badly.
- Does it scale to zero, and what does waking cost? For internal tools and side projects, paying for an idle server around the clock is the main cost, and cold-start behaviour is the main annoyance.
- Can you get a real Postgres next to it? Most Flask apps are Flask plus SQLAlchemy plus Postgres. A platform that makes the database someone else's problem adds a migration and a latency hop.
Render and Railway — the default answer, and a good one
For most Flask apps, a git-push PaaS is the right call, and Render and Railway are the two most people land on. You connect a repo, they detect Python, you give them a start command, and you get a URL. Both offer managed Postgres in the same project, which removes the most common source of latency and configuration pain.
Render's Python builds are conventional — a requirements file, a build command, a start command — and it has a clear worker-service type for Celery. Railway leans on its own build system and is generally the faster of the two to get from zero to a URL. Pick either when your priority is not thinking about infrastructure, and be aware that both are container-per-service platforms: your per-service costs are the provisioned instance, not the traffic.
Fly.io — when latency to users is the requirement
Fly runs your app as Firecracker microVMs in regions you choose, which makes it the natural pick when you need the app near users in more than one place, or when you want VM-level isolation without operating VMs. You bring a Dockerfile — or let their launcher generate one — and you get real control over process types and machine sizes.
The trade is that you are closer to the metal than on Render or Railway. That is exactly what some teams want and exactly what others do not. If nobody on the team wants to think about a Dockerfile, that is a legitimate reason to choose something else.
PythonAnywhere and Heroku — the old guard
PythonAnywhere is the most Flask-specific option here: a Python-only host, with a web-app configuration UI that wires up a WSGI file for you, and a free tier that has taught an enormous number of people to deploy their first app. For a small app, a class project, or an internal tool, it is genuinely the shortest path, and the constraints (a fixed process model, limited outbound access on free tiers) are usually fine at that size.
Heroku is still a completely reasonable place to run Flask — the Python buildpack works, the Procfile model maps cleanly onto web plus worker, and the add-on ecosystem is deep. The reason it appears lower in these roundups than it used to is price relative to the newer PaaS options, not capability.
AWS — ECS, App Runner, or Lambda
If you are already on AWS, the sensible options are App Runner for a container you do not want to orchestrate, ECS Fargate when you need control, or Lambda behind an adapter when your traffic is spiky and your handlers are short. That last one deserves a caution: Flask on Lambda works, but you inherit the serverless constraints — no long-lived connections, no background threads that outlive the response, and connection-pool behaviour that fights you unless you put a pooler in front of Postgres.
PandaStack — a microVM per app, asleep when idle
PandaStack is our project. An app is a git repo we build and run inside a Firecracker microVM with its own guest kernel, behind a stable URL. Python is a first-class runtime: the platform reads your .python-version or .tool-versions through mise, installs dependencies from your requirements, poetry, uv, or pipenv setup, and runs the start command you give it.
Flask specifically is commands-first, and deliberately so: we will not guess whether you want gunicorn, uwsgi, or a plain script, because guessing wrong produces a confusing failure. Give us the start command and we run exactly that.
{
"framework": "python",
"install_command": "pip install -r requirements.txt",
"start_command": "gunicorn -w 4 -b 0.0.0.0:$PORT app:app",
"port": 8080
}Two things to note in that snippet, because they are the two most common Flask deployment bugs anywhere, not just here. Bind to 0.0.0.0, not 127.0.0.1 — a server listening on loopback inside a VM or container is invisible from outside it. And read the port from the environment rather than hard-coding 5000, so the platform's health check and your server agree about where the app is.
The parts that are ours rather than generic: idle apps scale to zero and bill nothing while asleep, and waking is a snapshot restore rather than a cold boot. Billing is $0.054 per active vCPU-hour and $0.0162 per working-set GiB-hour, metered per second, with network egress not billed at all — so a mostly-idle internal Flask tool costs close to nothing between requests. Managed Postgres runs on the same substrate, and you attach it to the app as an environment variable.
The honest counterweights: we are a younger platform than Render or Heroku, the ecosystem of one-click add-ons does not exist, and if your app needs a GPU we are not the answer. Self-hosting the whole stack is possible — the core is Apache-2.0 — but it means Linux hosts with KVM, which is real operational weight.
The worker-count math nobody does before deploying
Whichever platform you pick, do this arithmetic once. Each gunicorn sync worker is a full copy of your app in memory, and it handles exactly one request at a time. If a request takes 200ms and you run 4 workers, your ceiling is roughly 20 requests per second — and one slow endpoint that takes 2 seconds will drag that number down hard, because it holds a worker hostage for ten times as long as everything else.
Two fixes, both cheap. Move anything slow and non-essential out of the request path into a background job. And if your handlers are I/O-bound — waiting on a database, an HTTP call, a queue — switch the worker class to gthread or gevent so a worker can hold many in-flight requests instead of one. Measure the memory per worker before you raise the count; the usual failure is picking a worker number from a blog post and discovering the OOM killer disagrees.
Frequently asked questions
Can I deploy Flask without writing a Dockerfile?
On most modern platforms, yes. Render, Railway, PythonAnywhere, Heroku, and PandaStack all detect a Python repo and build it from your requirements without a Dockerfile — you supply the start command. Fly.io and the AWS container services expect an image, though Fly's launcher can generate a starter Dockerfile for you. Writing one yourself is still worth it if you have unusual system dependencies, because it makes the build reproducible instead of dependent on the platform's detection.
How many gunicorn workers should I run?
Start from memory, not a formula. Measure the resident size of one worker under load, divide the memory you have by that number, leave headroom, and stop there. The often-quoted 2n+1 formula assumes CPU-bound work and enough RAM to back it, which is frequently not true for a Flask app holding an ORM session and a few caches. If your work is I/O-bound, you will get far more from switching to threaded or async workers than from adding sync ones.
Where should background jobs run for a Flask app?
In a separate process, always — never in a thread spawned from a request handler, because that thread dies with the worker and takes the job with it. The conventional stack is Celery or RQ with Redis as the broker, deployed as a second service that shares your code. Most platforms support this as a worker service type. If your jobs are periodic rather than queued, a scheduled task that runs the job on a cron expression is simpler and avoids the broker entirely.
Does Flask work with scale-to-zero hosting?
Yes, and it is usually a good fit, because a Flask process is cheap to start and most small Flask apps are idle most of the time. The thing to check is what waking actually costs on your platform: restoring a snapshot of an already-running process is roughly a second, while a genuine cold start means booting, installing nothing, but re-importing your whole dependency tree — which for a heavy app with pandas or a large ORM model set can be several seconds. Measure it on your own app rather than trusting the platform's headline number.
Keep reading
49ms p50 cold start. Fork, snapshot, and scale to zero.