> ## Documentation Index
> Fetch the complete documentation index at: https://controlplanecorporation-majid-docs-content-expansion.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Unleash

> Deploy Unleash on Control Plane using the Template Catalog. Open-source feature-flag server with an admin UI and SDK APIs, backed by a highly available PostgreSQL cluster. Covers the prerequisite admin secret, replicas, first-boot API token seeding, database modes, and backups.

## Overview

Unleash is an open-source feature-flag server — toggle features on and off, roll them out gradually, and run A/B tests without redeploying your applications. This template deploys the free open-source edition backed by a highly available PostgreSQL cluster by default. The admin UI and the Admin, Client, and Frontend APIs are served on one public HTTPS endpoint, and the server is stateless — every flag, user, and token lives in PostgreSQL — so it scales to multiple replicas with a single value.

### Architecture

* **Unleash** — A standard workload (default 1 replica, `replicas` knob for more) serving the admin UI and all APIs on port `4242`. Stateless by design: all state lives in the PostgreSQL database, so Unleash itself has no volume set. The public URL (`UNLEASH_URL`) is derived from the canonical endpoint at start.
* **PostgreSQL (HA, default)** — The [postgres-highly-available](/template-catalog/templates/postgres-highly-available) template as a subchart: 3× Patroni PostgreSQL, 3× etcd, and a HAProxy leader-routing endpoint Unleash connects through.
* **PostgreSQL (dev/lightweight, optional)** — The single-instance [postgres](/template-catalog/templates/postgres) template instead, for lighter non-HA deployments.

### What Gets Created

* **Standard Unleash Workload** — One or more stateless replicas serving the admin UI and the Admin, Client, and Frontend APIs on port `4242`.
* **Database Workloads** — HA mode: a stateful Patroni PostgreSQL workload, a stateful etcd workload, and a standard HAProxy leader-routing workload. Single mode: one stateful PostgreSQL workload.
* **Volume Sets** — The database subchart's persistent volumes (10 GiB per replica by default). Unleash itself has none.
* **Secrets** — The start script that derives the public URL and waits for the database at startup, plus the database credentials from the subchart. The admin login is **not** created by this template: you create that dictionary secret yourself before installing and reference it by name (see [Prerequisites](#prerequisites)).
* **Identity & Policy** — A least-privilege policy granting the Unleash identity `reveal` on exactly the secrets it uses, including your pre-created API-token secret when one is configured.
* **Cron Backup Workload** *(optional)* — When database backups are enabled.

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

## Prerequisites

**One dictionary secret must exist before you install.** The initial admin guards a login form on the public endpoint, so its credentials are a prerequisite secret rather than template values — a value would sit in plaintext in the Helm release for the life of the install. The template creates no admin secret of its own.

<Steps>
  <Step title="Create the admin credentials secret">
    A [dictionary secret](/guides/create-secret/dictionary) holding exactly the keys `username` and `password`. Secrets are org-level, so no GVC flag is involved:

    ```bash theme={null}
    cpln secret create-dictionary --name my-unleash-admin \
      --entry username=admin \
      --entry password="$(openssl rand -hex 24)"
    ```

    Set `admin.secretName` to the name you used.
  </Step>

  <Step title="Read the password back later">
    `-o yaml` is required; without it the command prints the secret's metadata table rather than its contents:

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

| Key        | What it is                                                        |
| ---------- | ----------------------------------------------------------------- |
| `username` | The initial admin's login name.                                   |
| `password` | Its password. The login form it guards is on the public endpoint. |

<Warning>
  **A missing prerequisite secret wedges the install rather than failing it.** The install still exits 0 and reports success, every resource is created, and the workload then never starts. Because the container never ran, `cpln logs` returns **zero lines**, which reads as a broken platform rather than a missing prerequisite.

  The only diagnostic is `status.versions[].message`, which names the missing secret:

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

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

  It is **`get-deployments`** — plain `cpln workload get` has no `versions` key at all. Creating the missing secret clears the wedge on its own, but slowly: recovery has measured between 5.5 and 10.5 minutes across the catalog, so poll rather than giving up. `cpln workload force-redeployment RELEASE_NAME-unleash --gvc GVC_NAME` shortcuts it to roughly 90 seconds.
</Warning>

Optionally, the template can also seed a backend and a frontend SDK API token on first boot from a second [dictionary secret](/guides/create-secret/dictionary) that you create **before** installing. This one is unchanged from earlier versions, and the token strings are never passed through values.

<Steps>
  <Step title="Generate token strings">
    An Unleash token has the format `<project>:<environment>.<secret>`. The open-source edition ships the `default` project and the `development` and `production` environments. For example:

    ```bash theme={null}
    echo "default:production.$(openssl rand -hex 24)"
    ```

    Generate one token string for backend SDKs and one for frontend SDKs.
  </Step>

  <Step title="Create a dictionary secret">
    Create a [dictionary secret](/guides/create-secret/dictionary) with exactly two keys, `backend` and `frontend`, each holding a full token string:

    ```bash theme={null}
    cpln secret create-dictionary --name my-unleash-api-tokens \
      --entry backend="default:production.$(openssl rand -hex 24)" \
      --entry frontend="default:production.$(openssl rand -hex 24)"
    ```
  </Step>

  <Step title="Reference it in values">
    Set `apiTokens.secretName` to the secret's name. Leave it empty to skip seeding and create tokens in the admin UI instead.
  </Step>
</Steps>

For optional database backups, you also need a bucket and access setup for one of the supported providers — see [Backing Up](#backing-up).

Once the admin 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>

## Choosing a Database Mode

Exactly one of the two database modes must be enabled — the chart enforces this at render and fails the install with a clear message otherwise.

|                   | `postgresHA` (default)                                          | `postgres`                             |
| ----------------- | --------------------------------------------------------------- | -------------------------------------- |
| What runs         | 3× Patroni PostgreSQL, 3× etcd, HAProxy leader endpoint         | One single-replica PostgreSQL workload |
| Database failover | Automatic (Patroni leader election)                             | None                                   |
| Footprint         | 8 replicas across 3 workloads (3× Patroni, 3× etcd, 2× HAProxy) | 1 workload                             |
| Best for          | Production                                                      | Development and lightweight installs   |

A fresh HA-mode install takes several minutes to fully converge while the Patroni and etcd replicas come up; single mode is ready in about a minute.

<Note>
  **Unleash waits for its database before starting.** Version `1.1.0` added that wait to the start script. Earlier versions ran their schema migrations immediately at boot and did not retry, so on the default HA database they raced Patroni's leader election and crash-looped for roughly six minutes with `Failed to migrate db Error: read ECONNRESET` — long enough that the install looked broken and, in one measured run, missed a five-minute readiness expectation. The database itself was healthy throughout; only the startup ordering was wrong.
</Note>

## Configuration

The default `values.yaml` for this template:

```yaml theme={null}
image: unleashorg/unleash-server:8.0.3

resources:
  cpu: 1000m
  memory: 1Gi
  minCpu: 250m
  minMemory: 512Mi

replicas: 1 # stateless server — set 2+ for high availability (all state lives in PostgreSQL)

# Initial admin login, seeded on first boot only.
# REQUIRED PREREQUISITE SECRET — CREATE IT BEFORE YOU INSTALL. A `dictionary`
# secret holding exactly two keys, `username` and `password`; the login form it
# guards sits on the public endpoint. The account then lives in the database —
# change it later in the UI, not here.
admin:
  secretName: my-unleash-admin # name of your pre-created dictionary secret

apiTokens:
  secretName: "" # your pre-created dictionary secret (see Prerequisites); empty = create tokens in the UI

publicAccess:
  enabled: true # admin UI + SDK APIs on the canonical *.cpln.app HTTPS endpoint

internalAccess: # internal firewall scope (in-GVC SDK callers)
  type: same-gvc # options: none, same-gvc, same-org, workload-list
  workloads: [] # used with workload-list, e.g. //gvc/GVC_NAME/workload/WORKLOAD_NAME

postgresHA: # default: highly available PostgreSQL
  enabled: true
  postgres:
    username: unleash
    password: change-me-unleash-db-password # change before installing
    database: unleash
  replicas: 3
  volumeset:
    capacity: 10 # initial capacity in GiB per replica (minimum is 10)
  backup:
    enabled: false # optional — see Backing Up
    mode: logical # logical or wal-g
    resources:
      cpu: 100m
      memory: 128Mi
    logical:
      image: ghcr.io/controlplane-com/backup-images/postgres-backup:17.1.0
      schedule: "0 2 * * *"
    walg:
      intervalSeconds: 21600
    provider: aws # options: aws, gcp, minio
    aws:
      bucket: unleash-pg-backup-bucket
      region: us-east-1
      cloudAccountName: my-s3-cloud-account
      policyName: unleash-pg-backup-policy
      prefix: postgres/backups
    gcp:
      bucket: unleash-pg-backup-bucket
      cloudAccountName: my-gcs-cloud-account
      prefix: postgres/backups
    minio:
      endpoint: http://my-minio-workload:9000
      bucket: unleash-pg-backup-bucket
      accessKey: my-minio-username
      secretKey: my-minio-password
      prefix: postgres/backups

postgres: # dev/lightweight: single-instance PostgreSQL (disable postgresHA first)
  enabled: false
  credentials: # the chart writes these into the secret named below — nothing for you to create
    username: unleash
    password: change-me-unleash-db-password # change before installing
    database: unleash
  config:
    credentialsSecretName: my-unleash-db-credentials # secret names are org-wide — give each release its own
  volumeset:
    capacity: 10 # initial capacity in GiB (minimum is 10)
  backup:
    enabled: false # optional — see Backing Up
    image: ghcr.io/controlplane-com/backup-images/postgres-backup:18.1.0
    schedule: "0 2 * * *"
    resources:
      cpu: 100m
      memory: 128Mi
    provider: aws # options: aws, gcp, minio
    aws:
      bucket: unleash-pg-backup-bucket
      region: us-east-1
      cloudAccountName: my-s3-cloud-account
      policyName: unleash-pg-backup-policy
      prefix: postgres/backups
    gcp:
      bucket: unleash-pg-backup-bucket
      cloudAccountName: my-gcs-cloud-account
      prefix: postgres/backups
    minio:
      endpoint: http://my-minio-workload:9000
      bucket: unleash-pg-backup-bucket
      credentialsSecretName: my-unleash-minio-credentials # dictionary secret holding accessKey + secretKey
      prefix: postgres/backups
```

### Unleash Instance

* `image` — The Unleash open-source server image.
* `resources` — CPU and memory for the Unleash container.
* `replicas` — Number of Unleash replicas behind the platform load balancer. The server is stateless, so scaling is a single value change; `2+` is recommended for production. At 2 replicas, rolling redeploys and a killed replica were both verified to serve every request with zero failures.
* `admin.secretName` — Name of the dictionary secret you created in [Prerequisites](#prerequisites), holding the `username` and `password` of the initial admin. The credentials never pass through values, and the template creates no secret of its own — it references yours and grants the workload `reveal` on exactly that one secret. The account is seeded into the database on first boot only, so editing the secret later does not modify it; change the password in the admin UI instead.
* `apiTokens.secretName` — Name of your pre-created dictionary secret with keys `backend` and `frontend` (see [Prerequisites](#prerequisites)). When set, both tokens are seeded on first boot; when empty (default), create tokens in the admin UI after install. Like the admin account, tokens are seeded on first boot only.

### Access

* `publicAccess.enabled` — Serve the admin UI and SDK APIs on the canonical `*.cpln.app` HTTPS endpoint. It is **deliberately on by default** and load-bearing: SDKs running in browsers and in applications outside the GVC call `/api/frontend` and `/api/client` directly, so a private instance cannot serve them. Every surface is authenticated — the UI and Admin API by login, the Client and Frontend APIs by tokens — and the login is now a credential you created rather than a published default. A firewall change takes roughly 30 seconds to a few minutes to propagate, so re-test rather than trusting the first response. Set to `false` for an internal-only instance (external requests are blocked at the edge; in-GVC callers still reach it per `internalAccess`). The public URL Unleash embeds in password-reset and invite links (`UNLEASH_URL`) is derived at startup — from the canonical endpoint when public, or the internal `cpln.local` address when not — and re-derived on every restart.
* `internalAccess.type` — Internal firewall scope of the Unleash workload:

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

### Database

Enable exactly one of `postgresHA` (production, default) or `postgres` (dev/lightweight) — see [Choosing a Database Mode](#choosing-a-database-mode). In both modes, **change the database password before installing** (`postgres.credentials.password`). Unleash is wired to the active database automatically — the HAProxy leader endpoint in HA mode, or the single instance directly in dev mode.

If you run more than one release of this template in the same organization, give each its own `postgres.config.credentialsSecretName`. Secret names are organization-wide, so a second release left on the default name is **refused at install** and creates nothing — the first release is unaffected.

<Warning>
  **Template version `1.0.0` did not compact the etcd cluster inside the bundled highly available database**, so etcd's backend grows with time alone and goes read-only once it reaches its 2 GiB quota — after roughly 110 days — taking PostgreSQL failover with it. Only installs running the HA database are affected (`postgresHA.enabled`, the default here); see [etcd History Compaction](/template-catalog/templates/postgres-highly-available#etcd-history-compaction) for the mechanism and the symptoms. Upgrade to `1.0.1` or later to turn compaction on: that stops further growth but cannot shrink a backend that has already grown, and a cluster that has already raised a `NOSPACE` alarm needs operator recovery rather than an upgrade.
</Warning>

## Upgrading From 1.0.x

Version `1.1.0` moved the initial admin login out of Helm values. Earlier versions shipped a username and a working password as values, used exactly as written — a published default guarding a login form on the public endpoint, sitting in the Helm release for the life of the install.

|                            | `1.0.x`                                      | `1.1.0`                                                                                |
| -------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------- |
| Initial admin login        | `admin.username` and `admin.password` values | `admin.secretName` — a dictionary secret you create, holding `username` and `password` |
| Chart-created admin secret | Held the admin credentials                   | None; the template creates no admin secret at all                                      |
| `apiTokens.secretName`     | Prerequisite dictionary secret               | Unchanged                                                                              |
| Database startup           | Migrations ran immediately at boot           | The start script waits for the database first                                          |
| Database password          | A value (`postgres.credentials.password`)    | Unchanged — still a value, because it is bundled plumbing nobody logs in with          |

<Warning>
  **An upgrade that still carries either removed key is rejected at render, before anything is applied.** Each guard names its replacement, and there is no compatibility fallback — the version bump is the migration path. A real upgrade carrying an old key failed at render, created no Helm revision, and left the running release healthy and untouched:

  ```text theme={null}
  unleash: admin.password was REMOVED in 1.1.0. Put it in a `dictionary` secret (key: `password`)
  together with `username`, and set admin.secretName to that secret's name. Create the secret BEFORE
  installing; see Prerequisites in the README.
  ```

  Leaving `admin.secretName` empty is refused the same way.
</Warning>

<Note>
  **An existing install's admin password does not change on upgrade.** `UNLEASH_DEFAULT_ADMIN_*` is read on first boot only; the account then lives in the database, so the secret's contents only matter to a fresh install. Change the password in the admin UI. If the install is still carrying the published `1.0.x` default (`change-me-unleash-admin`), treat that password as compromised and change it now.
</Note>

To upgrade an existing install:

<Steps>
  <Step title="Create the admin credentials secret">
    Follow [Prerequisites](#prerequisites). An existing `apiTokens.secretName` secret stays exactly as it is.
  </Step>

  <Step title="Drop the removed keys from your values">
    Remove `admin.username` and `admin.password`, and set `admin.secretName` instead.
  </Step>

  <Step title="Upgrade">
    The Unleash replicas restart; flags, users, and tokens are in PostgreSQL and are untouched.
  </Step>
</Steps>

## Connecting

| What                               | Value                                                                                                                  |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Admin UI / Admin API (public)      | `https://<canonical>.cpln.app` — `status.canonicalEndpoint` of `{release}-unleash`                                     |
| Client API (backend SDKs)          | `https://<canonical>.cpln.app/api/client` — `Authorization: <backend token>`                                           |
| Frontend API (browser/mobile SDKs) | `https://<canonical>.cpln.app/api/frontend` — `Authorization: <frontend token>`                                        |
| Internal (same GVC)                | `http://{release}-unleash.{gvc}.cpln.local:4242`                                                                       |
| Login                              | The `username` / `password` in your `admin.secretName` secret — `cpln secret reveal my-unleash-admin -o yaml`          |
| PostgreSQL (internal, HA mode)     | `{release}-postgres-ha-proxy.{gvc}.cpln.local:5432`, credentials in the `{release}-postgres-config` secret             |
| PostgreSQL (internal, single mode) | `{release}-postgres.{gvc}.cpln.local:5432`, credentials in the secret named by `postgres.config.credentialsSecretName` |

### API Tokens

SDKs authenticate with an API token in the `Authorization` header. The two token types are not interchangeable:

* **Backend tokens** authenticate server-side SDKs against `/api/client`. Treat them like passwords — they must stay secret.
* **Frontend tokens** authenticate browser and mobile SDKs against `/api/frontend`. They are designed to be publishable and safe to embed in client code.

A `401` or `403` from an SDK API usually means the wrong token type for the endpoint, or the wrong environment in the token string. Tokens come either from your prerequisite secret (seeded on first boot) or from the admin UI (**Admin → API access**) at any time.

## Backing Up

Database backups are optional and disabled by default. They cover the PostgreSQL database — every flag, strategy, user, and token in your Unleash instance. Enable them with `postgresHA.backup.enabled` or `postgres.backup.enabled` (matching your database mode), and complete the storage setup for your provider **before** installing. The values below are shown under `backup.*` — set them within the enabled database block.

<Tabs>
  <Tab title="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) for your AWS account. Set `backup.aws.cloudAccountName` to its name.
      </Step>

      <Step title="Create a bucket-scoped IAM policy">
        Create an AWS IAM policy with the JSON below (replace `YOUR_BUCKET`), then set `backup.aws.policyName` to the policy's name:

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

  <Tab title="Google Cloud Storage">
    <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) for your GCP project. Set `backup.gcp.cloudAccountName` to its name — access is keyless (no stored credentials).
      </Step>

      <Step title="Grant the Storage Admin role">
        Grant the **Storage Admin** role to the GCP service account created for the Cloud Account (`roles/storage.objectAdmin` scoped to the bucket also works).
      </Step>
    </Steps>
  </Tab>

  <Tab title="S3-compatible (MinIO, R2, Wasabi)">
    <Steps>
      <Step title="Create a bucket">
        Create your bucket on the server. Set `backup.minio.bucket` to its name.
      </Step>

      <Step title="Set the endpoint">
        Set `backup.minio.endpoint` to the S3 API address including port. For the `minio` marketplace template deployed in the same GVC, this is `http://WORKLOAD_NAME:9000`.
      </Step>

      <Step title="Set credentials">
        The two backing stores take these differently. In HA mode (`postgresHA`), set `backup.minio.accessKey` and `backup.minio.secretKey` to credentials with access to the bucket. In single-instance mode (`postgres`), those two values were removed: create a [dictionary secret](/guides/create-secret/dictionary) holding the keys `accessKey` and `secretKey`, and set `backup.minio.credentialsSecretName` to its name — see [MinIO backup prerequisites](/template-catalog/templates/postgres#minio) for the exact command.
      </Step>
    </Steps>
  </Tab>
</Tabs>

In HA mode, `backup.mode` selects `logical` (scheduled `pg_dump` via a cron workload) or `wal-g` (continuous WAL archiving). The single-instance mode takes scheduled logical dumps.

## Important Notes

* **Create the admin secret before installing** — a missing prerequisite secret leaves the workload waiting on something that does not exist, with zero log lines. See [Prerequisites](#prerequisites) for how to diagnose it.
* **Change the database password before installing** (`postgres.credentials.password`) — it is bundled plumbing, used exactly as given, and it remains a value by design.
* **Admin credentials and API tokens are seeded on first boot only** — they live in the database afterwards; change the password or manage tokens in the admin UI, not by editing the secrets.
* **Backend tokens must stay secret** (server-side SDKs, `/api/client`); frontend tokens are safe to embed in browsers (`/api/frontend`). A `401` usually means the wrong token type or environment.
* **The free edition ships exactly two environments** (`development`, `production`) — SSO, role-based access control, multiple projects, change requests, and audit logs require an Unleash Enterprise license and are not available in this template.
* **HA-mode first boot takes several minutes** while the database cluster converges. Since `1.1.0` the start script waits for the database before running migrations, which is what earlier versions crash-looped on.
* **Uninstall deletes the database volume sets** — all flags, users, and tokens. Enable backups if the data matters.

## External References

<CardGroup cols={2}>
  <Card title="Unleash Documentation" icon="book" href="https://docs.getunleash.io/">
    Official Unleash documentation
  </Card>

  <Card title="Configuring Unleash" icon="gear" href="https://docs.getunleash.io/deploy/configuring-unleash">
    Environment variable configuration reference
  </Card>

  <Card title="API Tokens and Client Keys" icon="key" href="https://docs.getunleash.io/concepts/api-tokens-and-client-keys">
    Token types, formats, and how SDKs authenticate
  </Card>

  <Card title="SDK Overview" icon="code" href="https://docs.getunleash.io/sdks">
    Official backend and frontend SDKs
  </Card>

  <Card title="Scaling Unleash" icon="server" href="https://docs.getunleash.io/guides/scaling-unleash">
    Upstream guidance on horizontal scaling
  </Card>

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