Skip to content
View as .md

Self-hosting

Running your own arbe means accounts at five 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
Electric CloudShape sync (Postgres → browser) and durable streams (transcripts) — two productsSame throw
CloudflareWorkers host for apps/www and apps/docsNeeds a different SvelteKit adapter
GitHubAn OAuth App — the only human sign-in pathNo human login; API keys still work
OpenRouterBot replies, the ambient gate, pi’s default sandbox modelBots never answer

Optional, one feature each:

  • Daytona (DAYTONA_API_KEY) — sandboxes. Agents talk but can’t run code. Sprites is the legacy alternative, selected per environment.
  • Fly.io — hosts apps/workflow-conductor. Workflows never advance without it; chat is unaffected.
  • ragthis (RAGTHIS_API_URL + RAGTHIS_API_KEY) — the whole files feature. Nothing to sign up for: it’s arbe’s own file index and you deploy an instance. Miss either variable and uploads are rejected before Storage, listing falls back to Storage-only, and the file tools are never offered.
  • PostHog — analytics. Keys are public, committed in apps/www/.env.
  • Direct provider keys (ANTHROPIC_API_KEY, GEMINI_API_KEY, …) — only for explicit anthropic/<id>-style model refs; everything else routes through OpenRouter. 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. GitHub OAuth App, callback https://<your-ref>.supabase.co/auth/v1/callback. Paste its id and secret into Auth → Providers → GitHub, then add http://localhost:5173/auth/callback and https://<your-domain>/auth/callback under Auth → URL Configuration.

  3. Electric Cloud. A source over your Supabase Postgres yields ELECTRIC_SOURCE_ID and ELECTRIC_SOURCE_SECRET. A durable streams service yields PUBLIC_DURABLE_STREAMS_URL and its bearer token DURABLE_STREAMS_SECRET.

  4. Env files. Public values in apps/www/.env (committed, baked in at build), secrets in apps/www/.env.local. Nine variables are checked at module load and named in the throw if empty — hooks.server.ts owns the list. 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 devhttps://localhost:8443. See system/development.

  6. Deploy. Your own account_id and routes in apps/www/wrangler.jsonc, then bunx wrangler secret put <NAME> per secret. 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 stream-proxy
    bunx supabase secrets set ARBE_WORKER_URL=https://<your-domain>/api/stream

    Daytona’s egress allowlist reaches *.supabase.co but not Electric, so sandboxes post thread entries through this shim. It forwards method, path, query, body, and the caller’s bearer token verbatim to your worker, which owns JWT verification and scope enforcement; it holds no stream secret, which is why supabase/config.toml pins verify_jwt = false. That file also hardcodes our project_id — change it. See system/sandbox-daytona.

  2. Files. Deploy ragthis somewhere reachable over HTTP, set its API_KEY, point RAGTHIS_API_URL and RAGTHIS_API_KEY at it. Ingest runs a vision model over images and PDF pages, so budget for that provider too. See system/files.

  3. Workflows. The conductor on Fly with DATABASE_URL (your Supabase pooler URL), CONDUCTOR_SECRET, and APP_URL. That same secret goes in as a worker secret — it authenticates x-conductor-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 OAuth 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. Its ~30s cancellation is why dispatch self-fetches /api/dispatch/run; on a host without that cap the indirection is harmless. Moving to adapter-node is plausible work, not a rewrite.

Electric is two independent services sharing a vendor. The sync engine is open source and self-hostable. For durable streams, packages/streams/gateway.ts is a single-token auth proxy over the embedded server.ts, and it’s what the package’s tests run against. Neither path runs in production here.

The file index is swappable. packages/core/files.ts is the only module that talks to ragthis, and no route, tool name, CLI command, or UI string says “rag” or “store”. A replacement needs to return the same citation shape — chunk text, file id and filename, page/line coordinates, score.

Fly hosts the conductor, a Bun daemon polling Postgres. Any always-on host works. Daytona has no Fly relationship; the legacy sprite runtime is the Fly one.

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