Introducing tinbase — local Supabase without Docker (one process, real Postgres)
* Supabase local dev = a 12-container, **2.3 GB** Docker stack. tinbase serves the same APIs from **one 58 MB process**. No Docker.
* It's **real Postgres 17** — RLS, `auth.uid()`, jsonb, triggers, foreign keys all behave like hosted Supabase.
* The official **supabase-js SDK works unchanged** (168/168 integration tests pass).
* The whole backend — database included — can run **inside a browser tab**.
* Open source, MIT. Try it: `npx tinbase start`
## The problem
You know the drill. `supabase start`, then Docker pulls Postgres, PostgREST, GoTrue, Storage, Realtime, Studio... twelve containers, 2.3 GB on disk, ~1.6 GB of RAM under load. All you wanted was to run your app locally.
I love Supabase. I did not love paying a 2 GB tax to write a todo app on the train.
## What tinbase does instead
One process. Same wire protocols:
| Install footprint | Memory under load
---|---|---
Supabase local (12 containers) | 2,291 MB | 1,626 MB
tinbase (single binary) | 92 MB | 66 MB
tinbase (native) | 36 MB | 100 MB
npx tinbase start
~2 seconds later you're serving requests. There's also a single 58 MB executable that needs no Node, npm, or Docker on the target machine.
## Your code doesn't change
This is the whole point. tinbase implements the PostgREST query grammar, GoTrue auth flows, the Storage API, and the Realtime Phoenix protocol — verified by running the **official SDK** against it:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('http://127.0.0.1:54321', ANON_KEY)
await supabase.auth.signUp({ email, password })
await supabase.from('todos').insert({ title: 'hello' })
const { data } = await supabase
.from('todos')
.select('*, author:users(name)')
.eq('done', false)
supabase.channel('feed')
.on('postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'todos' },
handleNewTodo)
.subscribe()
No fork of the SDK. No wrapper. No different query language. RLS runs with your JWT claims applied, so `auth.uid()` policies work as-is — and Realtime does per-subscriber RLS filtering, so users only get change events for rows they can see.
Auth covers email/password, anonymous, OTP, magic links, password recovery, and OAuth (Google/GitHub + generic) with PKCE. Edge Functions run in-process via `supabase.functions.invoke()`. Webhooks, `cron.schedule()`, and a pgmq subset are in there natively — no `pg_net`/`pg_cron`/`pgmq` extensions to install. And a Supabase-Studio-style dashboard ships at `/_/`.
## The weird part: it runs in a browser tab
Every service in tinbase is a pure `(Request) ⇒ Response` fetch handler on top of a swappable DB engine:
supabase-js (unmodified)
│
▼
one (Request) ⇒ Response handler
├─ /rest/v1 (PostgREST)
├─ /auth/v1 (GoTrue)
├─ /storage/v1 (Storage)
├─ Realtime (WebSocket)
├─ /functions/v1 (Edge Fns)
└─ /_/ (Studio)
│
▼
DbEngine adapter
├─ native → embedded Postgres 17
├─ wasm → PGlite (Postgres in WASM)
└─ pg-mem → pure JS, in-memory
In Node, that handler is an HTTP + WebSocket server. In the browser, you hand it to supabase-js as a custom `fetch` — and the entire backend, Postgres included, runs in-process in the tab. No server. No cloud. You can play with it at tinbase.dev/browser.
## It's not a dead end
tinbase reads `supabase/migrations/*.sql` and `seed.sql` exactly like the Supabase CLI, tracked in the same table. Outgrow it? Push the same files to hosted Supabase and keep moving. You can also point it at a Postgres you already run with `--database-url`.
## Why we built it
tinbase came out of building RapidNative and lifo, with a stubborn goal: run an entire dev stack — database, auth, storage, realtime — in the browser and on phones, with no VMs and no cloud behind it. Cutting the backend down to a single process was step one; making that same process run inside a tab was step two. Somewhere along the way it turned into a genuinely useful Docker-free replacement for local Supabase dev, so we open-sourced the whole thing (MIT).
## Honest caveats
It's **alpha (v0.10.0)**. Built for local dev, prototypes, and embedded/browser use — not production. 168/168 integration tests pass across both engines, but if you hit an API edge we haven't covered, open an issue.
## Try it
npx tinbase start
GitHub: github.com/tinbase/tinbase
What would you build if your whole backend fit in a browser tab? Drop a comment — especially if you try the browser engine, I want to see what you make with it. 🚀