Orva
v2026.05.13 · latest self-hosted · MIT

Functions on
your own metal.

Node, Python, and TypeScript. Five kernel-enforced boundaries per call. One binary, one port, zero external dependencies.

~ /
docker run -d --name orva -p 8443:8443 ghcr.io/harsh-2002/orva:latest

Open https://localhost:8443 · onboarding takes ~30 seconds.

01 / Capabilities 14 items

Fourteen things.
One container.

Everything an operator needs to run untrusted code — observed, reversible, no external dependencies, no add-on services.

Multi-runtime
Node.js 22/24, Python 3.13/3.14, TypeScript — all in one binary.
nsjail isolation
Five overlapping Linux kernel boundaries per call — user NS, mount NS, cgroup v2, seccomp, network NS.
Warm pools
Idle workers stay resident between invocations. Pool size configurable per function; back-to-back calls skip spawn entirely.
KV store
Per-function SQLite-backed KV with optional TTL. kv.put / kv.get / kv.list — browsable from the dashboard.
Background jobs
jobs.enqueue(name, payload). Persisted queue with configurable retries and exponential backoff.
Cron schedules
Fire any function on a cron expression. Dashboard shows last run, next run, current status.
Function-to-function
invoke("name", payload) calls another function via the warm pool as a child span in the same trace.
Distributed tracing
HTTP, F2F calls, and background jobs share one trace_id. Waterfall view in dashboard; zero code changes.
Custom routes
Map a path like /webhooks/stripe to a function so callers use a clean URL instead of a UUID.
Secrets
Encrypted per-function secrets injected as env vars at sandbox spawn time. Never logged, never plaintext on disk.
Inbound webhooks
Signed external trigger endpoints — GitHub, Stripe, Slack, and generic HMAC — fanning into a function.
Rollback
Every deploy is content-hashed and archived. Roll back to any prior version in one click.
MCP server
70 tools at /mcp. Claude Code, Cursor, and any OAuth MCP client can deploy and manage functions from chat.
16 templates
Stripe webhooks, GitHub events, JWT auth, OAuth, CSV→JSON, URL shortener — pickable in the editor.
02 / Isolation 5 layers

Five overlapping
kernel boundaries.

Each layer is enforced by the kernel, not by Orva. Orva configures the boundaries; the kernel holds them.

  1. 01

    User namespace

    UID 0 inside the sandbox maps to UID 65534 (nobody) on the host. All 64 Linux capability bits cleared — verified via /proc/self/status. The function cannot escalate to host root.

  2. 02

    Mount namespace + chroot

    /code is a read-only bind mount of the function's versioned directory. /tmp is a private tmpfs wiped after each worker exits. No host filesystem visible.

  3. 03

    cgroup v2

    Hard limits per function: memory.max, cpu.max, pids.max. The host refuses new spawns past 80% total memory reservation.

  4. 04

    Seccomp (Kafel policy)

    ~150 syscalls blocked: mount, unshare, bpf, kexec_load, init_module, ptrace, and more. Blocked attempts log and kill the process — never silently fail.

  5. 05

    Network namespace

    Default network_mode: none — loopback only, no inbound possible. Functions opt in to network_mode: egress for outbound HTTPS via an nftables allowlist.

Orva (nsjail) Firecracker / VMs Plain Docker
Kernel Shared with host Separate per VM Shared with host
Isolation primitive Linux namespaces + seccomp + cgroup v2 Hardware virtualisation (KVM) Linux namespaces + cgroup
Syscall surface ~150 syscalls blocked via Kafel policy Near-zero (hardware boundary) Unfiltered by default
Capability drop All 64 Linux caps cleared N/A (separate kernel) Partial (Docker defaults)
Cold start ~50–200 ms ~125 ms (Firecracker MicroVM) N/A
Memory / worker ~30 MB ~5 MB per MicroVM Varies
Suited for Homelabs, internal tools, trusted code Multi-tenant cloud, untrusted code General app containers

Full threat model and self-verification recipe:  docs/SECURITY.md . Verified end-to-end under Kata Containers 3.30 (QEMU + Cloud Hypervisor); the nsjail boundary holds unchanged with a real Linux kernel underneath.

03 / Dashboard 9 views

Everything the operator needs.
Nothing they don't.

Click any screenshot for full size.

System overview
System overview Live metrics, warm pools, response-time percentiles
Functions
Functions Every deployed handler, runtime, resources, last deploy date
Editor
Editor Write and deploy code directly in the browser
Traces
Traces Automatic causal waterfall across HTTP, F2F, and background jobs
Activity
Activity Live feed of every API call, CLI command, and webhook delivery
Invocation logs
Invocation logs Every execution with request, response, duration, trace link
Docs
Docs Full reference always at /web/docs — no tab-switching
Firewall & DNS
Firewall & DNS Per-function egress rules, custom resolvers, blocklist
Settings
Settings Storage, account, and OAuth-connected apps
04 / Install 3 paths

Three ways in.
Pick the one you trust.

Docker

The fast path. One command, a persistent volume, exposes :8443.

shell
docker run -d \
  --name orva \
  -p 8443:8443 \
  -v orva-data:/var/lib/orva \
  ghcr.io/harsh-2002/orva:latest

Docker Compose

Pin a tag, declare the volume, keep your compose.yml in version control.

compose.yml
services:
  orva:
    image: ghcr.io/harsh-2002/orva:latest
    restart: unless-stopped
    ports:
      - "8443:8443"
    volumes:
      - orva-data:/var/lib/orva

volumes:
  orva-data:

Standalone CLI

No daemon, no Docker. Single binary, systemd unit, native nsjail.

shell
curl -fsSL https://orva.dev/install.sh | sh
sudo systemctl enable --now orva
05 / Runtimes 3 languages

Write a function.
Get a URL.

Every handler is a default export. kv, invoke, and jobs are pre-installed — no npm install, no pip install.

Node.js 22 · 24

Default export an async handler. ctx.kv backed by per-function SQLite; no install needed.

handler.js
export default async function (req, ctx) {
  const seen = (await ctx.kv.get('count')) ?? 0;
  await ctx.kv.put('count', seen + 1);
  return { ok: true, seen: seen + 1 };
}

Python 3.13 · 3.14

Plain async def handler(req, ctx). Async SDK on ctx — no FastAPI, no boilerplate.

handler.py
async def handler(req, ctx):
    seen = await ctx.kv.get('count') or 0
    await ctx.kv.put('count', seen + 1)
    return {'ok': True, 'seen': seen + 1}

TypeScript native

Types ship with the runtime — import type { OrvaHandler } from 'orva' and you're set.

handler.ts
import type { OrvaHandler } from 'orva';

const handler: OrvaHandler = async (req, ctx) => {
  await ctx.jobs.enqueue('thumbnail', { url: req.body.url });
  return { queued: true };
};
export default handler;

Full SDK surface on ctx: kv, invoke, jobs, logger, trace, secrets. Reference docs ship in-product at /web/docs.

— Last word

One binary.
Boring on purpose.

No control plane. No telemetry. No vendor account. Your hardware, your code, your kernel boundaries.