Skip to content
View as .md

Self-hosting

Running your own arbe means accounts at four services, plus one more per optional feature. Every value below is a placeholder you generate — our own account ids and project refs live in system/deployment.

Accounts you need

ServiceWhat it gives arbeWithout it
SupabasePostgres, GoTrue auth, PostgREST + RLS, Storage, Vault, one edge functionWorker throws at startup
Fly.ioHosts apps/durable-streams (transcripts) and apps/backstage (workflows)Worker throws; workflows stall
CloudflareWorkers host for apps/www and apps/docsNeeds a different SvelteKit adapter
OpenRouterBot replies, the ambient gate, pi’s default sandbox modelBots never answer

Optional, one feature each:

  • GitHub — an OAuth App for “Sign in with GitHub”. Email link and password sign-in work without it.
  • Daytona (DAYTONA_API_KEY) — sandboxes. Agents talk but can’t run code.
  • PostHog — analytics. Keys are public, committed as defaults in the root .env.schema.
  • Direct provider keys (ANTHROPIC_API_KEY, GEMINI_API_KEY, …) — required for explicit provider model refs. GEMINI_API_KEY also enables file readings (images, scanned PDFs). See system/llm-keys.
  • Cloudflare R2 + GitHub Actions — publishing CLI binaries. Any static host works. See releases.

Wiring it up

  1. Supabase project. Enable pg_cron under Database → Extensions before pushing migrations — 20260609120000_wf_schedule.sql calls cron.schedule and fails without it.

    Terminal window
    cd packages/supabase && bunx supabase link --project-ref <your-ref>
    cd ../.. && bun run push-migrations # migrations + regenerated database.types.ts

    Migrations bring the house-files bucket and every RLS policy, so there’s no dashboard clicking. Gotchas around --linked and the two supabase/ directories: system/supabase.

  2. Sign-in. Email link and email + password use Supabase’s built-in email auth; add https://arbe.localhost/auth/callback and https://<your-domain>/auth/callback under Auth → URL Configuration so the links land back in arbe. Supabase’s default mailer is heavily rate-limited, so set custom SMTP before real users arrive. For GitHub sign-in, create an OAuth App with callback https://<your-ref>.supabase.co/auth/v1/callback and paste its id and secret into Auth → Providers → GitHub.

  3. Durable streams. Deploy apps/durable-streams/ to Fly — the upstream durable-streams server with a Fly volume and one bearer token. Its URL becomes PUBLIC_DURABLE_STREAMS_URL and the token DURABLE_STREAMS_SECRET; www and the backstage must point at the same one. Setup steps are in its README.

  4. Env files. Public values are defaults in the root .env.schema (committed, baked in at build); secrets go in a gitignored .env.local next to it. Varlock validates the app schema before startup; www and backstage read its typed ENV object. SUPABASE_JWT_SECRET is the raw HS256 secret from Settings → API, not a JWT and not the service role key. See system/environment-variables.

  5. Run it. bun install && bun run dev → https://arbe.localhost. See system/development.

  6. Deploy. Set your own account_id and routes in apps/www/wrangler.jsonc and supply secrets in Cloudflare Builds. The deploy script uses varlock-wrangler to validate and upload them. See system/deployment.

Then, in any order:

  1. Sandboxes. DAYTONA_API_KEY as worker env or a house secret, plus the one edge function arbe deploys:

    Terminal window
    bunx supabase functions deploy arbe-proxy
    bunx supabase secrets set ARBE_APP_URL=https://<your-domain>

    Daytona’s egress allowlist reaches *.supabase.co but not your worker, so sandboxes call arbe through this shim. It forwards /api/* verbatim — method, path, query, body, and the caller’s own bearer token — to your worker, which owns every auth decision; it holds no secret of its own, which is why supabase/config.toml pins verify_jwt = false. That file also hardcodes our project_id — change it. Point sandboxes at your own deployment with ARBE_API_URL. See system/sandbox-daytona.

  2. Files. Set GEMINI_API_KEY on the backstage for visual readings of images and scanned PDFs; file search is lexical and needs no key. See system/volumes.

  3. Workflows. The backstage on Fly with DATABASE_URL (your Supabase pooler URL), BACKSTAGE_SECRET, and APP_URL. That same secret goes in as a worker secret — it authenticates x-backstage-secret on POST /api/wf/step.

What’s actually locked

Supabase is not swappable for plain Postgres. arbe uses five of its surfaces: GoTrue for sign-in and account deletion, PostgREST as the data path, Storage for house files, Vault for house secrets, and one edge function. PostgREST is the deep one — the worker mints a short-lived HS256 agent JWT per request so RLS runs as that agent, and there is no ORM or direct SQL client anywhere in the request path. Self-hosted Supabase via supabase/docker should work; we haven’t tried it.

Cloudflare is the softest lock. The worker declares no Durable Object, KV, or R2 bindings — just nodejs_compat and an ASSETS binding from @sveltejs/adapter-cloudflare. The only runtime coupling is event.platform?.context?.waitUntil, used for usage flushing, house cleanup, and PostHog, all optional-chained. Bot turns don’t run in the worker at all — www appends the entry and enqueues, and the backstage daemon runs the turn — so the ~30s cancellation bounds only those background chores. Moving to adapter-node is plausible work, not a rewrite.

Durable streams are an open protocol, not a vendor. apps/durable-streams/ runs the upstream server on Fly, and packages/streams/gateway.ts is the same surface embedded for local dev and tests; any host that keeps a disk and long-lived connections works. Structural sync involves no sync service at all — the browser reads www API routes.

Fly also hosts the backstage, a Bun daemon polling Postgres. Any always-on host works for either — but neither may idle-pause. Daytona, the sandbox runtime, has no Fly relationship.

Code: apps/www/src/hooks.server.ts, apps/www/wrangler.jsonc, packages/supabase/migrations/, apps/backstage/fly.toml, apps/durable-streams/fly.toml.
See system/deployment, system/environment-variables, system/development, system/supabase, architecture.