Back to Blog
Tutorial8 min read2026-07-18

Deploy a Robyn API on PandaStack (Python Syntax, Rust Runtime)

Robyn is a Python web framework with a multi-threaded Rust runtime — Flask-like ergonomics, serious throughput. Here's how to containerize and deploy a Robyn API to PandaStack with PostgreSQL.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

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.29

Step 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

  1. 1Push to GitHub.
  2. 2New App → Container App, connect the repo, container port 8080.
  3. 3Attach the PostgreSQL database.
  4. 4Deploy.

CLI:

npm install -g @pandastack/cli
panda login
panda deploy

Live 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 request

Set 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 4

Performance 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/health

You 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.

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also