Skip to main content
A Python web application runs on Sevalla as an application: Sevalla builds an image from your Git repository, then runs it as processes. The web process serves HTTP with a production server such as Gunicorn or Uvicorn, background workers run task queues such as Celery, and jobs run migrations before each deployment.

Framework guides

Django

Deploy Django, then add object storage, caching, async views, Celery, health checks and multitenancy.

FastAPI

Deploy FastAPI with SQLModel and Alembic, then add object storage, caching, Celery and health checks.

Flask

Deploy Flask with an application factory, then add caching, Celery and health checks.

Streamlit

Deploy a Streamlit data app with uv, including file uploads, secrets and health checks.

Wagtail

Deploy a Wagtail site on Django with PostgreSQL, images and documents in object storage, and the admin over HTTPS.

Litestar

Deploy a Litestar API with Uvicorn, PostgreSQL through Advanced Alchemy, and migrations in a job.

Dash

Deploy a Plotly Dash app with Gunicorn, and scale its callbacks across instances.

MkDocs

Deploy an MkDocs site with Material as a free static site built with uv, or as an application from a Dockerfile.

Gradio

Deploy a Gradio app with uv, and call it from Python with gradio_client.
Moving from Heroku? See Migrate from Heroku.

How Python applications run on Sevalla

Every process (web, worker, cron job and job) runs from the same image, each with its own start command. See Processes for every process type and its settings.

Pin the Python version

Ingot builds on Python 3.14, its default line, unless the project sets a version. Pin the version you develop with in .python-version, so the build doesn’t change when a new Ingot release moves its default line:
.python-version
Ingot takes the version from the first of these that’s set: the INGOT_PYTHON_VERSION environment variable, versions.python in ingot.json, .python-version, .tool-versions, mise’s configuration, runtime.txt, requires-python in pyproject.toml (or Poetry’s python dependency), then python_version in the Pipfile. A line such as 3.14 builds on that line’s newest release, so every new build picks up its security fixes. The build plan’s Python row shows the version and where it came from. When no version is set and requirements.txt pins a release with no Linux wheels for the default line, Ingot builds on the newest line those pins have wheels for, and the build log names the pin that chose it. See Set the language version.

Build with a Dockerfile

Every Python guide ends its deployment with a Deploy with a Dockerfile section: Django, FastAPI, Flask, Streamlit, Gradio, Wagtail, Litestar and Dash. Their Dockerfiles build on python:3.14-slim-trixie and do by hand what Ingot does for you:
  • Proxy headers. Set FORWARDED_ALLOW_IPS="*", so Gunicorn and Uvicorn trust the X-Forwarded-Proto header from Sevalla’s proxy. Without it, a Django admin sign-in over HTTPS fails with CSRF verification failed. Request aborted. Set PYTHONUNBUFFERED=1 too, so logs reach the runtime logs as they’re written.
  • Gunicorn settings. Ingot’s Gunicorn runs each worker with 4 threads, gives requests 20 seconds to finish after SIGTERM, and closes each connection after its response. A Dockerfile passes the same as --threads 4 --graceful-timeout 20 --keep-alive 0: without --keep-alive 0, Gunicorn 26.2 waits out the whole graceful timeout before it stops whenever Sevalla’s proxy holds an idle connection open.
  • Commands for jobs and workers. Sevalla runs a process’s custom start command in a login shell, /bin/sh -l -c, and keeps the image’s PATH in it. Commands installed with pip into the image’s Python, or into a uv virtual environment whose bin folder the Dockerfile puts on PATH, are found the same way as in the image’s CMD.
  • Static files. For Django, run collectstatic during the build with a placeholder SECRET_KEY, so the real key never enters the image.
  • User. Run as user 1000 with a home directory, where Gunicorn keeps its control socket, and leave the code owned by root, so the application can’t change it.

Connection variables

When you add an internal connection and turn on Add environment variables to the application, Sevalla adds these variables (see Environment variables an internal connection adds). You can rename them in the same sheet before you save, for example DB_URL to DATABASE_URL. Two schemes need care in Python:
  • SQLAlchemy (and so SQLModel, Flask-SQLAlchemy-Lite and Alembic) doesn’t accept postgres://. Replace it with postgresql+psycopg:// in code. Django’s database URL parsers accept it as is.
  • redis-py, Celery and Django’s Redis cache accept redis://, rediss:// and unix:// only. A Valkey REDIS_URL added before Oct 30, 2026 starts with valkey://: replace it with redis://.