A multi-tenant, self-hostable Backend-as-a-Service on real Postgres.
The developer experience of a managed platform, the lightweight footprint of an embedded database, and true multi-tenancy — on infrastructure you own.
In Old Norse law, a hauldr was a freeholder — someone who held their land outright, by inherited right, answering to no lord. Hauldr is a backend you hold the same way: your data, your Postgres, your box. No landlord.
Hauldr is an open-source backend platform you run yourself. It gives a small team the same building blocks a managed BaaS would — authentication, a real SQL database with row-level security, file storage, realtime, and a typed client SDK — but each tenant project is isolated, lightweight, and provisioned in seconds, all on top of standard, battle-tested open-source components.
It is designed for the shape of work where you run many small applications (one per client, per product, per internal tool) and want each one to have a proper database with real auth and access control — without paying the per-app overhead of a heavyweight stack or the per-seat bill of a hosted vendor.
Three moving parts:
- Control plane — a self-hosted panel (Supabase-like UX) plus a management API for creating and operating projects.
- Shared data plane — a Postgres cluster fronted by a multi-tenant connection pooler, with one database per project.
- Per-project satellites — only the services a given project asks for (auth always; a REST API layer à-la-carte).
Self-host the whole stack on any Docker host with one command:
git clone https://github.com/cold-code-labs/hauldr
cd hauldr
./scripts/selfhost-up.shThe script generates a .env with strong secrets, builds and starts everything
(Postgres, the multi-tenant pooler, the control plane, project-zero auth,
Realtime, Garage object storage, and the panel), waits until it's healthy, and
provisions a demo project so there's something to poke at immediately.
Then open the panel and configure your first access:
http://localhost:3000/setup
A first-run wizard walks you through creating the operator account and your organization — then you're in the panel. (Prefer to stay in the terminal? Skip the panel and drive everything through the management API below.)
Create and inspect a project through the management API:
API=http://localhost:8787
KEY=$(grep '^HAULDR_API_KEY=' .env | cut -d= -f2)
# Create a project (its own database + auth, REST layer optional)
curl -sX POST $API/v1/projects -H "Authorization: Bearer $KEY" \
-H 'content-type: application/json' -d '{"name":"blog","rest":true}'
# Its endpoints, keys, and connection strings
curl -s $API/v1/projects/blog -H "Authorization: Bearer $KEY" | jqThen talk to that project from your app with the SDK:
import { createClient } from "@hauldr/client"
const hauldr = createClient({ url: PROJECT_URL, db: { connectionString } })
await hauldr.auth.signUp({ email, password })
await hauldr.db.asUser(token).insert("todos", { title: "hello" }) // RLS-scopedPrefer to run the pieces by hand, or develop on Hauldr itself? See self-hosting.md and development.md.
Note on the panel. The management API, CLI, and SDK are the complete self-serve surface. The web panel (
./panel) is an early UI; it builds out of the box against an open design-system fallback (panel/vendor, no private access needed) and swaps back to Cold Code Labs' branded packages with a one-line change — see panel/vendor/README.md.
The two ends of the spectrum each have a cost:
- Heavyweight managed stacks are excellent but assume one big tenant. Running dozens of small isolated apps means dozens of full deployments, or giving up isolation.
- Embedded single-file databases are wonderfully light, but you eventually hit real ceilings: concurrent writers, true SQL, row-level security, point-in- time recovery, connection scaling.
Hauldr sits in the middle deliberately: heavy things are shared (one Postgres cluster, one pooler, one object store, multi-tenant), light things are per- project and optional. A project is just a database, an auth service, and a JWT secret — measured in tens of megabytes, not gigabytes.
- Shared is heavy, per-project is light and optional. Multi-tenancy lives in the shared plane; each project only carries what it actually needs.
- One auth model, always. Every project ships with its own GoTrue instance and its own JWT secret. Row-level security is always on — access control is enforced by the database, reading the claims from the auth token.
- Real Postgres underneath. No reimplemented query engine. Extensions (pgvector, etc.) are enabled per-database, on demand.
- Scale honestly. A multi-tenant pooler keeps connections under control; tiering lets a project that outgrows the shared cluster move to a dedicated one without changing application code.
- Assemble upstream, don't reinvent. Hauldr builds the panel, the provisioner, the SDK, and the conventions. It never rewrites the database, the pooler, or the auth server — those are proven projects, used as-is.
CONTROL PLANE DATA PLANE (shared)
┌─────────────────────┐ ┌──────────────────────────────────────┐
│ Hauldr Panel │ │ Connection pooler (multi-tenant) │
│ (SQL / tables / │ ── API ──▶│ routes project → database │
│ auth / RLS / │ │ │ │
│ keys / logs / │◀── meta ──│ Postgres (1+ cluster, 1 DB/project) │
│ backups) │ │ ├ db_acme ├ db_shop ├ … │
│ │ └──────────────────────────────────────┘
│ Hauldr API (mgmt) │ ▲ per-project, à-la-carte
└──────────┬──────────┘ │ ┌ Auth (GoTrue) [always]
│ called by │ └ REST (PostgREST) [optional]
your provisioning automation │ realtime = shared WS · storage = S3
Full detail in docs/architecture.md.
The SDK is the layer that makes a decomposed backend feel like one thing. It hides the pooler, the auth server, and the object store behind a small surface:
npm i @hauldr/clientimport { createClient } from "@hauldr/client"
const hauldr = createClient({ url: PROJECT_URL, db: { connectionString } })
// Auth — full lifecycle (signup, OAuth, magic-link, MFA, reset)
const { access_token } = await hauldr.auth.signInWithPassword({ email, password })
// Data — RLS-aware; the caller's claim is injected per transaction
await hauldr.db.asUser(access_token).insert("posts", { title: "hello" })
const posts = await hauldr.db.asUser(access_token).select("posts")
// Files — S3-style upload + signed URLs
const { url } = await hauldr.files.upload("avatars", file)
// Realtime — WebSocket via a shared, multi-tenant Realtime service
hauldr.live.on("posts", (change) => render(change))| Namespace | Backed by |
|---|---|
hauldr.auth |
GoTrue (lifecycle / OAuth / magic-link / MFA) |
hauldr.db |
Typed queries through the pooler (RLS-aware) |
hauldr.files |
Object storage over standard S3 |
hauldr.live |
Shared Realtime over WebSocket (broadcast · presence · changes) |
See docs/sdk.md.
There's also a tiny CLI for deploying a project's edge functions:
npm i -g @hauldr/cli (or npx @hauldr/cli functions list <project>) — see
@hauldr/cli.
| Doc | What's inside |
|---|---|
| Architecture | The full system: control plane, data plane, satellites |
| Development | The isolated local dev stack (v17, hot-reload panel + control plane) |
| Concepts | Glossary and the model behind projects, tenancy, tiering |
| Auth & data | GoTrue, RLS, server actions vs. REST, claim injection |
| Data plane | Postgres, the pooler, migrations, backups, tiering |
| Storage | S3-compatible object storage, buckets, durability |
| Functions | Per-project edge functions: enabling, deploying, cron |
| SDK | @hauldr/client surface and usage |
| Self-hosting | Bootstrap, "project zero", running the stack |
| Roadmap | Phased delivery plan |
Hauldr is pre-alpha and developed in the open. The foundational architectural decisions are settled. The control plane provisions projects end-to-end — auth always, REST / storage / realtime à-la-carte — over a multi-tenant pooler, and it already runs a real fleet of applications. What's left is operational polish (backups / PITR, tiering, per-project metrics) and a one-click migration path. Expect breaking changes until a tagged release.
Self-hosting on a single Docker host is a one command away
(scripts/selfhost-up.sh); the management API, CLI,
and SDK are the complete self-serve surface. Known gaps a contributor could pick
up:
- Panel design system. The panel now builds against an open fallback
(
panel/vendor); the richer Cold Code Labs component set stays private. A fuller open design system (more components, the real brand-neutral polish) is welcome. - Per-project Functions route through a reverse proxy, so they require
HAULDR_NAMESPACE_PATTERN+ a Traefik-style gateway (not bare localhost). - Scheduled jobs / cron need the
pg_cron/pg_netextensions (thesupabase/postgresimage), not stockpostgres:16.
See the roadmap for what's coming and in what order.
Hauldr is an assembly of excellent open-source projects:
- PostgreSQL — the database
- Supavisor — multi-tenant connection pooler
- GoTrue — authentication
- PostgREST — optional REST API layer
- Garage — S3-compatible object storage
- Drizzle — the typed data layer in the SDK
Contributions are welcome — see CONTRIBUTING.md. Please also read the Code of Conduct and our security policy.