Skip to main content

Deploying Python on Vessl

Vessl detects Python apps via requirements.txt, pyproject.toml, or Pipfile and builds them with Nixpacks. This guide focuses on FastAPI and ASGI apps, but the same flow applies to Flask, aiohttp, and other Python web frameworks.

Prerequisites

  • A server added and provisioned in Vessl
  • A GitHub repository with your Python app
  • At least one of: requirements.txt, pyproject.toml, or Pipfile

What Vessl auto-detects

When Vessl finds requirements.txt or pyproject.toml (without manage.py) it sets:

SettingAuto-filled value
Build engineNixpacks
Port8000

Nixpacks installs your dependencies and starts your app. For FastAPI apps it typically picks up uvicorn from your requirements and sets the start command accordingly. If it doesn't, set it manually (see below).

Step 1 — Set the start command

Nixpacks auto-detects a Procfile or a uvicorn/gunicorn entry in pyproject.toml. If not, set the Start command in the wizard or app Settings:

FastAPI with Uvicorn:

uvicorn main:app --host 0.0.0.0 --port ${PORT:-8000}

FastAPI with Gunicorn + Uvicorn workers (production):

gunicorn main:app --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:${PORT:-8000} --workers 2

Flask with Gunicorn:

gunicorn app:app --bind 0.0.0.0:${PORT:-8000}

Replace main:app with your module name and the FastAPI/Flask instance variable (e.g. src.main:app for a src/ layout).

Use a Procfile

The cleanest way to define the start command for Nixpacks is a Procfile at the repo root:

web: uvicorn main:app --host 0.0.0.0 --port ${PORT:-8000}

Nixpacks reads this automatically and you don't need to set anything in Vessl.

Step 2 — Create the app

  1. In your project, click New Application.
  2. Select your Python repository and branch.
  3. Vessl detects Python — port defaults to 8000.
  4. Set the Start command if Nixpacks won't pick it up automatically.
  5. Click Deploy.

Step 3 — Python version

Nixpacks picks the Python version from (in order):

  1. .python-version file
  2. python_requires in pyproject.toml
  3. python field in Pipfile
  4. Latest stable if none of the above

To pin a version, create .python-version:

3.12

Or set NIXPACKS_PYTHON_VERSION in the Variables tab (e.g. 3.12.3).

Step 4 — Add a database

From the Add-ons tab, add PostgreSQL, MySQL, or Redis. Vessl injects:

PostgreSQL:

DATABASE_URL=postgresql://<user>:<pass>@<host>:5432/<db>
DB_HOST=<internal hostname>
DB_PORT=5432
DB_DATABASE=<db name>
DB_USERNAME=<username>
DB_PASSWORD=<password>

FastAPI with SQLAlchemy:

import os
from sqlalchemy import create_engine

engine = create_engine(os.environ["DATABASE_URL"])

FastAPI with asyncpg:

import os
import asyncpg

conn = await asyncpg.connect(os.environ["DATABASE_URL"])

Redis:

import os
import redis

r = redis.from_url(os.environ["REDIS_URL"])

Step 5 — Database migrations

If you use Alembic for migrations, set the Post-deploy command to:

alembic upgrade head

For SQLModel or bare SQLAlchemy with create_all, you can run it from a startup script — but a post-deploy command is cleaner and only runs after a successful deploy.

Step 6 — Background workers

For Celery workers, add a daemon from the Daemons tab:

Worker typeCommand
Celery workercelery -A main.celery worker --loglevel=info
Celery beatcelery -A main.celery beat --loglevel=info
ARQ / RQ workerarq main.WorkerSettings

Each daemon runs in its own container and shares the same environment variables (including REDIS_URL for the broker).

Step 7 — Monorepo / subdirectory

If your Python app lives in a subdirectory (e.g. backend/), set the Root Directory field in the wizard. All commands — install, build, start, post-deploy — run relative to that path.

Common issues

ModuleNotFoundError at startup The package is missing from requirements.txt (or pyproject.toml dependencies). Add it, commit, and redeploy.

Uvicorn not found Add uvicorn (or uvicorn[standard]) to your requirements.txt:

fastapi>=0.110.0
uvicorn[standard]>=0.29.0

App starts but returns 422 on the health check FastAPI returns 422 for requests that fail validation. If your / route requires query parameters, set the Health check path to a dedicated /health endpoint:

@app.get("/health")
def health():
return {"status": "ok"}

DATABASE_URL uses postgres:// but SQLAlchemy 1.4+ requires postgresql:// Vessl injects postgresql:// — this should work. But if you're constructing the URL yourself or using an external DB, SQLAlchemy 1.4+ dropped support for the postgres:// scheme. Rename it to postgresql://.

uv / pdm dependency manager Nixpacks supports uv (detects uv.lock) and pdm (detects pdm.lock). Make sure the lockfile is committed to the repo.