> ## 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.

# PocketBase

> Deploy PocketBase on Control Plane into an existing GVC as a single-instance backend with embedded SQLite on a persistent volume, auto-generated REST API, auth, file uploads, realtime, and admin dashboard. One dictionary secret with the superuser login and encryption key must exist first.

## Overview

PocketBase is an open-source backend (MIT licensed) that ships as a single executable: an embedded SQLite database, an auto-generated REST API over your collections, user authentication, file uploads, realtime subscriptions over Server-Sent Events, and a web admin dashboard. This template deploys one stateful PocketBase workload with its data directory on a persistent volume, served over HTTPS on the canonical `*.cpln.app` endpoint. The superuser account is created from a secret you provide before the server starts listening, so the dashboard never has an unclaimed admin account.

It suits a mobile or single-page app backend, a prototype that needs a real API quickly, or a small internal tool — anywhere a full database plus API server plus auth service would be more than the team needs.

<Note>
  This template does not create a GVC. It deploys into an existing GVC that you already have.
</Note>

### What Gets Created

| Resource | Name | Purpose | Optional |
| - | - | - | - |
| Workload | `RELEASE_NAME-pocketbase` | PocketBase server: REST API, realtime stream, and the `/_/` dashboard (stateful, 1 replica, port `8090`) | — |
| Volume set | `RELEASE_NAME-pocketbase-data` | `/pb_data` — SQLite database, uploaded files, and PocketBase's own local backup archives (10 GiB, scheduled snapshots) | — |
| Identity | `RELEASE_NAME-pocketbase-identity` | Workload identity | — |
| Policy | `RELEASE_NAME-pocketbase-policy` | `reveal` on exactly one secret — your credentials secret | — |

The credentials secret is **not** created by the template; you create it before installing (see [Prerequisites](#prerequisites)). PocketBase has no external database or cache — it talks to nothing but its own volume.

## Prerequisites

**One [dictionary secret](/guides/create-secret/dictionary) must exist before you install.** It holds the superuser login and the key that encrypts settings at rest; none of these values pass through Helm values. Secrets are org-level, so no GVC flag is involved.

<Steps>
  <Step title="Create the credentials secret">
    The secret must have exactly these three keys:

    | Key | Value |
    | - | - |
    | `email` | Superuser email for the `/_/` dashboard. |
    | `password` | Superuser password — **at least 8 characters**. |
    | `encryptionKey` | **Exactly 32 characters** — encrypts the SMTP password, OAuth2 client secrets, and S3 credentials stored in the database. |

    ```bash theme={null}
    cpln secret create-dictionary --name SECRET_NAME \
      --entry email=admin@example.com \
      --entry password='YOUR-STRONG-PASSWORD' \
      --entry encryptionKey="$(openssl rand -hex 16)"
    ```

    `openssl rand -hex 16` produces exactly the 32 characters PocketBase requires. Set `credentials.secretName` to this name. If you run more than one release in the same org, give each its own secret.
  </Step>

  <Step title="Keep a copy of the encryption key">
    Store `encryptionKey` somewhere safe outside Control Plane. It can never be changed after install without orphaning everything it protects. Read it back any time with:

    ```bash theme={null}
    cpln secret reveal SECRET_NAME -o yaml
    ```
  </Step>
</Steps>

<Warning>
  **The secret is the login, at every restart.** The container runs `pocketbase superuser upsert` with the `email` and `password` from this secret before the server binds its port, on **every** start. Changing the password in the dashboard is reverted at the next restart — change it in the secret instead, then force a redeployment (see [Rotating the Superuser Password](#rotating-the-superuser-password)).
</Warning>

<Warning>
  **A missing prerequisite secret wedges the deployment silently.** `cpln helm install` still succeeds, all four resources are created, and the workload never becomes ready. `cpln logs` returns nothing at all, because the container never ran. The only place the reason appears is `status.versions[].message`, which names the missing secret:

  ```bash theme={null}
  cpln workload get-deployments RELEASE_NAME-pocketbase --gvc GVC_NAME -o yaml
  ```

  Note this is `get-deployments` — plain `cpln workload get` has no `versions` key. Create the missing secret and the deployment recovers on its own, or force a redeployment to skip the wait:

  ```bash theme={null}
  cpln workload force-redeployment RELEASE_NAME-pocketbase --gvc GVC_NAME
  ```
</Warning>

Nothing else is required. Backups are scheduled volume set snapshots managed by the platform and need no bucket or cloud account — see [Backing Up](#backing-up).

## Installation

Once the credentials secret exists, install from the marketplace registry, naming the secret:

```bash theme={null}
cpln helm install RELEASE_NAME oci://ghcr.io/controlplane-com/templates/pocketbase \
  --version 1.0.0 --gvc GVC_NAME \
  --set credentials.secretName=SECRET_NAME
```

Or 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

### PocketBase Instance

```yaml theme={null}
image: ghcr.io/muchobien/pocketbase:0.40.1 # must run as root with `pocketbase` on PATH

resources:
  maxCpu: 500m
  maxMemory: 1Gi
  minCpu: 125m # ratio to maxCpu may not exceed 4:1 on a stateful workload
  minMemory: 256Mi

volumeset:
  capacity: 10 # GiB (minimum 10) — SQLite database, uploaded files, and any local backups
```

The `resources` block exposes both a reservation and a limit, so the limit is named `maxCpu` / `maxMemory`; a values file carrying the bare `cpu` or `memory` keys is rejected at render with a message telling you to rename them. Volume capacity is fixed when the volume set is created, so size `volumeset.capacity` at install time — uploads and PocketBase's local backup archives share it with the database.

No official PocketBase image exists. This template pins a widely used community build and **replaces its entrypoint**: the container runs `pocketbase superuser upsert` to completion and then `exec pocketbase serve` directly, so the chart depends only on the `pocketbase` binary at the pinned tag.

<Warning>
  **The `PB_ADMIN_EMAIL` and `PB_ADMIN_PASSWORD` environment variables described for this image do nothing here.** They belong to the image's own `entrypoint.sh`, which this chart does not run. PocketBase itself reads no environment variables for configuration — everything is either a CLI flag the chart sets or a dashboard setting. If you override `image`, it must run as root with `pocketbase` on `PATH`.
</Warning>

### Credentials

```yaml theme={null}
credentials:
  secretName: my-pocketbase-credentials
```

`credentials.secretName` names your pre-created dictionary secret holding `email`, `password`, and `encryptionKey` (see [Prerequisites](#prerequisites)). It **must exist before you install**, and it is the only secret the workload's identity can reveal. The secret is re-read only when the replica starts, so a changed value needs a forced redeployment — see [Rotating the Superuser Password](#rotating-the-superuser-password).

### Backup

```yaml theme={null}
backup:
  enabled: true # periodic snapshots of the data volume
  schedule: "0 3 * * *" # cron in UTC — default daily at 03:00 (hourly is the most frequent the platform allows)
  retention: 7d # how long each snapshot is kept (e.g. 7d, 720h, 30d)
```

Backups are scheduled, crash-consistent snapshots of the data volume, managed by the platform — no cloud account or bucket is required. `backup.enabled: false` removes the schedule only; `backup.retention` still applies, and a final snapshot is taken when the release is uninstalled either way. See [Backing Up](#backing-up).

### API

```yaml theme={null}
cors:
  allowedOrigins: # browser origins allowed to call the API; ["*"] allows any
    - "*"
```

`cors.allowedOrigins` is passed to PocketBase as its `--origins` flag and must list at least one origin (the chart refuses to render an empty list). With a restricted list, a browser request from an origin that is not on it still returns `200` but without an `access-control-allow-origin` header — the browser is what blocks the response. A change takes effect once the new replica is serving, so verify it with a real request rather than by polling readiness.

### Access

```yaml theme={null}
publicAccess:
  enabled: true # REST API, realtime and the /_/ dashboard on the canonical *.cpln.app HTTPS endpoint

internalAccess: # internal firewall scope (in-GVC callers of the API)
  type: same-gvc # options: none, same-gvc, same-org, workload-list
  workloads: [] # used with workload-list
  # workloads:
  #   - //gvc/GVC_NAME/workload/WORKLOAD_NAME
```

Public access is safe to leave on: the superuser is created from your secret before the server starts listening, so no unclaimed admin account is ever exposed, and new collections are superuser-only until you write an API rule that opens them. With `publicAccess.enabled: false`, external requests are refused and you reach the dashboard through a port-forward — see [Connecting](#connecting).

| `internalAccess.type` | Description |
| - | - |
| `none` | No internal access. |
| `same-gvc` | Allow access from all workloads in the same GVC (default). |
| `same-org` | Allow access from all workloads in the same org. |
| `workload-list` | Allow access only from the workloads listed in `internalAccess.workloads`. |

Access changes are not instantaneous; re-test after a pause rather than concluding a knob did not work.

## Connecting

The REST API, realtime stream, and the `/_/` dashboard are all served on the PocketBase workload's canonical endpoint, which is public by default (`publicAccess.enabled: true`). Read it from `status.canonicalEndpoint`:

```bash theme={null}
cpln workload get RELEASE_NAME-pocketbase --gvc GVC_NAME -o yaml
```

| What | Address | Notes |
| - | - | - |
| Admin dashboard | `https://CANONICAL_ENDPOINT/_/` | Sign in with the `email` and `password` from your credentials secret |
| API base URL | `https://CANONICAL_ENDPOINT` | The URL you give the PocketBase SDK; records are under `/api/collections/COLLECTION/records` |
| Realtime | `https://CANONICAL_ENDPOINT/api/realtime` | Server-Sent Events — see [Important Notes](#important-notes) for the connection ceiling |
| Health check | `https://CANONICAL_ENDPOINT/api/health` | No authentication required |
| Internal (same GVC) | `http://RELEASE_NAME-pocketbase.GVC_NAME.cpln.local:8090` | Subject to `internalAccess`; always use the fully qualified name |
| Login | `email` and `password` in your credentials secret | `cpln secret reveal SECRET_NAME -o yaml` |

Verify the instance is serving before you sign in:

```bash theme={null}
curl -s https://CANONICAL_ENDPOINT/api/health
```

A `200` with a small JSON body means PocketBase is up. From your application, point the official SDK at the base URL:

```javascript theme={null}
import PocketBase from 'pocketbase';

const pb = new PocketBase('https://CANONICAL_ENDPOINT');
const records = await pb.collection('posts').getList(1, 20);
```

With `publicAccess.enabled: false`, forward the port instead. PocketBase listens on all interfaces inside the container, so the tunnel reaches it with the firewall fully closed:

```bash theme={null}
cpln port-forward RELEASE_NAME-pocketbase 8090:8090 --gvc GVC_NAME
```

Then open `http://localhost:8090/_/`. Browser traffic through a port-forward is a convenience for setup, not a substitute for the real endpoint.

### First Run

There is no setup wizard: the superuser account already exists when PocketBase first starts.

<Steps>
  <Step title="Sign in to the dashboard">
    Open `https://CANONICAL_ENDPOINT/_/` and sign in with the `email` and `password` from your credentials secret.
  </Step>

  <Step title="Set the Application URL">
    Go to **Settings → Application → Application URL** and set it to `https://CANONICAL_ENDPOINT`. Verification and password-reset emails build their links from this value, so until you set it those links point at the wrong host. It is a database setting, so the template cannot write it for you.
  </Step>

  <Step title="Configure SMTP and OAuth2 providers">
    Both live under **Settings**, in the database rather than in Helm values, and are encrypted at rest with your `encryptionKey`.
  </Step>

  <Step title="Create your collections and API rules">
    New collections are superuser-only until you write an API rule that opens them.
  </Step>
</Steps>

## Operations

### Backing Up

Two layers protect `/pb_data`, and they are configured in different places.

**Volume set snapshots (configured by the template).** On the cron schedule in `backup.schedule` (default daily at 03:00 UTC), the platform takes a crash-consistent snapshot of `RELEASE_NAME-pocketbase-data`; SQLite recovers cleanly from one through its write-ahead log. Snapshots are pruned after `backup.retention` (default 7 days), and a final snapshot is always taken when the release is uninstalled. A snapshot covers everything on the volume: the SQLite database, uploaded files, and any backup archives PocketBase itself wrote locally.

```bash theme={null}
# list snapshots for the release's volume set
cpln volumeset snapshot get RELEASE_NAME-pocketbase-data --gvc GVC_NAME

# take one on demand
cpln volumeset snapshot create RELEASE_NAME-pocketbase-data --gvc GVC_NAME --snapshot-name NAME
```

Snapshots live in the platform storage layer alongside the volume, not off-site — they protect against data corruption and accidental changes, but they are not an off-platform copy.

**PocketBase's own backups (configured in the dashboard).** Under **Settings → Backups**, PocketBase can create ZIP archives of `pb_data` on demand or on its own schedule. Because the chart runs the server with `--dir=/pb_data`, local archives land under `/pb_data/backups` — on the same volume as the database, and therefore inside every volume snapshot. Pointing the backups at an S3 bucket in the same screen is the way to get an off-platform copy; the S3 credentials are stored in the database and encrypted with your `encryptionKey`. This template deliberately does not configure any of this — it is a database setting with no Helm knob.

Keep a copy of the credentials secret too, especially `encryptionKey`: a restored database only yields usable SMTP, OAuth2, and S3 settings if PocketBase runs with the same key they were encrypted under.

### Restoring a Backup

Two restore paths follow from the two backup layers. **Neither has been exercised against a live install of this template** — rehearse on a throwaway release before you depend on either.

**From a volume set snapshot.** The platform provisions a fresh volume from the chosen snapshot and swaps it in; the single-replica workload restarts to remount it, and everything written after the snapshot is lost. List the snapshots with `cpln volumeset snapshot get RELEASE_NAME-pocketbase-data --gvc GVC_NAME`, then restore with `cpln volumeset snapshot restore` — check `cpln volumeset snapshot restore --help` for the flags that select the snapshot, location, and volume index. See the [volume set reference](/reference/volumeset#snapshots).

**From a PocketBase backup archive.** Under **Settings → Backups** in the dashboard, PocketBase can restore one of its own ZIP archives in place and restart itself. A restore brings back the data as it was when the archive was taken, including the collections, records, uploaded files, and settings. Because the template re-applies the superuser from your secret on every start, the login after a restore is whatever your secret holds, not what the archive held — and the restored settings decrypt only if `encryptionKey` is unchanged.

<Warning>
  Both paths restart the only replica, so PocketBase is unavailable while the restore completes, and both revert everything written after the backup was taken.
</Warning>

### Rotating the Superuser Password

The superuser password is re-applied from the credentials secret on every start, so it is rotated by changing the secret and restarting the workload — not from inside the dashboard. Apply the whole secret with the new password, keeping `email` and `encryptionKey` exactly as they are, then force a redeployment (a changed secret is picked up only when the replica restarts):

```yaml theme={null}
# credentials-secret.yaml
kind: secret
name: SECRET_NAME
type: dictionary
data:
  email: admin@example.com
  password: 'YOUR-NEW-PASSWORD'
  encryptionKey: 'PASTE_THE_EXISTING_32_CHARACTER_KEY'
```

```bash theme={null}
cpln apply -f credentials-secret.yaml
cpln workload force-redeployment RELEASE_NAME-pocketbase --gvc GVC_NAME
```

Until the new replica is serving, the old password continues to work and the workload reports ready against the outgoing replica. There is no error and no warning if you skip the redeployment — the old credential simply stays valid.

<Warning>
  **Never change `encryptionKey` in this manifest.** It encrypts the SMTP password, OAuth2 client secrets, and S3 backup credentials stored inside the database; a different key orphans all of them with no way back.
</Warning>

### Scaling and Availability

PocketBase runs as **exactly one replica**, and there is deliberately no `replicas` knob. The database is embedded SQLite with no clustering, so PocketBase scales vertically only — raise `resources.maxCpu` and `resources.maxMemory` (keeping `maxCpu` within 4:1 of `minCpu`) rather than adding instances. On this platform each stateful replica would also get its own volume, so a second replica would serve a different, empty database behind the same endpoint.

Any restart — a `helm upgrade` that changes the container, a forced redeployment, or a replica being rescheduled — takes the service offline for a short time while the new replica starts and the volume reattaches. Data is not at risk: the same volume reattaches, and encrypted settings still decrypt. The difference between this single instance and a highly available service is minutes of downtime, not data loss.

### Troubleshooting

<AccordionGroup>
  <Accordion title="Deployment never becomes ready and the logs are empty">
    **Symptom:** `cpln helm install` reported success, `cpln logs` returns nothing, and `cpln workload get-deployments RELEASE_NAME-pocketbase --gvc GVC_NAME -o yaml` shows `The secret SECRET_NAME no longer exists. Workload updates are paused until the secret is added or the reference to the secret removed.` under `status.versions[].message`.

    **Cause:** The dictionary secret named by `credentials.secretName` does not exist.

    **Fix:** Create it (see [Prerequisites](#prerequisites)). The deployment recovers on its own once the secret exists; `cpln workload force-redeployment RELEASE_NAME-pocketbase --gvc GVC_NAME` skips the wait.
  </Accordion>

  <Accordion title="Container exits with password must be at least 8 characters">
    **Symptom:** The workload restarts repeatedly and the logs show `password: Must be at least 8 character(s).`

    **Cause:** The `password` entry in your credentials secret is shorter than PocketBase's minimum. The chart runs the superuser command with `set -e`, so a rejected credential is a visible crash rather than a silent skip.

    **Fix:** Update the secret with a password of at least 8 characters and force a redeployment (see [Rotating the Superuser Password](#rotating-the-superuser-password)).
  </Accordion>

  <Accordion title="Container exits with crypto/aes invalid key size">
    **Symptom:** The logs show `crypto/aes: invalid key size 31` (or another number) and the container never serves.

    **Cause:** `encryptionKey` is not a valid AES key length. PocketBase requires exactly 32 characters.

    **Fix:** On a fresh install that has never started, recreate the secret with `encryptionKey="$(openssl rand -hex 16)"` and force a redeployment. On an install that already ran with a valid key, do not change it — restore the original key instead, or every encrypted setting is lost.
  </Accordion>

  <Accordion title="Render fails saying resources.cpu is not a knob in this chart">
    **Symptom:** `pocketbase: resources.cpu is not a knob in this chart — a block exposing both a reservation and a limit names the limit resources.maxCpu.` (or the same for `resources.memory`).

    **Cause:** Your values use the bare `cpu` / `memory` names.

    **Fix:** Rename them to `resources.maxCpu` and `resources.maxMemory`.
  </Accordion>

  <Accordion title="Password changed in the dashboard is back to the old one after a restart">
    **Symptom:** You changed the superuser password under the dashboard's settings; after an upgrade or redeployment the old password works again and the new one is rejected.

    **Cause:** The chart runs `pocketbase superuser upsert` from the credentials secret on every start, so the secret is authoritative.

    **Fix:** Change the password in the secret and force a redeployment — see [Rotating the Superuser Password](#rotating-the-superuser-password).
  </Accordion>

  <Accordion title="Realtime subscriptions disconnect every ten minutes">
    **Symptom:** A long-lived `GET /api/realtime` stream ends cleanly after ten minutes even while events are flowing; a hand-rolled client stops receiving updates.

    **Cause:** Realtime is Server-Sent Events — one long HTTP response — and the workload's request timeout (`timeoutSeconds`, set to `600`, the platform maximum) closes it. PocketBase also closes a stream that receives no events for five minutes, by its own design. Nothing is lost while the stream is open, and the cut is a normal end-of-stream.

    **Fix:** The official PocketBase SDKs reconnect and resubscribe automatically. A custom `EventSource` or `curl` consumer must reconnect and resubscribe when the stream ends.
  </Accordion>

  <Accordion title="PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD have no effect">
    **Symptom:** You set the environment variables documented for the community image and the superuser login did not change.

    **Cause:** Those variables are read by the image's own `entrypoint.sh`, which this chart replaces with its own command. PocketBase itself reads no environment variables for configuration.

    **Fix:** The superuser is defined by the `email` and `password` keys of your credentials secret; nothing else is consulted.
  </Accordion>

  <Accordion title="Browser requests to the API are blocked by CORS">
    **Symptom:** The browser console shows a CORS error and the response has no `access-control-allow-origin` header, while the same request from `curl` returns `200`.

    **Cause:** `cors.allowedOrigins` does not include your app's origin.

    **Fix:** Add the origin (scheme and host, e.g. `https://app.example.com`) to `cors.allowedOrigins` and upgrade the release; the change applies once the new replica is serving.
  </Accordion>

  <Accordion title="Verification and password-reset emails link to the wrong host">
    **Symptom:** Links in emails PocketBase sends point at `localhost` or a hostname that is not your endpoint.

    **Cause:** **Settings → Application → Application URL** has not been set. It is a database setting the chart cannot write.

    **Fix:** Set it to `https://CANONICAL_ENDPOINT` in the dashboard (see [First Run](#first-run)).
  </Accordion>
</AccordionGroup>

## Important Notes

* **Create the credentials secret before installing.** Without it the deployment wedges silently: `cpln logs` returns nothing and the only diagnostic is `status.versions[].message` from `cpln workload get-deployments`.
* **Never change `encryptionKey` after install.** It encrypts the SMTP password, OAuth2 client secrets, and S3 backup credentials in the database; a changed key orphans all of them. Keep a copy outside Control Plane.
* **The secret is the superuser login at every restart.** A password changed in the dashboard is reverted; change it in the secret and force a redeployment. See [Rotating the Superuser Password](#rotating-the-superuser-password).
* **Rotating the secret needs a forced redeployment.** The workload does not pick up a changed secret on its own, and nothing warns you — the old password keeps working until the replica restarts.
* **Single instance, no HA, and that is upstream's design.** There is no `replicas` knob; every restart is a brief outage, and data is not at risk because the same volume reattaches. See [Scaling and Availability](#scaling-and-availability).
* **Realtime connections end after ten minutes at most.** The official SDKs reconnect automatically; a hand-rolled client must handle it.
* **Set the Application URL at first login.** Emails link to the wrong host until **Settings → Application → Application URL** is set.
* **Uploads and local backup archives share the volume with the database.** A file-heavy app needs more than the 10 GiB default; set `volumeset.capacity` at install time.
* **Snapshots are not an off-platform copy.** For one, configure PocketBase's own S3 backups under **Settings → Backups**.
* **Restore paths are unverified against this template.** Rehearse a snapshot or archive restore on a throwaway release before relying on it.
* **This is a community image with its entrypoint replaced.** `PB_ADMIN_EMAIL` and `PB_ADMIN_PASSWORD` do nothing here; an `image` override must run as root with `pocketbase` on `PATH`.
* **Data survives restarts and upgrades; uninstalling deletes the volume set** after taking a final snapshot. Your credentials secret is yours and survives an uninstall.

## External References

<CardGroup cols={2}>
  <Card title="PocketBase Documentation" icon="book" href="https://pocketbase.io/docs/">
    Official documentation for collections, API rules, and the dashboard
  </Card>

  <Card title="REST API Reference" icon="code" href="https://pocketbase.io/docs/api-records/">
    Reading and writing records over the auto-generated API
  </Card>

  <Card title="Realtime API" icon="bolt" href="https://pocketbase.io/docs/api-realtime/">
    Server-Sent Events subscriptions and the reconnect model
  </Card>

  <Card title="Going to Production" icon="server" href="https://pocketbase.io/docs/going-to-production/">
    Upstream guidance, including settings encryption and backups
  </Card>

  <Card title="PocketBase Template" icon="github" href="https://github.com/controlplane-com/templates/tree/main/pocketbase">
    View the source files, default values, and chart definition
  </Card>
</CardGroup>
