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.
RSGI (recommended)¶
ASGI¶
Install Granian through the optional extra:
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.
With reload during development:
Install Uvicorn through the optional extra:
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:
- Trusted hosts - reject requests with forged
Hostheaders. - Forwarded headers - let VelociPy see the original scheme and client IP.
- 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:
Build and run:
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¶
- Configuration - typed app configuration with
Config. - Middleware - CORS, GZip, security headers, and more.
- Static files - serve assets efficiently.
- Rate limiting - protect production endpoints.