> ## Documentation Index
> Fetch the complete documentation index at: https://docs.controlplane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Supabase

> Deploy a self-hosted Supabase instance on Control Plane. Covers PostgreSQL with Supabase extensions, Kong API gateway, GoTrue authentication, PostgREST, Realtime, file storage, Studio dashboard, PgBouncer connection pooling, and logical or WAL-G backups.

## Overview

Supabase is an open-source backend-as-a-service built on PostgreSQL. This template deploys the full self-hosted Supabase stack on Control Plane — all services run in your own GVC with no dependency on Supabase Cloud.

### Architecture

* **Postgres** — Supabase-patched PostgreSQL 15 with pgvector, pg\_graphql, pg\_net, pgjwt, and other required extensions pre-installed.
* **Kong** — API gateway and single public entry point. Routes all traffic to PostgREST, Auth, Realtime, and Storage.
* **PostgREST** — Auto-generated REST and GraphQL API derived from your Postgres schema. Stateless, scales horizontally.
* **Auth (GoTrue)** — Full-featured auth service supporting email/password, magic links, OAuth providers, and JWT sessions. Stateless, scales horizontally.
* **Realtime** — WebSocket server that streams database change events to subscribed clients. Stateless, scales horizontally.
* **Storage** — Object storage API backed by S3, GCS, or a local volume.
* **Studio** — Web dashboard for managing your database, auth users, storage, and API settings.
* **pg\_meta** — Postgres metadata API. Runs as a sidecar inside the Studio workload.
* **PgBouncer** *(optional)* — Connection pooler that multiplexes application connections into a smaller pool of real database connections.
* **Backup** *(optional)* — Logical (`pg_dump` cron) or WAL-G (continuous WAL archiving with PITR support).

### What Gets Created

* **Stateful Postgres Workload** — Supabase-patched PostgreSQL with a persistent volume set.
* **Standard Kong Workload** — API gateway, autoscales on RPS.
* **Standard PostgREST Workload** — REST/GraphQL API, autoscales on RPS.
* **Standard Auth Workload** — GoTrue auth service, autoscales on RPS.
* **Standard Realtime Workload** — WebSocket change feed, autoscales on RPS.
* **Standard Storage Workload** — Object storage API, autoscales on RPS *(optional, enabled by default)*.
* **Standard Studio Workload** — Web dashboard with pg\_meta sidecar *(optional, enabled by default)*.
* **Standard PgBouncer Workload** — Connection pooler *(optional, disabled by default)*.
* **Cron Backup Workload** *(optional)* — Logical or WAL-G backup on a schedule.
* **Volume Sets** — One for Postgres data; one for Storage when using the `local` backend.
* **Identity & Policy** — Identities bound to each workload with `reveal` access to credential secrets, and cloud storage access when backup or S3/GCS storage is enabled.
* **Secrets** — Dictionary secrets for Postgres credentials, JWT keys, and service API keys; opaque secrets for Kong routing config, SMTP settings, and OAuth provider credentials.

<Note>
  This template does not create a GVC. You must deploy it into an existing GVC.
</Note>

## Prerequisites

This template has no external prerequisites unless S3/GCS storage or backup is enabled. To install, follow the instructions for your preferred method:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## Configuration

The default `values.yaml` for this template:

```yaml theme={null}
postgres:
  image: supabase/postgres:15.8.1.060
  password: change-me-postgres
  database: postgres
  resources:
    minCpu: 500m
    minMemory: 512Mi
    maxCpu: 2
    maxMemory: 2Gi
  volumeset:
    capacity: 10  # initial capacity in GiB (minimum is 10)
    autoscaling:
      enabled: false
      maxCapacity: 100
      minFreePercentage: 10
      scalingFactor: 1.2
  internalAccess:
    type: same-gvc  # options: none, same-gvc, same-org, workload-list
    workloads:
      #- //gvc/GVC_NAME/workload/WORKLOAD_NAME

kong:
  image: kong:2.8.1
  resources:
    minCpu: 100m
    minMemory: 256Mi
    maxCpu: 1000m
    maxMemory: 1Gi
  minReplicas: 1
  maxReplicas: 3
  publicAccess:
    enabled: false
    siteUrl: ""  # e.g. https://api.my-app.com — required when publicAccess is enabled

postgrest:
  image: postgrest/postgrest:v12.2.3
  resources:
    minCpu: 100m
    minMemory: 128Mi
    maxCpu: 500m
    maxMemory: 512Mi
  minReplicas: 1
  maxReplicas: 3

auth:
  image: supabase/gotrue:v2.170.0
  resources:
    minCpu: 100m
    minMemory: 128Mi
    maxCpu: 500m
    maxMemory: 512Mi
  minReplicas: 1
  maxReplicas: 3
  disableSignup: false
  smtp:
    enabled: false
    host: smtp.example.com
    port: 587
    user: smtp-user
    password: smtp-password
    senderName: Supabase
    senderEmail: noreply@example.com
  # providers:
  #   github:
  #     clientId: ""
  #     clientSecret: ""
  #   google:
  #     clientId: ""
  #     clientSecret: ""

realtime:
  enabled: true
  image: supabase/realtime:v2.34.47
  resources:
    minCpu: 100m
    minMemory: 128Mi
    maxCpu: 500m
    maxMemory: 512Mi
  minReplicas: 1
  maxReplicas: 3

storage:
  enabled: true
  image: supabase/storage-api:v1.14.6
  resources:
    minCpu: 100m
    minMemory: 128Mi
    maxCpu: 500m
    maxMemory: 512Mi
  minReplicas: 1
  maxReplicas: 3
  backend: s3  # options: local, s3, gcs
  volumeset:   # only used when backend is local
    capacity: 10
    autoscaling:
      enabled: false
      maxCapacity: 100
      minFreePercentage: 10
      scalingFactor: 1.2
  s3:          # only used when backend is s3
    bucket: my-storage-bucket
    region: us-east-1
    cloudAccountName: my-s3-cloudaccount
    policyName: my-storage-policy
  gcs:         # only used when backend is gcs (S3-compatible HMAC auth, no cloud account needed)
    bucket: my-storage-bucket
    accessKeyId: my-gcs-hmac-access-key-id
    secretAccessKey: my-gcs-hmac-secret-access-key

studio:
  enabled: true
  image: supabase/studio:2025.06.02-sha-8f2993d
  username: supabase
  password: change-me-studio
  resources:
    minCpu: 100m
    minMemory: 256Mi
    maxCpu: 500m
    maxMemory: 512Mi
  allowedCidrs: []
  #  - 203.0.113.0/24
  #  - 0.0.0.0/0
  internalAccess:
    type: same-gvc
  meta:
    image: supabase/postgres-meta:v0.86.0
    resources:
      minCpu: 100m
      minMemory: 64Mi
      maxCpu: 200m
      maxMemory: 256Mi

jwt:
  secret: your-super-secret-jwt-token-with-at-least-32-characters-long
  secretKeyBase: your-super-secret-key-base-used-by-realtime-must-be-at-least-64-characters-long!!
  anonKey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  serviceRoleKey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

pgbouncer:
  enabled: false
  image: edoburu/pgbouncer:v1.25.1-p0
  poolMode: transaction  # options: session, transaction, statement
  defaultPoolSize: 25
  maxClientConn: 1000
  replicas: 1
  resources:
    minCpu: 100m
    minMemory: 64Mi
    maxCpu: 200m
    maxMemory: 128Mi

backup:
  enabled: false
  mode: logical  # options: logical, walg
  provider: aws  # options: aws, gcp
  resources:
    minCpu: 100m
    minMemory: 128Mi
    maxCpu: 200m
    maxMemory: 256Mi
  logical:
    image: ghcr.io/controlplane-com/backup-images/postgres-backup:17.1.0
    schedule: "0 2 * * *"  # daily at 2am UTC
  walg:
    intervalSeconds: 21600  # base backup every 6 hours
  aws:
    bucket: my-supabase-backup-bucket
    region: us-east-1
    cloudAccountName: my-backup-cloudaccount
    policyName: my-backup-policy
    prefix: supabase/backups
  gcp:
    bucket: my-supabase-backup-bucket
    cloudAccountName: my-backup-cloudaccount
    prefix: supabase/backups
```

### JWT Keys

Supabase uses JWT to authenticate requests between services and from clients.

* `jwt.secret` — The signing secret for all JWTs. Must be at least 32 characters.
* `jwt.secretKeyBase` — Used by Realtime (Phoenix) for cookie signing. Must be at least 64 characters. Generate with `openssl rand -base64 64`.
* `jwt.anonKey` — Public key for unauthenticated (anonymous) client access.
* `jwt.serviceRoleKey` — Privileged key that bypasses row-level security. For trusted server-side code only.

The default values are Supabase's official published development keys and work together out of the box. **Change all of them before any production deployment.**

<Warning>
  `anonKey` and `serviceRoleKey` must be valid HMAC-SHA256 JWTs signed with `jwt.secret`. Using mismatched keys causes `bad_jwt` errors across all services. Use the [Supabase key generator](https://supabase.com/docs/guides/self-hosting/docker#generate-api-keys) to produce a matching set.
</Warning>

### Kong (API Gateway)

Kong is the single entry point for all Supabase API traffic. PostgREST, Auth, Realtime, and Storage are only reachable through Kong.

* `kong.publicAccess.enabled` — Expose Kong on a public external endpoint.
* `kong.publicAccess.siteUrl` — The full URL clients will reach Kong at (e.g. `https://api.my-app.com`). Required when `publicAccess` is enabled. GoTrue uses this for OAuth redirect callbacks and magic link emails.

When `publicAccess` is disabled, internal clients use the Kong hostname within the GVC and OAuth/magic links will not work.

### Postgres

<Warning>
  You must use the `supabase/postgres` image. The standard `postgres` image is missing required extensions (pgvector, pg\_graphql, pg\_net, pgjwt, etc.) that GoTrue, PostgREST, Realtime, and Storage depend on.
</Warning>

* `postgres.password` — Database superuser password. **Change before deploying to production.**
* `postgres.resources` — CPU and memory limits/requests for the Postgres workload.
* `postgres.volumeset.capacity` — Initial volume size in GiB (minimum 10). Autoscaling is configurable.
* `postgres.internalAccess.type` — Controls which workloads can reach Postgres directly: `same-gvc`, `same-org`, `workload-list`, or `none`. All Supabase services connect internally. Use `workload-list` to also grant access to your own application workloads.

### Auth (GoTrue)

* `auth.disableSignup` — Set to `true` to prevent new user registration.

**SMTP** — required for magic link login, email confirmation, and password reset:

* `auth.smtp.enabled` — Enable SMTP. When disabled, all signups are auto-confirmed and email flows are unavailable.
* `auth.smtp.host` / `auth.smtp.port` — SMTP server address and port.
* `auth.smtp.user` / `auth.smtp.password` — SMTP credentials.
* `auth.smtp.senderName` / `auth.smtp.senderEmail` — Display name and from address for outgoing emails.

Any SMTP provider works: Gmail, SendGrid, Mailgun, Postmark, Mailtrap, etc.

**OAuth Providers** — enable by uncommenting `auth.providers` and adding credentials:

```yaml theme={null}
auth:
  providers:
    github:
      clientId: ""
      clientSecret: ""
    google:
      clientId: ""
      clientSecret: ""
```

Supported providers: Apple, Azure, Bitbucket, Discord, Facebook, Figma, GitHub, GitLab, Google, Kakao, Keycloak, LinkedIn, Notion, Slack, Spotify, Twitch, Twitter/X, WorkOS, Zoom.

When setting up OAuth in your provider's developer console:

* **Authorized JavaScript Origin**: `{kong.publicAccess.siteUrl}`
* **Authorized Redirect URI**: `{kong.publicAccess.siteUrl}/auth/v1/callback`

<Note>
  OAuth requires `kong.publicAccess.enabled: true` and a valid `siteUrl`. OAuth providers will not redirect to internal hostnames.
</Note>

### Storage

Three backends are supported:

| Backend | Description                                                                      |
| ------- | -------------------------------------------------------------------------------- |
| `s3`    | Stateless, horizontally scalable. Recommended for production.                    |
| `gcs`   | GCS accessed via the S3-compatible API using HMAC keys. No Cloud Account needed. |
| `local` | Stateful, single-replica, volume-backed. For development only.                   |

For `s3`: set `storage.s3.bucket`, `storage.s3.region`, `storage.s3.cloudAccountName`, and `storage.s3.policyName`. The IAM policy must grant `s3:GetObject`, `s3:PutObject`, and `s3:DeleteObject` on the bucket.

For `gcs`: create HMAC keys in the GCP console under **Cloud Storage → Settings → Interoperability** and set `storage.gcs.accessKeyId` and `storage.gcs.secretAccessKey`.

<Note>
  If using S3/GCS for backup as well, storage and backup require **separate** buckets, cloud accounts, and IAM policies.
</Note>

### Studio (Web Dashboard)

Studio is protected by username and password login and has no external access by default.

* `studio.username` / `studio.password` — Login credentials. **Change before deploying to production.**
* `studio.allowedCidrs` — List of CIDRs allowed to reach Studio externally. Empty by default (no external access). Use `cpln workload connect` for local tunnel access, or set to `["0.0.0.0/0"]` to open it publicly (login is still required).

### PgBouncer

PgBouncer multiplexes application connections into a smaller pool of real database connections, reducing Postgres connection overhead under high concurrency. Disabled by default.

| Pool Mode     | Description                                                                                                                                                            |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transaction` | Connection held only for the duration of a transaction. Best for most web and API workloads. Not compatible with `SET` variables, temporary tables, or advisory locks. |
| `session`     | Connection held for the entire client session. Compatible with all Postgres features.                                                                                  |
| `statement`   | Connection returned after every statement. Transactions not supported.                                                                                                 |

* `pgbouncer.defaultPoolSize` — Number of real Postgres connections per pool (default: `25`).
* `pgbouncer.maxClientConn` — Maximum number of client connections PgBouncer accepts (default: `1000`).

When enabled, connect through PgBouncer rather than directly to Postgres.

## Connecting

All API traffic flows through Kong. Connect your application using the Kong endpoint:

| Endpoint                 | Address                                                           |
| ------------------------ | ----------------------------------------------------------------- |
| API — internal           | `{release-name}-kong.{gvc}.cpln.local:8000`                       |
| API — public             | `{kong.publicAccess.siteUrl}` *(when publicAccess enabled)*       |
| Postgres — direct        | `{release-name}-postgres.{gvc}.cpln.local:5432`                   |
| Postgres — via PgBouncer | `{release-name}-pgbouncer.{gvc}.cpln.local:5432` *(when enabled)* |

Key API paths routed through Kong:

| Service              | Path            |
| -------------------- | --------------- |
| PostgREST (REST API) | `/rest/v1/`     |
| Auth (GoTrue)        | `/auth/v1/`     |
| Storage              | `/storage/v1/`  |
| Realtime             | `/realtime/v1/` |

Pass `apikey: {anonKey}` as a header on all requests. Use `serviceRoleKey` for privileged server-side calls. The Supabase client library handles auth headers automatically:

```js theme={null}
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'https://api.my-app.com',  // kong.publicAccess.siteUrl
  'YOUR_ANON_KEY'
)
```

## Backing Up

Two backup modes are available:

| Mode      | Mechanism                               | Best For                                                        |
| --------- | --------------------------------------- | --------------------------------------------------------------- |
| `logical` | `pg_dump` cron                          | Portable SQL dumps, smaller databases, cross-version migrations |
| `walg`    | Continuous WAL archiving + base backups | Production — supports point-in-time recovery (PITR)             |

Set `backup.enabled: true`, choose a `mode` and `provider`, then fill in the corresponding provider block.

<Warning>
  Switching `backup.mode` between `logical` and `walg` changes Postgres `archive_mode` and `wal_level`, which requires a Postgres restart. Plan the switch accordingly.
</Warning>

### AWS S3

<Steps>
  <Step title="Create a bucket">
    Create an S3 bucket. Set `backup.aws.bucket` and `backup.aws.region` to match.
  </Step>

  <Step title="Set up a Cloud Account">
    If you do not have one, [create a Cloud Account](https://docs.controlplane.com/guides/create-cloud-account). Set `backup.aws.cloudAccountName` to its name.
  </Step>

  <Step title="Create an IAM policy">
    Create an IAM policy with the following JSON (replace `YOUR_BUCKET_NAME`) and set `backup.aws.policyName` to its name:

    ```json theme={null}
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "s3:GetObject",
                    "s3:PutObject",
                    "s3:DeleteObject",
                    "s3:ListBucket",
                    "s3:GetObjectVersion",
                    "s3:DeleteObjectVersion"
                ],
                "Resource": [
                    "arn:aws:s3:::YOUR_BUCKET_NAME",
                    "arn:aws:s3:::YOUR_BUCKET_NAME/*"
                ]
            }
        ]
    }
    ```
  </Step>
</Steps>

### GCS

<Steps>
  <Step title="Create a bucket">
    Create a GCS bucket. Set `backup.gcp.bucket` to its name.
  </Step>

  <Step title="Set up a Cloud Account">
    If you do not have one, [create a Cloud Account](https://docs.controlplane.com/guides/create-cloud-account). Set `backup.gcp.cloudAccountName` to its name.
  </Step>
</Steps>

<Warning>
  You must add the `Storage Admin` role to the GCP service account created for the Cloud Account.
</Warning>

## Restoring a Backup

### Logical Restore

Run from a machine with network access to Postgres (e.g. via `cpln workload connect`):

**AWS S3:**

```bash theme={null}
export PGPASSWORD="YOUR_POSTGRES_PASSWORD"

aws s3 cp "s3://BUCKET_NAME/PREFIX/BACKUP_FILE.sql.gz" - \
  | gunzip \
  | psql \
      --host={release-name}-postgres.{gvc}.cpln.local \
      --port=5432 \
      --username=postgres \
      --dbname=postgres

unset PGPASSWORD
```

**GCS:**

```bash theme={null}
export PGPASSWORD="YOUR_POSTGRES_PASSWORD"

gsutil cp "gs://BUCKET_NAME/PREFIX/BACKUP_FILE.sql.gz" - \
  | gunzip \
  | psql \
      --host={release-name}-postgres.{gvc}.cpln.local \
      --port=5432 \
      --username=postgres \
      --dbname=postgres

unset PGPASSWORD
```

### WAL-G Restore

WAL-G restores require an empty data directory.

<Steps>
  <Step title="List available backups">
    ```bash theme={null}
    cpln workload connect {release-name}-postgres --gvc {gvc} --container wal-g-backup -- wal-g backup-list
    ```
  </Step>

  <Step title="Stop the Postgres workload">
    Stop the workload via the Control Plane console or CLI.
  </Step>

  <Step title="Create a new volume set">
    Create a fresh empty volume set for the restore target — do not reuse the existing one.
  </Step>

  <Step title="Run the restore">
    Run a one-off workload with the new volume set mounted at `/var/lib/postgresql/data` and execute:

    ```bash theme={null}
    wal-g backup-fetch /var/lib/postgresql/data/pg_data BACKUP_NAME
    ```
  </Step>

  <Step title="Restart Postgres">
    Re-point the Postgres workload to the restored volume set and restart. After restore, update `backup.aws.prefix` (or `backup.gcp.prefix`) to a new path before re-enabling backups to avoid WAL stream conflicts with the original cluster's archived segments.
  </Step>
</Steps>

## Important Notes

* **Use the Supabase Postgres image** — `supabase/postgres` is required. The standard `postgres` image is missing extensions that GoTrue, PostgREST, Realtime, and Storage depend on.
* **JWT keys must match** — `anonKey` and `serviceRoleKey` must be HMAC-SHA256 JWTs signed with `jwt.secret`. Mismatched keys produce `bad_jwt` errors. Use the [official Supabase key generator](https://supabase.com/docs/guides/self-hosting/docker#generate-api-keys) to produce a matching set.
* **Change default credentials before production** — `jwt.secret`, `postgres.password`, and `studio.password` are placeholders. Replace all of them before any production deployment.
* **OAuth requires a public siteUrl** — Set `kong.publicAccess.enabled: true` and `kong.publicAccess.siteUrl` before configuring any OAuth provider.
* **Storage and backup use separate buckets** — Do not share a bucket between `storage.s3` and `backup.aws`. Each requires its own bucket, cloud account, and IAM policy.
* **Studio has no external access by default** — Use `cpln workload connect` for local access, or set `studio.allowedCidrs` to open it externally.

## External References

<CardGroup cols={2}>
  <Card title="Supabase Self-Hosting Guide" href="https://supabase.com/docs/guides/self-hosting" icon="server">
    Official self-hosting documentation and architecture overview
  </Card>

  <Card title="Supabase Client Libraries" href="https://supabase.com/docs/reference" icon="code">
    JavaScript, Python, Swift, Kotlin, and other client SDK references
  </Card>

  <Card title="GoTrue (Auth) Documentation" href="https://github.com/supabase/gotrue" icon="lock">
    GoTrue auth service configuration and API reference
  </Card>

  <Card title="PostgREST Documentation" href="https://postgrest.org/en/stable/" icon="database">
    Auto-generated REST API from your Postgres schema
  </Card>

  <Card title="WAL-G Documentation" href="https://github.com/wal-g/wal-g" icon="box-archive">
    WAL-G continuous archiving and PITR restore documentation
  </Card>

  <Card title="Generate Supabase API Keys" href="https://supabase.com/docs/guides/self-hosting/docker#generate-api-keys" icon="key">
    Generate matching JWT secret, anonKey, and serviceRoleKey
  </Card>
</CardGroup>
