Documentation · Deploying your app

Deploy a Python application

WSGI and ASGI servers, binding to all interfaces, and why a containerised database in the same service keeps restarting.

Updated 2026-09-25 · 7 min read

Python deployments go wrong in a small number of predictable ways: the wrong server type, the wrong host binding, and an in-process database that outgrows the memory limit.

Choose the server to match your framework

Flask and Django are WSGI. FastAPI, Starlette and Socket.IO are ASGI. They are not interchangeable.

FrameworkServerCommand
FlaskGunicorngunicorn --bind 0.0.0.0:$PORT app:app
DjangoGunicorngunicorn --bind 0.0.0.0:$PORT project.wsgi
FastAPIUvicornuvicorn main:app --host 0.0.0.0 --port $PORT
Socket.IOUvicornuvicorn main:app --host 0.0.0.0 --port $PORT

Set this as the startup command on the Startup tab. Note --host 0.0.0.0 on Uvicorn: its default is 127.0.0.1, and the symptom is identical to every other runtime — a healthy-looking log and no external connectivity.

Why not the development server

Never run flask run or uvicorn without a production server in front of it. The Flask development server is explicitly not built for concurrent traffic: it handles requests one at a time, so a slow endpoint blocks every other request. The result is a service that works fine in testing and appears to hang under real load.

Container vs process count

Concurrency in Python costs memory linearly. Four Gunicorn workers on a 512 MB plan is four interpreter baselines plus your dependencies, which is how a service that fits on Basic dies on Starter.

As a starting point on a 512 MB container: one worker. On 1 GB: two. Use --workers 1 plus an async server instead of many sync workers where the framework supports it — async handles concurrency in one process, which is usually a much better fit for a small container.

Dependencies

Use a pinned requirements file, and install before starting:

pip install --no-cache-dir -r requirements.txt
gunicorn --bind 0.0.0.0:$PORT app:app

Pin exact versions. Unpinned dependencies mean a restart can install a newer release than the one you tested against, and Python releases do not hold a major version steady the way Node does.

--no-cache-dir is worth the flag on a small container: pip caches every wheel it downloads, and that cache consumes storage you need for your own files.

Do not host the database in the same container

Running MySQL or PostgreSQL inside your app's container is the most common cause of a service that restarts unpredictably. The database reserves memory well beyond its working set — the buffer pool alone can be sized to a large fraction of available RAM — so the two processes compete for the same limit and the kernel kills whichever is larger.

Provision a managed database from the Databases tab instead and connect over the network. It isolates the memory, survives an app restart, and keeps your data when you redeploy. Working with databases covers the connection setup.

If you must self-host a database, do it in a separate service and size that service for the database, not for your app.

These guides describe behaviour that is common across container platforms. Where a setting is specific to your service — your assigned port, your SFTP credentials, your startup command — it is shown in the panel rather than here, so check the Startup and Files tabs for your own values.

Found an error or something unclear? Let us know so we can correct it.