Skip to main content

System overview

DBHost is a two-tier system: a control plane on Vercel and a data plane on a VPS.
┌─────────────────────────────────────────────────┐
│  Control Plane (Vercel)                         │
│                                                 │
│  Next.js 16 App Router                          │
│  ├─ Dashboard UI (Server Components + CSS)      │
│  ├─ Admin panel (role-gated)                    │
│  ├─ REST API (/api/v1/databases)                │
│  ├─ Clerk webhook handler                       │
│  ├─ Server Actions (create/delete/reset)        │
│  └─ Drizzle ORM → PgBouncer → PostgreSQL        │
│                                                 │
│  Auth: Clerk (JWT + OAuth)                      │
│  Billing: Stripe (subscriptions)                │
│  Styling: CSS Modules (dark theme)              │
│  Font: Geist Sans + Geist Mono                  │
└───────────────────┬─────────────────────────────┘

                    │ HTTPS + Bearer token
                    │ (VPS_AGENT_URL + VPS_AGENT_API_KEY)

┌─────────────────────────────────────────────────┐
│  Data Plane (AWS Lightsail VPS)                 │
│                                                 │
│  Python FastAPI Agent                           │
│  ├─ PostgresService  (DDL: CREATE/DROP/ALTER)   │
│  ├─ PgBouncerService (INI config + SIGHUP)      │
│  └─ BackupService    (pg_dump + gzip)           │
│                                                 │
│  PostgreSQL 16+ (port 5432, localhost only)      │
│  PgBouncer (port 6432, public)                  │
│  Caddy (port 443, reverse proxy + auto-TLS)     │
│  systemd (process management)                   │
└─────────────────────────────────────────────────┘

Design decisions

Why split control plane and data plane?

  • Security isolation — Vercel never has superuser database credentials. The agent holds the Postgres superuser password; the control plane only has a bearer token.
  • Independent scaling — Frontend can scale on Vercel’s edge network. VPS handles database operations.
  • Operational simplicity — Vercel handles TLS, CDN, deploys. VPS only runs database workloads.

Why PgBouncer?

PostgreSQL creates a new process per connection. With many tenants, this wastes memory. PgBouncer multiplexes many client connections onto a small number of backend connections. The agent manages PgBouncer by directly editing its INI config file and sending SIGHUP to reload. This avoids needing PgBouncer admin credentials or a management socket.

Why Drizzle over Prisma?

  • Zero runtime overhead (generates raw SQL)
  • TypeScript-native (no code generation step)
  • Migration system is SQL-based (reviewable, reversible)
  • Smaller bundle size

Why FastAPI?

  • Async-first (all database operations are I/O-bound)
  • Minimal — no heavy framework for a ~10 endpoint agent
  • Pydantic models for request/response validation
  • psycopg 3 integrates cleanly with async/await

Why Clerk?

  • Enterprise-grade auth without building it
  • OAuth, SAML, MFA out of the box
  • Backend API for admin operations (ban, delete, change email)
  • Webhook system for user sync

Data flow: creating a database

1. User clicks "Create Database" in dashboard
2. Server Action validates input
3. Server Action calls agent.createDatabase(name)
4. Agent creates PostgreSQL user + database
5. Agent adds PgBouncer entry + reloads
6. Server Action inserts record into databases table
7. Server Action inserts audit log entry
8. Dashboard revalidates and shows new database
If step 6 fails (DB insert), the Server Action rolls back by calling agent.deleteDatabase(name).

Data flow: connections

Client app → PgBouncer (6432) → PostgreSQL (5432)
Clients never connect to PostgreSQL directly. PgBouncer handles connection pooling, authentication (via its userlist), and routing to the correct database.