> ## 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 using the Template Catalog. Self-hosted open-source backend in a single executable — embedded SQLite, an auto-generated REST API, user authentication, file uploads, realtime subscriptions, and an admin dashboard on a persistent volume.

## 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, and a web admin dashboard. This template deploys one stateful workload with its data directory on a persistent volume, served over HTTPS on the canonical `*.cpln.app` endpoint on port `8090`.

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 PostgreSQL plus API-server plus auth-service stack would be more than the team needs.

### Architecture

* **PocketBase** — A single-replica stateful workload running one container that serves the REST API, the realtime stream, and the `/_/` dashboard on port `8090`. The superuser account is created from your credentials secret before the server ever binds the port, so there is no unclaimed-admin window.
* **SQLite on a persistent volume** — The database, uploaded files, and any locally-stored backup archives all live at `/pb_data` on the workload's volume set. There is no external database dependency; PocketBase talks to nothing but its own disk.

### What Gets Created

* **Stateful PocketBase Workload** — One replica (`{release}-pocketbase`) serving the API, realtime, and dashboard on port `8090`.
* **Volume Set** — A 10 GiB persistent volume (`{release}-pocketbase-data`) mounted at `/pb_data`, holding the SQLite database, uploaded files, and local backup archives. Scheduled snapshots protect the data, and a final snapshot is taken on uninstall.
* **Identity & Policy** — An identity bound to the workload, with a policy granting `reveal` on exactly one secret: the credentials secret you create. Nothing else.

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

## Prerequisites

PocketBase needs one **required prerequisite [dictionary secret](/guides/create-secret/dictionary) that 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.

<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 SMTP, OAuth2, and S3 settings stored in the database |

    ```bash theme={null}
    cpln secret create-dictionary --name my-pocketbase-credentials \
      --entry email=admin@example.com \
      --entry password='choose-a-strong-password' \
      --entry encryptionKey="$(openssl rand -hex 16)"
    ```

    `openssl rand -hex 16` produces exactly the 32 characters PocketBase requires.
  </Step>

  <Step title="Point the template at it">
    Set `credentials.secretName` to the secret's name (default `my-pocketbase-credentials`). Secrets are org-level, so no GVC flag is involved — give each release its own secret name if you run more than one.
  </Step>

  <Step title="Keep a copy of the encryption key">
    Store `encryptionKey` somewhere safe outside Control Plane. It cannot be changed later without orphaning everything it protects — see [Important Notes](#important-notes).
  </Step>
</Steps>

<Warning>
  **Both credential rules are enforced by PocketBase itself, and a rejected value is a visible crash rather than a silent skip.** A password under 8 characters is refused with `password: Must be at least 8 character(s).`, and an encryption key that is not a valid AES length fails with `crypto/aes: invalid key size 31`. Use `openssl rand -hex 16` and the length is correct by construction.
</Warning>

<Warning>
  **Installing without the secret looks like a platform fault, not a missing step.** `cpln helm install` reports complete success and creates all four resources, then the workload simply never becomes ready. `cpln logs` returns **zero lines** — the container never starts, so there is no output to collect — and the only diagnostic is in `status.versions[].message`:

  ```text theme={null}
  The secret my-pocketbase-credentials no longer exists. Workload updates
  are paused until the secret is added or the reference to the secret
  removed.
  ```

  Read it with `cpln workload get-deployments {release}-pocketbase --gvc {gvc} -o yaml`. Creating the secret afterwards **does** release the deployment on its own, but slowly — measured at 10 minutes 11 seconds — so poll rather than time-box it. To skip the wait, force a redeployment of the workload.
</Warning>

Once your credentials secret exists, install the template using 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>

## First Steps After Install

A default install reaches ready in well under a minute. The superuser account already exists — it is created from your secret before the server starts listening — so there is nothing to claim and no open signup window.

<Steps>
  <Step title="Sign in to the dashboard">
    Open `https://<canonical>.cpln.app/_/` 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 your public 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>

## Configuration

The default `values.yaml` for this template:

```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

# ─── Credentials (REQUIRED — create this secret BEFORE installing) ────────────
# A dictionary secret with exactly three keys:
#   email          superuser login for the dashboard at /_/
#   password       superuser password, at least 8 characters (re-applied on EVERY
#                  start — the secret is authoritative; changing it in the
#                  dashboard is reverted)
#   encryptionKey  EXACTLY 32 characters (openssl rand -hex 16) — encrypts SMTP,
#                  OAuth2 and S3 settings at rest. NEVER change it after install.
# Installing without this secret wedges the deployment silently: no logs at all.
# The missing name appears only in `cpln workload get-deployments` under
# status.versions[].message. See the README.
credentials:
  secretName: my-pocketbase-credentials

# ─── Backup ───────────────────────────────────────────────────────────────────
# Scheduled, crash-consistent snapshots of the data volume, managed by the
# platform (no cloud account or bucket required). SQLite recovers cleanly from a
# snapshot via its WAL. A final snapshot is always taken on uninstall.
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)

# ─── API ──────────────────────────────────────────────────────────────────────
cors:
  allowedOrigins: # browser origins allowed to call the API; ["*"] allows any
    - "*"

# ─── Access ───────────────────────────────────────────────────────────────────
publicAccess:
  # Safe to leave on: the superuser is created from the secret above BEFORE the
  # server starts listening, so there is no unclaimed-admin window, and new
  # collections are superuser-only until you write an API rule.
  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
```

### PocketBase Instance

* `image` — The PocketBase container image. See [Image](#image) for why this is a community build and what an override must satisfy.
* `resources` — CPU and memory bounds for the container. The block exposes both a reservation and a limit, so the limit is named `maxCpu` / `maxMemory`; the bare `cpu` and `memory` keys are rejected at render with a message telling you to rename them. On a stateful workload the platform caps the `maxCpu` to `minCpu` ratio at 4:1, which the defaults sit exactly on.
* `volumeset.capacity` — Volume size in GiB (minimum 10) for `/pb_data`. Capacity is fixed when the volume set is created, so size it at install time.

### Credentials

* `credentials.secretName` — Name of 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 superuser password is re-applied from this secret on **every** start, and rotating it needs one extra step — see [Rotating Credentials](#rotating-credentials).

### Backup

Scheduled, crash-consistent snapshots of the data volume, managed by the platform — no cloud account or bucket is required. See [Backing Up](#backing-up) for how snapshots and restores work.

* `backup.enabled` — Take periodic snapshots of the data volume (default `true`). Setting it to `false` removes the schedule; the final snapshot on uninstall is taken either way.
* `backup.schedule` — Cron expression in UTC (default `0 3 * * *`, daily at 03:00). The platform does not accept schedules more frequent than hourly.
* `backup.retention` — How long each snapshot is kept, e.g. `7d`, `720h`, `30d`.

### API

* `cors.allowedOrigins` — Browser origins allowed to call the API, passed to PocketBase as its `--origins` flag. The default `["*"]` allows any origin. With a restricted list, a request from an origin that is not on it still returns `200` but comes back **without** an `access-control-allow-origin` header — which is correct CORS behavior, since the browser is what blocks the response.

<Note>
  A `cors.allowedOrigins` change takes effect only once the new replica is serving, measured at 110 seconds after the upgrade — and the workload reports `ready: true` almost immediately, against the *outgoing* replica. Verify a CORS change with an actual request rather than by polling readiness.
</Note>

### Access

* `publicAccess.enabled` — Serve the REST API, realtime, and the `/_/` dashboard on the canonical `*.cpln.app` HTTPS endpoint (default `true`). This 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. Set it to `false` for an internal-only instance — external requests are then refused at the edge with a `403`.
* `internalAccess.type` — Controls which workloads can reach PocketBase over the internal network:

| 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 organization.              |
| `workload-list` | Allow access only from workloads listed in `internalAccess.workloads`. |

<Note>
  Firewall changes are not instantaneous. Flipping `publicAccess.enabled` took 126 seconds to be enforced in testing. Re-test after a pause rather than concluding the knob did not work.
</Note>

A private install stays fully operable — PocketBase binds `0.0.0.0`, so the dashboard is reachable through a tunnel even with public access off and `internalAccess.type: none`. Forward the port and open `http://localhost:8090/_/`:

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

### Image

No official PocketBase image exists. This template pins a well-used community build and **overrides its entrypoint**, running `pocketbase superuser upsert` to completion and then `exec pocketbase serve` directly. The chart therefore depends only on the binary at the pinned tag, not on an entrypoint script that is versioned by the image repository rather than by PocketBase.

<Warning>
  **The `PB_ADMIN_EMAIL` and `PB_ADMIN_PASSWORD` environment variables you will find in community documentation do nothing here.** They are a feature of that image's `entrypoint.sh`, which this chart replaces. PocketBase core reads **no** environment variables at all — anything you want configured is either a CLI flag or a dashboard setting. If you override `image`, it must run as root with `pocketbase` on `PATH`.
</Warning>

## Connecting

| What                | Value                                                                                     |
| ------------------- | ----------------------------------------------------------------------------------------- |
| Public base URL     | `https://<canonical>.cpln.app` — `status.canonicalEndpoint` of `{release}-pocketbase`     |
| Admin dashboard     | `https://<canonical>.cpln.app/_/`                                                         |
| REST API            | `https://<canonical>.cpln.app/api/`                                                       |
| Health check        | `GET /api/health` — no auth, returns `{"message":"API is healthy.","code":200,"data":{}}` |
| Internal (same GVC) | `http://{release}-pocketbase.{gvc}.cpln.local:8090` — use the fully qualified name        |
| Login               | The `email` and `password` entries of your credentials secret                             |
| Private install     | Forward the port with `cpln port-forward`, then open `http://localhost:8090/_/`           |

## Realtime Subscriptions

PocketBase delivers realtime updates over **Server-Sent Events** — one long-lived HTTP response, not a websocket. A client holds `GET /api/realtime` open and posts its subscriptions to `POST /api/realtime`.

<Warning>
  **Every realtime connection is closed after ten minutes.** The platform cuts any HTTP request at the workload's `timeoutSeconds`, which this template already sets to `600` — the maximum the platform allows. Server-Sent Events are not exempt the way an upgraded websocket connection is, so ten minutes is the longest a subscription can live here. **This is a hard ceiling, not a tunable.**

  A stream that goes quiet ends sooner: PocketBase itself disconnects an idle subscription after about five minutes, by its own design. So ten minutes is the ceiling for an *active* stream, not a floor.
</Warning>

What was measured over a full ten-minute stream, kept continuously active:

* **No events are lost while the stream is open** — 10 of 10 record events were delivered, each carrying the mutation that triggered it.
* **The cut is a clean end-of-stream, not an error or a reset.** The connection simply ends.
* **The cause is the platform timeout, proven causally.** Lowering `timeoutSeconds` to 60 cut an otherwise identical stream at exactly 60 seconds.

The practical consequence depends on your client:

| Client                                                  | What you need to do                                                                                                                                     |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Official PocketBase SDKs                                | Nothing. They reconnect and resubscribe automatically, so the cut is invisible.                                                                         |
| A hand-rolled `EventSource`, `curl`, or custom consumer | **Handle the reconnect yourself.** Expect an active stream to end at ten minutes at the latest — sooner if it goes idle — and resubscribe when it does. |

If someone reports that realtime "keeps dropping" or "reconnects every ten minutes", this is the reason and it is expected behavior.

## Rotating Credentials

The superuser password is re-applied from the credentials secret on **every** start — the container runs `pocketbase superuser upsert` before the server binds. The secret is therefore the source of truth for the login, and changing the password in the dashboard is reverted at the next restart.

<Warning>
  **Updating the secret does not take effect on its own — you must force a redeployment.** Measured: eleven minutes after rotating the `password` key, the container still held the old environment, the old password still authenticated, and the workload reported `ready: true` the entire time. There is no error and no warning, so a rotation looks like it succeeded while the old credential stays valid indefinitely.
</Warning>

<Steps>
  <Step title="Update the secret">
    Change the `password` entry of your credentials secret. Leave `encryptionKey` alone — see [Important Notes](#important-notes).
  </Step>

  <Step title="Force a redeployment">
    ```bash theme={null}
    cpln workload force-redeployment {release}-pocketbase --gvc {gvc}
    ```
  </Step>

  <Step title="Wait for the new replica to serve">
    The rotation applies once the new replica is serving, which costs the usual restart gap of about 85 seconds (see [Restarts and Availability](#restarts-and-availability)). Until then the old password continues to work. Afterwards the old password is rejected with `400` and the new one authenticates.
  </Step>
</Steps>

## Restarts and Availability

PocketBase runs as **exactly one replica**, and there is deliberately no `replicas` knob. This is upstream's design, not a limitation of this template — PocketBase scales vertically only ("Horizontal scaling? Only on a single server"), because the database is embedded SQLite with no clustering. On this platform each stateful replica would additionally get its own volume, so a second replica would serve a different, empty database behind the same endpoint.

The practical consequence is that **any restart is a full outage of about 85 seconds**, during which requests fail with `503`. The gap was measured three ways and landed in a tight band:

| Trigger                                        | Unavailability |
| ---------------------------------------------- | -------------- |
| `helm upgrade` that changes the container spec | 83 seconds     |
| Forced redeployment                            | 88 seconds     |
| Replica loss                                   | 87 seconds     |

Volume detach and reattach dominates that time, which is why an unplanned replica loss costs the same as a planned restart.

<Note>
  **Data is never at risk during a restart.** The same volume reattaches to the new replica — a marker record written before a replica was stopped was readable immediately after it came back, and encrypted settings still decrypted correctly. The difference between a single instance and a highly available one here is minutes of downtime, not data loss.
</Note>

## Backing Up

Backups are **scheduled volume snapshots** managed by the platform — no cloud account or bucket is required. On the configured cron schedule the platform takes a crash-consistent snapshot of the data volume; SQLite recovers cleanly from one via its write-ahead log. Snapshots are pruned automatically after `backup.retention`, and a final snapshot is always taken when the release is uninstalled.

* **Default schedule** — Daily at 03:00 UTC (`backup.schedule: "0 3 * * *"`), kept for 7 days (`backup.retention: 7d`).
* **Minimum interval** — The platform does not accept schedules more frequent than hourly.
* **Coverage** — Everything under `/pb_data`: the SQLite database, uploaded files, and any backup archives PocketBase itself wrote locally.

List the snapshots for the release's volume set:

```bash theme={null}
cpln volumeset snapshot get {release}-pocketbase-data --gvc {gvc} -o yaml
```

<Note>
  Snapshots live in the platform storage layer alongside the volume, not off-site. They protect against data corruption and accidental changes, but losing the whole GVC would lose them too. For an off-platform copy, configure PocketBase's own S3 backups under **Settings → Backups** in the dashboard — that is a database setting this template deliberately does not configure, so it is yours to set up.
</Note>

## Restoring a Backup

Restore is **in-place** on the release's own volume set: the platform provisions a fresh volume from the chosen snapshot and swaps it in, then the workload restarts to remount it.

<Warning>
  Restoring reverts the volume to the exact snapshot state — any records, uploads, or settings changed after that snapshot are lost — and restarts the single-replica workload, so PocketBase is unavailable during the swap.
</Warning>

<Steps>
  <Step title="Find the snapshot to restore">
    List snapshots and note the `name`, `location`, and `volumeIndex` of the one you want:

    ```bash theme={null}
    cpln volumeset snapshot get {release}-pocketbase-data --gvc {gvc} -o yaml
    ```
  </Step>

  <Step title="Restore it in place">
    Provision a fresh volume from the snapshot and swap it in. The workload restarts to remount it:

    ```bash theme={null}
    cpln volumeset snapshot restore {release}-pocketbase-data \
      --snapshot-name <snapshot-name> \
      --location <location> \
      --volume-index 0 \
      --gvc {gvc}
    ```
  </Step>

  <Step title="Verify">
    Once the workload is ready again, sign in to `/_/` and confirm your collections, records, and uploaded files are present.
  </Step>
</Steps>

## Important Notes

* **Create the credentials secret before installing.** `credentials.secretName` must name an existing dictionary secret holding `email`, `password`, and `encryptionKey`. Without it the deployment wedges **silently**: `cpln logs` returns zero lines and the only diagnostic is `status.versions[].message` from `cpln workload get-deployments`. Recovery after creating the secret took 10 minutes 11 seconds unaided, or force a redeployment to skip the wait.
* **Never change `encryptionKey` after install.** It encrypts the SMTP password, OAuth2 client secrets, and S3 backup credentials stored inside the database. Changing it orphans all of them with no way back. Keep a copy outside Control Plane.
* **The superuser password is re-applied from the secret on every start.** Changing it in the dashboard is reverted at the next restart — change it in the secret instead, and note that doing so changes your login.
* **Rotating the secret needs a forced redeployment.** The workload does not pick up a changed secret on its own: eleven minutes after a rotation the old password still worked and the workload reported `ready: true` throughout. See [Rotating Credentials](#rotating-credentials).
* **Realtime subscriptions are cut at ten minutes.** `timeoutSeconds` is already at the platform maximum of 600, so this is a hard ceiling. No events are lost and the cut is a clean end-of-stream; the official SDKs reconnect automatically, but a hand-rolled client must handle it. See [Realtime Subscriptions](#realtime-subscriptions).
* **Single instance, no HA — and that is upstream's design.** PocketBase does not cluster, so there is no `replicas` knob and every restart is an outage of about 85 seconds. Data is not at risk; the same volume reattaches. See [Restarts and Availability](#restarts-and-availability).
* **Set the Application URL at first login.** Verification and password-reset emails build their links from **Settings → Application → Application URL**, which is a database setting the chart cannot write. Until you set it, those links point at the wrong host.
* **Uploads and local backup archives share the volume with the database.** A file-heavy app needs more than the 10 GiB default; raise `volumeset.capacity` at install time, since capacity is fixed when the volume set is created.
* **Backups are platform volume snapshots, not off-site copies.** They live in the platform storage layer next to the volume. For an off-platform copy, configure PocketBase's own S3 backups in the dashboard.
* **Access-knob changes take time to propagate.** Flipping `publicAccess.enabled` took 126 seconds to be enforced. Re-poll rather than trusting the first response.
* **This is not an official image.** No official PocketBase image exists, so the template pins a community build and overrides its entrypoint. The `PB_ADMIN_EMAIL` and `PB_ADMIN_PASSWORD` variables from that image's documentation do nothing here — PocketBase core reads no environment variables. An `image` override must run as root with `pocketbase` on `PATH`.
* **Data survives restarts and upgrades; uninstalling deletes it.** The volume set goes with the release, though a final snapshot is taken on uninstall. 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, rules, and the API
  </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="Client SDKs" icon="plug" href="https://pocketbase.io/docs/client-side-integration/">
    Official JavaScript and Dart SDKs, which reconnect automatically
  </Card>

  <Card title="Going to Production" icon="server" href="https://pocketbase.io/docs/going-to-production/">
    Upstream guidance on running PocketBase for real
  </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>
