Robyn (https://robyn.tech) is one of the more interesting Python frameworks of the last few years: you write ordinary Python route handlers, but the HTTP server underneath is written in Rust and runs multi-threaded. The result is Flask-level ergonomics with throughput that embarrasses a lot of pure-Python servers, without you touching a line of Rust. It also has first-class async, WebSockets, and a built-in dev server with hot reload.
This guide deploys a Robyn API to PandaStack as a container with a managed PostgreSQL database.
Why you might reach for Robyn
- Speed without Rust knowledge — the Rust core handles the socket/threading layer; you write Python.
- Batteries — routing, middleware, WebSockets, and a hot-reloading dev server out of the box.
- Small and readable — the API feels like Flask, so onboarding is quick.
Be honest about tradeoffs: Robyn's ecosystem is younger than Flask's or FastAPI's, so you'll write more glue for things like ORMs and auth. If you need the biggest ecosystem, FastAPI or Django is safer; if you want raw throughput with minimal ceremony, Robyn is a great pick.
Step 1: A minimal Robyn app
app.py:
from robyn import Robyn
import os
import asyncpg
app = Robyn(__file__)
pool = None
@app.startup_handler
async def startup():
global pool
pool = await asyncpg.create_pool(os.environ["DATABASE_URL"])
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/users/:id")
async def get_user(request):
user_id = int(request.path_params["id"])
async with pool.acquire() as conn:
row = await conn.fetchrow("SELECT id, name FROM users WHERE id = $1", user_id)
if row is None:
return {"error": "not found"}, 404
return {"id": row["id"], "name": row["name"]}
if __name__ == "__main__":
app.start(host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))Two production details baked in: bind to 0.0.0.0 (not 127.0.0.1, or the container won't accept external traffic), and read the port from PORT.
Step 2: requirements.txt
robyn>=0.60
asyncpg>=0.29Step 3: Dockerfile
Robyn ships prebuilt wheels with the Rust binary included, so no Rust toolchain is needed in the image:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
# Robyn's own runtime; --processes/--workers tune concurrency
CMD ["python", "app.py", "--processes", "2", "--workers", "4"]--processes forks OS processes; --workers sets threads per process. Start with 2 x 4 and tune against your CPU allocation. Over-provisioning workers on a small tier just adds context-switching overhead.
Step 4: Create managed PostgreSQL
Dashboard (https://dashboard.pandastack.io) → Databases → New Database → PostgreSQL. Attaching it to the app injects DATABASE_URL automatically.
Step 5: Deploy
- 1Push to GitHub.
- 2New App → Container App, connect the repo, container port 8080.
- 3Attach the PostgreSQL database.
- 4Deploy.
CLI:
npm install -g @pandastack/cli
panda login
panda deployLive at https://your-robyn.pandastack.io. Test:
curl https://your-robyn.pandastack.io/health
# {"status": "ok"}Step 6: Middleware and auth
Robyn supports before/after request middleware — handy for auth and logging:
@app.before_request()
async def check_auth(request):
if request.url.path.startswith("/admin"):
token = request.headers.get("authorization")
if token != f"Bearer {os.environ['ADMIN_TOKEN']}":
return {"error": "unauthorized"}, 401
return requestSet ADMIN_TOKEN as a PandaStack environment variable, not in code.
Step 7: WebSockets
Robyn has native WebSocket support, so real-time features don't need a second framework:
from robyn import WebSocket
ws = WebSocket(app, "/ws")
@ws.on("message")
async def on_message(ws, msg):
return f"echo: {msg}"Since PandaStack container apps support WebSocket connections, this works in production without extra configuration.
Step 8: Migrations
Robyn doesn't bundle an ORM or migration tool, so bring your own. A pragmatic choice is raw SQL migrations run on deploy via an entrypoint, or use a lightweight tool like yoyo-migrations:
#!/bin/sh
yoyo apply --database "$DATABASE_URL" ./migrations --batch
exec python app.py --processes 2 --workers 4Performance sanity check
The reason to pick Robyn is throughput, so measure it. From your machine:
# Requires the 'hey' load tester
hey -n 5000 -c 50 https://your-robyn.pandastack.io/healthYou should see the Rust runtime hold up under concurrency far better than a single-threaded WSGI server would. If it doesn't, check that you actually set --processes/--workers above 1.
Wrap-up
Robyn is Python you already know sitting on a Rust engine you don't have to learn. On PandaStack it's a plain container app plus managed Postgres, deployed on Git push, with native WebSockets included. Just go in knowing the ecosystem is younger than Flask's. Docs: https://docs.pandastack.io. Start free at https://dashboard.pandastack.io.