---
title: "Backend modes and databases"
description: "DB_BACKEND_MODE, PostgreSQL vs Firebase vs Supabase, single shared pool SSOT, FCM-only Firebase, Tunnel transports, React 19 cache() SSOT helpers."
locale: "en"
---
# Backend modes and databases

> **Info**
> Use the **Founder** / **Developer** tabs to filter this page for your role. The `DB_BACKEND_MODE` environment variable is **required** — the platform will not start without it.

Ring Platform uses a **single codebase** with a **required** runtime switch: **`DB_BACKEND_MODE`**. The value selects which database adapters are registered, whether Firestore is used for application data, and how push notifications are wired.

| Mode | Primary database | Firebase use | Best for |
|------|-----------------|--------------|----------|
| **`k8s-postgres-fcm`** | PostgreSQL 16 + PostGIS | FCM push only | Production self-hosted, local dev |
| **`firebase-full`** | Firestore **(first-class)** | Everything: DB, FCM, Storage, AI, Hosting | Vercel Edge, clones, rapid prototyping |
| **`supabase-fcm`** | Supabase PostgreSQL | FCM push only | Managed cloud PostgreSQL |

**Naming:** Production on your own Postgres is often described as "self‑hosted PostgreSQL + FCM." The enum value remains `k8s-postgres-fcm` — renaming would break every deployment and clone without a coordinated migration.

**Implementation files:** `lib/database/backend-mode-config.ts`, `lib/database/DatabaseService.ts`, `lib/database/BackendSelector.ts`, `lib/database/shared-pg-pool.ts`, `lib/services/firebase-service-manager.ts` (SSOT helpers for `firebase-full`).

  
  
  
  
  
  

### For founders

## Which mode should I choose?

| Scenario | Recommended mode | Why |
|----------|----------------|-----|
| Self-hosted production (k3s, K8s) | **k8s-postgres-fcm** | Full data sovereignty, PostgreSQL performance, PostGIS spatial queries |
| Rapid prototyping / MVP | **firebase-full** | Zero database provisioning, free FCM push, auto-scale |
| Ring clone (white-label) | **firebase-full** | No PostgreSQL dependency for the clone operator |
| Vercel Edge deployment | **firebase-full** | Vercel Edge Runtime + Firestore = no cold-start DB connection |
| Cloud PostgreSQL (no K8s) | **supabase-fcm** | Managed PostgreSQL with Supabase, FCM push |
| Push notifications needed | Any mode | FCM is optional and available in all three modes |

### Why connection pooling matters

In **k8s-postgres-fcm** and **supabase-fcm**, every feature module shares one adapter pool. You do not get surprise connection spikes when admin settings, wallet oracle, geolocation, or email CRM load concurrently — they all route through `db()` or the sanctioned `getSharedPgPool()` escape hatch for PostGIS.

### Cost comparison

| Aspect | k8s-postgres-fcm | firebase-full | supabase-fcm |
|--------|-----------------|---------------|--------------|
| Database | Infrastructure cost only | Pay-per-read/write | Supabase tier pricing |
| Push (FCM) | Free | Free | Free |
| Storage | Vercel Blob / self-hosted | Firebase Storage (free tier) | Supabase Storage |
| Compute | K8s / Docker | Vercel Edge / Cloud Run | Vercel / Cloud Run |
| AI | LLM client via `lib/ai/llm-client.ts` | Firebase AI Logic + Gemini | LLM client via `lib/ai/llm-client.ts` |

### For developers

## Architecture by mode

```mermaid
flowchart LR
  subgraph k8s_mode["k8s-postgres-fcm"]
    K8sApp["Next.js App"]
    K8sDB["db() / DatabaseService"]
    K8sPG["PostgreSQLAdapter\n(single pg.Pool)"]
    K8sRaw["getSharedPgPool()\n(PostGIS only)"]
    K8sFS[(PostgreSQL 16\n+ PostGIS)]
    K8sFCM["Firebase Admin\n(FCM push only)"]
    K8sApp --> K8sDB
    K8sDB --> K8sPG
    K8sPG --> K8sFS
    K8sApp --> K8sRaw
    K8sRaw --> K8sPG
    K8sApp --> K8sFCM
  end
  subgraph firebase_mode["firebase-full"]
    FbApp["Next.js App"]
    FbAdapter["FirebaseAdapter"]
    FbSSOT["firebase-service-manager.ts\n30+ SSOT helpers"]
    FbFS[(Firestore)]
    FbApp --> FbAdapter
    FbApp --> FbSSOT
    FbSSOT --> FbAdapter
    FbAdapter --> FbFS
  end
  subgraph supabase_mode["supabase-fcm"]
    SaApp["Next.js App"]
    SaPG["PostgreSQLAdapter\n(single pg.Pool)"]
    SaFS[(Supabase PostgreSQL)]
    SaApp --> SaPG
    SaPG --> SaFS
  end
```

### Database routing rules (PostgreSQL modes)

All application CRUD goes through **`db()`** from `@/lib/database`:

- `readDoc`, `createDoc`, `updateDoc`, `deleteDoc`, `queryDocs` — document-shaped collections
- `findById`, `create`, `update`, `delete`, `query`, `transaction` — legacy `DatabaseService` surface

**Raw SQL escape hatch:** `getSharedPgPool()` in `lib/database/shared-pg-pool.ts` — calls `initializeDatabase()` then returns the adapter pool. Use **only** for PostGIS / SQL the doc-model cannot express. `new Pool(` is allowed only under `lib/database/` (enforced by `validate-provider-ssot.sh`).

**2026-07-07 audit:** Three rogue private pools in `platform-settings-service.ts`, `native-token-oracle.ts`, and `geolocation-service.ts` were eliminated. Only `platform_settings` had a real hybrid-table bypass; those modules now use `db().*Doc` or `getSharedPgPool()`.

### Backend selection in code

```typescript
// lib/database/backend-mode-config.ts
export type BackendMode = 'k8s-postgres-fcm' | 'firebase-full' | 'supabase-fcm'

export function shouldUseFirebaseForDatabase(): boolean {
  return detectBackendMode() === 'firebase-full'
}
```

### Firebase in k8s-postgres-fcm and supabase-fcm

- **Application data** routes through `db()` / `PostgreSQLAdapter`
- **`getAdminDb()`** returns a mock Firestore when `shouldUseFirebaseForDatabase()` is false
- **FCM** uses Firebase project credentials for push delivery only
- **`build-mock.server.ts`** provides type-compatible mocks during SSG and Postgres-primary modes

### Firebase in firebase-full mode

- **All application data** goes to Firestore via `FirebaseAdapter`
- **`lib/services/firebase-service-manager.ts`** provides 30+ React 19 `cache()`-native SSOT helpers
- **3 atomic write primitives** wrap read-modify-write in a single Firestore transaction
- **6 real-time listener factories** pair with Tunnel for live UI updates

### React 19 cache() pattern

```typescript
import { cache } from 'react'

export const getCachedUserCreditBalance = cache(async (userId: string) => {
  // Firestore read — cached for the duration of the request
})
```

Eliminates duplicate Firestore reads within a single SSG/SSR cycle.

### Supabase: two different roles

1. **`DB_BACKEND_MODE=supabase-fcm`** — PostgreSQL on Supabase; same Postgres-primary + FCM pattern as k8s mode
2. **Tunnel realtime (`NEXT_PUBLIC_TUNNEL_TRANSPORT=supabase`)** — optional Realtime channel in `lib/tunnel/transports/supabase-transport.ts`. Orthogonal to which SQL backend owns your tables.

### Auth.js and database mode

Auth tables live in **PostgreSQL** (k8s-postgres-fcm, supabase-fcm) or **Firestore** (firebase-full). See `lib/auth/postgres-adapter.ts` / `data/schema.sql` for the SQL path.

## Related documentation
