Skip to content
Docs

Python

Deploy Django, FastAPI or Flask. We pick Python 3.10 to 3.14 from your code, install with pip, uv, Poetry or Pipenv, and start your app on port 8080.

Updated 27 Oct 20264 min read

A Python app deploys from its requirements.txt, pyproject.toml or Pipfile. We choose Python, install your packages into the app's own virtual environment, and start Django, FastAPI or Flask for you. Your app listens on the port in the PORT variable, which is always 8080.

Versions

VersionNotes
Python 3.14Supported
Python 3.13What new apps get when the code doesn't ask
Python 3.12Supported
Python 3.11Supported
Python 3.10No more security fixes from its makers: it still builds, with a warning

We read the version from .python-version, runtime.txt (python-3.12), requires-python in pyproject.toml and python_version in Pipfile, and use our default, Python 3.13, when it meets all of them, otherwise the newest version that does. requires-python = ">=3.11" builds with 3.13; ">=3.10,<3.13" builds with 3.12. To stay on one version, write it in .python-version (3.12), choose it in the app's Set up and run settings, or set "version" in tanzahost.json.

How packages are installed

Your code hasWe run
uv.lockuv sync --frozen --no-dev
poetry.lockpoetry install --only main
PipfilePipenv's requirements, installed with pip
requirements.txtpip install -r requirements.txt
Only pyproject.toml or setup.pypip install .

Poetry, uv and Pipenv run at a fixed version. Packages that compile against a system library get it added for you, with the reason on the plan: psycopg2 without its binary wheel gets the PostgreSQL client library, mysqlclient the MySQL one, lxml libxml2 and libxslt, python-ldap the LDAP library. Everything goes into a virtual environment that's already active for the build, the app, its workers and its scheduled tasks. PYTHONUNBUFFERED=1 is set, so what you print shows in your logs at once.

How your app starts

FrameworkStart command
A Procfile with a web: lineThat line
Djangogunicorn yourproject.wsgi:application --bind 0.0.0.0:${PORT}
FastAPIuvicorn main:app --host 0.0.0.0 --port ${PORT}
Flaskgunicorn main:app --bind 0.0.0.0:${PORT}
Anything elsepython main.py, app.py or server.py

For Django we read your project's name from manage.py. For FastAPI and Flask we look for the line that makes the app (app = FastAPI()) in main.py, app.py, server.py or api.py. When gunicorn isn't in your packages, we add it to the app's own environment for you; your files don't change. An app in a folder of the repository, like a template's backend, is found by itself.

Example: a Django shop

Asha's friend Neema moves her Django shop, project shop, to Tanzahost. Her repository has manage.py, requirements.txt with Django and psycopg2 (without the binary wheel), and .python-version with 3.12. The plan says: Django on Python 3.12, the PostgreSQL client library added for psycopg2, a PostgreSQL database made for it with its price first. She clicks Deploy. We install her packages, run collectstatic during the build and migrate before the version goes live, and start:

Terminal
gunicorn shop.wsgi:application --bind 0.0.0.0:${PORT}

Her variables are filled before the first deploy: SECRET_KEY made and kept encrypted, DJANGO_DEBUG=False where her settings read it, ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS set to her app's address, and DATABASE_URL from the database. A setting she already set is never replaced. See Connect an app to a database.

Django's ALLOWED_HOSTS and the health check

Before a new version gets visitors, our health check asks it for its health address at the app's private address, not at your domain. Django answers that with an error (400) when ALLOWED_HOSTS lists only your domains, so the new version never goes live. Either set ALLOWED_HOSTS to *, or keep your list of addresses and give the health check a path of its own, as DisallowedHost shows.

Django's migrate --noinput runs before each version goes live while Run migrations on each deploy is on (it is when we made the database). If it fails, the deploy stops and the version before keeps serving. See Deploying.

A Django app that uses SQLite moves to PostgreSQL by default, because a file database would be lost at every deploy. Choose Keep SQLite (one copy only) on the plan to keep the file in a kept folder instead.

Static files and uploads

collectstatic --noinput runs during the build. With WhiteNoise in your packages, Django serves them; otherwise they're served from STATIC_ROOT. Your MEDIA_ROOT is kept between deploys.

Workers and tasks

Celery, RQ or any long-running script runs as a worker, like celery -A shop worker. When your code uses Celery, or a Procfile has a worker: line, the plan adds the worker (and Celery beat, when you use it) with its price, on by default. Jobs on a timetable run as scheduled tasks, like python manage.py clearsessions every night.