---
title: "Backup & Recovery"
description: "Verified backup scope and restore procedures for PostgreSQL-primary Ring clones, object storage, and legacy Firebase-full deployments"
locale: "en"
---
# Backup & Recovery

> **Info**
> Filter with **Founder** / **Developer** in the docs sidebar. This page replaces legacy fiction (invented TypeScript managers, dollar KPIs, multi-region failover code that does not exist in the OSS tree). Everything below is traceable to `data/schema.sql`, `env.local.template`, and deployment docs.

Ring clones on **`DB_BACKEND_MODE=k8s-postgres-fcm`** (production default) treat **PostgreSQL as the source of truth** for users, entities, opportunities, store, payments, and most CRM data. Backups must cover the **database**, **uploaded files**, and **secrets/configuration** — three separate artifacts with different restore steps.

## What you are protecting

| Asset | Typical location | Restore priority |
|-------|------------------|------------------|
| **PostgreSQL** | `DATABASE_URL` cluster or Docker volume | Critical — identities, commerce, content |
| **Schema + migrations** | `data/schema.sql`, `data/migrations/*.sql` | Version-controlled in git; re-apply after empty restore |
| **Blob / object storage** | Provider-resolved `file()` uploads — `ring_filebase` (default), `vercel_blob`, `local_storage`, or `firebase_storage` ([RingFileBase](/docs/integrations/ring-filebase.md), [Ring CDN](/docs/integrations/ring-cdn.md)) | High — product images, attachments, generated media |
| **Runtime secrets** | `.env.local`, k8s Secrets (not in public repo) | Critical — Auth.js, WayForPay/Stripe, FCM service account |
| **Redis cache** | Docker `ring-redis-data` volume | Low — safe to rebuild; sessions may reset |
| **FCM tokens** | Postgres `fcm_tokens` JSONB when on postgres-primary | Covered by DB backup |

### For founders

## Why backup matters for your clone

A Ring deployment is not “just a website.” Postgres holds **membership state**, **vendor catalogs**, **order and payment ledgers**, **opportunity listings**, and **referral accounting**. Losing the database without a restore point means re-onboarding users and reconciling payments manually.

### Typical scenarios

  
- **[Pre-launch clone](/docs/getting-started/migrations.md)** — Take a snapshot after schema apply and seed data — rollback if a bad migration runs on staging.

  
- **[Multi-vendor marketplace](/docs/features/store.md)** — Orders and inventory live in Postgres; product images may live in Blob — back up **both**.

  
- **[Managed Ringdom hosting](/docs/development/oss-vs-enterprise.md)** — Settlers on ringdom.org k8s get operator-run CNPG/MinIO schedules; self-hosters own their own dump cadence.

  
- **[Compliance & audit](/docs/features/security.md)** — PaymentConductor rows in `payment_transactions` support dispute resolution — include DB in retention policy.

### Planning RTO and RPO (your numbers)

Ring Platform does **not** ship fixed recovery SLAs in the OSS repo. Define targets per clone:

- **RPO** — how much data you can lose (e.g. “last nightly dump” vs “hourly logical backup”).
- **RTO** — how long the clone can stay read-only or offline during restore.

Document who approves restore (operator vs developer) and where dumps are stored (encrypted object storage, off-cluster).

  `AUTH_SECRET`, WayForPay keys, and Firebase service accounts live in env/Secrets stores. Export them through your password manager or sealed-secrets workflow — a Postgres dump alone cannot rebuild auth.

### For developers

## PostgreSQL-primary (`k8s-postgres-fcm` / `supabase-fcm`)

Application data flows through `DatabaseService` → `PostgreSQLAdapter`. Backup = **logical dump of the clone database** (one DB per white-label clone, e.g. `ring_platform`, `ring_greenfood_live`).

### Logical backup (Docker dev — verified in `data/SCHEMA-README.md`)

### Dump to file

{`mkdir -p backups
docker exec ring-postgres-dev pg_dump -U ring_user ring_platform \
  > backups/ring_platform_$(date +%Y%m%d).sql`}

For production-sized DBs prefer custom format (parallel restore, compression):

{`docker exec ring-postgres-dev pg_dump -U ring_user -Fc ring_platform \
  > backups/ring_platform_$(date +%Y%m%d).dump`}

### Restore into an empty database

{`docker exec -i ring-postgres-dev psql -U ring_user -d ring_platform \
  < backups/ring_platform_20250204.sql`}

Custom format:

{`docker exec -i ring-postgres-dev pg_restore -U ring_user -d ring_platform \
  --clean --if-exists < backups/ring_platform_20250204.dump`}

### Re-apply migrations if restoring an old dump

After restore, compare the restored schema against the flattened SSOT `data/schema.sql` and the incremental files under `data/migrations/` (see [Database migrations](/docs/getting-started/migrations.md)). Apply any missing incremental SQL before starting the app.

Connection string for native `psql`/`pg_dump` (no Docker):

{`export DATABASE_URL=postgresql://user:pass@host:5432/ring_platform
pg_dump "$DATABASE_URL" -Fc -f backups/ring_platform.dump`}

See [Environment configuration](/docs/deployment/environment.md) for `DB_*` variables in `env.local.template`.

### Before destructive migrations

Some migrations require a backup first — e.g. `013_users_email_unique.sql` needs dedupe (`scripts/dedupe-users-by-email.cts`) documented in `data/migrations/README.md`. **Always dump before** running dedupe or role-normalization scripts.

### Object storage

Uploads use the resolved `file()` provider — object bytes are **not** inside Postgres. Resolution (`lib/storage/storage-config.ts`): `NEXT_PUBLIC_STORAGE_PROVIDER` / `STORAGE_PROVIDER` env → `ring-config.json` `storage.provider` → default **`ring_filebase`**. Backends: `ring_filebase` (default) | `vercel_blob` | `local_storage` | `firebase_storage`. Schedule object-store exports separately (or accept media loss); see [RingFileBase](/docs/integrations/ring-filebase.md) and [Ring CDN](/docs/integrations/ring-cdn.md).

### `firebase-full` mode (legacy / community)

When `DB_BACKEND_MODE=firebase-full`, Firestore holds application data (`shouldUseFirebaseForDatabase()` in `lib/database/backend-mode-config.ts`). Use **Google Cloud Firestore export/import** to GCS — not the Postgres commands above. Postgres may still hold Auth.js adapter tables depending on adapter configuration; confirm your clone’s adapter in [Authentication architecture](/docs/architecture/authentication.md).

### Kubernetes / Ringdom operators

The public OSS repo **does not ship** `k8s/` manifests ([OSS vs enterprise](/docs/development/oss-vs-enterprise.md)). Managed clusters typically use **CloudNativePG** `ScheduledBackup` to S3-compatible storage (e.g. MinIO). Operator runbooks live outside this tree; align dump retention with your storage class and off-site copy policy.

```mermaid
flowchart TB
  subgraph App[Ring Next.js]
    DS[DatabaseService]
    Blob[Object storage — file() provider]
  end
  subgraph Protect[Backup targets]
    PG[(PostgreSQL — pg_dump / CNPG)]
    OBJ[Object storage export]
    SEC[Secrets store — separate]
  end
  DS --> PG
  App --> Blob --> OBJ
  App -.-> SEC
```

### Recovery drill (minimal)

Restore latest dump to a **staging** database name (not production).

Run `npm run build` (or health check) against staging `DATABASE_URL`.

Verify login, one entity read, one opportunity list query, and `/api/health`.

Record dump age, restore duration, and gaps — adjust schedule if RPO missed.

## Related documentation

  
- [architecture/data-model](/docs/architecture/data-model.md) — Prerequisite: JSONB collections included in Postgres dumps.

  
- [architecture/backend-modes-and-databases](/docs/architecture/backend-modes-and-databases.md) — Deep-dive: when Postgres vs Firestore owns application data.

  
- [deployment/docker](/docs/deployment/docker.md) — Same-workflow: compose services, volumes, and health checks.

  
- [getting-started/migrations](/docs/getting-started/migrations.md) — Next-step: apply order for incremental migrations after a restore.

  
- [deployment/monitoring](/docs/deployment/monitoring.md) — Same-workflow: alert on backup job failures.
