Skip to content

Deployment

A velociraptor is only as effective as the ground it hunts on. VelociPy runs on two protocols - RSGI for raw speed and ASGI for ecosystem compatibility - so you can deploy on the terrain that fits your environment. This guide shows how to run production servers, handle startup and shutdown with lifespan events, sit safely behind a reverse proxy, and pack everything into a container.

Pick your terrain

VelociPy exposes two entry points from the same app object:

  • app(...) - the ASGI entry point, used by Uvicorn, Hypercorn, and Gunicorn workers.
  • app.__rsgi__(scope, proto) - the RSGI entry point, used by Granian.

The protocol adapter handles the surface; your handlers stay on solid ground. You can switch servers without rewriting routes.

Terrain Server Best for
RSGI Granian Maximum throughput, lowest overhead. The open plain.
ASGI Uvicorn, Hypercorn Broad compatibility, existing middleware, mature ecosystem. The dense forest.

Use RSGI when you can

RSGI skips the ASGI event-loop overhead and gives VelociPy the shortest path from the server to your handler. Use it unless you specifically need ASGI-only middleware or tooling.

Running with Granian

Granian is the recommended server for VelociPy because it supports both ASGI and RSGI from the same process model.

granian main:app --interface rsgi --host 0.0.0.0 --port 8000 --workers 4

ASGI

granian main:app --interface asgi --host 0.0.0.0 --port 8000 --workers 4

Install Granian through the optional extra:

pip install "velocipy[granian]"

See examples/basic.py for a programmatic Granian server block.

Running with Uvicorn

Use Uvicorn when you need the ASGI ecosystem or when your hosting platform expects an ASGI callable.

uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

With reload during development:

uvicorn main:app --host 0.0.0.0 --port 8000 --reload

Install Uvicorn through the optional extra:

pip install "velocipy[uvicorn]"

See examples/uvicorn_server.py for a runnable Uvicorn example.

Lifespan events

Use lifespan to open connection pools, load caches, or close resources when the server shuts down. VelociPy supports the ASGI lifespan protocol and RSGI lifespan hooks.

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager

from velocipy import VelociPy

@asynccontextmanager
async def lifespan(app: VelociPy) -> AsyncIterator[None]:
    print("Startup: sharpening claws...")
    yield
    print("Shutdown: cleaning the den...")

app = VelociPy(lifespan=lifespan)

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

The block before yield runs once on startup; the block after runs once on shutdown.

Behind a reverse proxy

When running behind Nginx, Traefik, or another proxy, you typically want three pieces of armor:

  1. Trusted hosts - reject requests with forged Host headers.
  2. Forwarded headers - let VelociPy see the original scheme and client IP.
  3. HTTPS redirect - force TLS at the edge.
from velocipy import VelociPy
from velocipy.middleware import TrustedHostMiddleware, HTTPSRedirectMiddleware

app = VelociPy()

app.add_middleware(
    TrustedHostMiddleware,
    allowed_hosts=["example.com", "*.example.com"],
)
# app.add_middleware(HTTPSRedirectMiddleware)

Configure your proxy headers correctly

TrustedHostMiddleware checks the Host header. If your proxy sets X-Forwarded-Host, make sure it is trustworthy and that the upstream server is not reachable directly. VelociPy does not automatically trust forwarded headers; enforce that trust at the proxy or middleware layer.

See examples/middleware.py for CORS, GZip, security headers, request IDs, and timing middleware.

Docker example

A minimal Dockerfile that installs the package, copies your app, and runs Granian on RSGI:

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["granian", "main:app", "--interface", "rsgi", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

Example requirements.txt:

velocipy[msgspec,granian]
redis>=4.2.0

Build and run:

docker build -t velocipy-app .
docker run -p 8000:8000 velocipy-app

Environment-based configuration

Keep deployment settings out of code. VelociPy's typed Config registry is a good place to centralize them:

import os
from velocipy import VelociPy
from velocipy.config import Config

@Config.register
class AppConfig(Config):
    host: str = "0.0.0.0"
    port: int = 8000
    workers: int = 4
    interface: str = "rsgi"

config = Config.get(AppConfig)

You can subclass Config for different environments and load values from environment variables in your entry-point script.

See examples/config.py for a runnable Config demo.

Health checks

Add a lightweight health endpoint for load balancers and orchestrators:

from velocipy import VelociPy

app = VelociPy()

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

@app.get("/ready")
async def ready():
    # Add real dependency checks here (database, cache, etc.)
    return {"ready": True}

Keep these endpoints cheap: they will be called often and should not depend on slow external services.

Deployment checklist

Concern Recommendation
Protocol Use RSGI/Granian for speed; use ASGI/Uvicorn for compatibility.
Workers Match CPU cores for CPU-bound work; increase for I/O-bound workloads with care.
Lifespan Open pools in startup, close them in shutdown.
Host header Add TrustedHostMiddleware.
Body size Set max_content_length to avoid large uploads.
HTTPS Terminate TLS at the proxy or use HTTPSRedirectMiddleware.
Static files Serve them from the proxy or CDN, not from Python workers.
Health checks Expose cheap /health and /ready endpoints.
Logging Use structured logging; VelociPy logs background-task failures to the velocipy logger.

Summary

Topic Key takeaway
RSGI granian main:app --interface rsgi for the fastest path.
ASGI uvicorn main:app for the broadest compatibility.
Lifespan Use an async context manager for startup/shutdown work.
Reverse proxy Use TrustedHostMiddleware and HTTPS redirection at the edge.
Docker Install extras, copy code, run Granian.
Config Centralize settings with Config subclasses.

Next steps