Skip to content

How to deploy a uv project to Vercel

uv

Vercel installs Python dependencies with uv by default, with no configuration, and it reads a uv.lock natively. Deploying a uv project is mostly committing the lockfile and giving Vercel an entrypoint it recognizes. This guide covers the entrypoint, the lockfile-priority trap that breaks builds, pinning the Python version, and keeping the bundle under Vercel’s 500 MB limit.

Give Vercel an entrypoint it recognizes

Vercel loads one top-level variable from a Python file as the function handler. The file must define one of three names:

  • app for most ASGI or WSGI frameworks, including FastAPI and Flask
  • application for Django and other WSGI applications
  • handler for serverless functions that subclass BaseHTTPRequestHandler

Vercel auto-detects an entrypoint named app.py, index.py, server.py, main.py, wsgi.py, or asgi.py at the project root or inside src/, app/, or api/. A FastAPI app at the root needs no configuration:

app.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def home():
    return {"message": "Hello from uv on Vercel"}

For a plain serverless function with no framework, drop a file in /api. Each .py file there that defines a handler becomes its own Vercel Function:

api/index.py
from http.server import BaseHTTPRequestHandler


class handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.end_headers()
        self.wfile.write(b"Hello from uv on Vercel")

Declare the dependency in pyproject.toml so it lands in the lockfile:

uv add fastapi

Deploy straight from the lockfile

Lock the project and commit both manifests. Vercel installs the exact versions in uv.lock with uv during the build:

uv lock
git add pyproject.toml uv.lock app.py
git commit -m "Add Vercel entrypoint"

Deploy with the Vercel CLI. A bare vercel creates a preview deployment; --prod targets production:

npm i -g vercel
vercel --prod

Vercel detects the framework from the dependency manifest, installs the locked dependencies with uv, and serves the entrypoint. The common case needs no requirements.txt, build hook, or install command override.

Avoid the pyproject and requirements.txt conflict

A repository that carries both a pyproject.toml and a requirements.txt with no lockfile is the one shape that breaks. Vercel’s uv-based installer prefers pyproject.toml, and if that file does not list the dependencies the old requirements.txt did, the build can install nothing and fail at runtime (vercel/vercel issue #14041).

Commit a uv.lock so pyproject.toml is the authoritative, complete source:

uv lock
git add uv.lock

If you would rather keep a single requirements.txt, delete the pyproject.toml from what Vercel builds, or generate the requirements file from uv and remove the project metadata Vercel would otherwise pick up. Pick one manifest and make it complete.

Pin the Python version

Vercel defaults to Python 3.12 and also offers 3.13 and 3.14. It reads the version from requires-python in pyproject.toml or from a .python-version file. The requires-python floor is a lower bound, so >=3.12 can resolve to a newer default as Vercel moves it forward.

Pin an exact runtime with uv, which writes the .python-version file Vercel reads:

uv python pin 3.12
git add .python-version

The .python-version file keeps the deployed runtime fixed regardless of where Vercel sets its default. To change versions later, see How to change the Python version of a uv project.

Keep the function bundle under 500 MB

Python functions have a 500 MB uncompressed bundle limit, and Vercel does no tree-shaking: everything reachable at build time ships. Two levers keep the bundle small.

Strip dev-only tools by exporting a runtime-only requirements file. Vercel’s lockfile install carries whatever your project resolves; exporting with --no-dev guarantees pytest, Ruff, and their transitive dependencies stay out of the deploy:

uv export --frozen --no-dev --no-emit-project -o requirements.txt

--no-emit-project excludes the project itself, which Vercel installs from source anyway, and --no-dev drops the dev dependency groups. On a FastAPI project with pytest and ruff in the dev group, this cut the exported file from 155 to 113 lines. Commit the generated requirements.txt as your single manifest and remove pyproject.toml from the build to avoid the conflict above.

Drop tests, fixtures, and static assets with excludeFiles in vercel.json, a glob relative to the project root:

vercel.json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "functions": {
    "api/**/*.py": {
      "excludeFiles": "{tests/**,**/test_*.py,fixtures/**,static/**}"
    }
  }
}

Vercel builds and installs in its own Linux environment, so wheels for packages with C or Rust extensions resolve for the right platform automatically. That avoids the cross-build flags a local-install target like AWS Lambda requires.

Learn more

Last updated on