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

# Metabase

> Deploy Metabase on Control Plane using the Template Catalog. Open-source BI with dashboards, a SQL editor, and scheduled reports, backed by a highly available PostgreSQL app database. Covers the prerequisite admin and encryption-key secrets, database modes, first-boot admin setup, and backups.

## Overview

Metabase is an open-source business intelligence platform — dashboards, a SQL editor, and scheduled report subscriptions. This template deploys the free open-source edition backed by a highly available PostgreSQL cluster by default. The bundled PostgreSQL is Metabase's own **app database** (users, dashboards, saved connections); the databases you analyze are **data sources** you connect in the app after install — they are never installed or touched by this template. The admin account is created automatically on first boot, and the workload only starts receiving traffic once setup is complete, so there is never a publicly reachable setup page.

### Architecture

* **Metabase** — A single-replica standard workload serving the UI and API on port `3000`. Stateless by design: all application state (questions, dashboards, users, saved connections) lives in the PostgreSQL app database, so Metabase itself has no volume set.
* **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 Metabase 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 Metabase Workload** — A single stateless replica serving the UI and API on port `3000`.
* **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). Metabase itself has none.
* **Secrets** — The start script that creates the admin account on first boot, plus the database credentials from the subchart. The admin login and the encryption key are **not** created by this template: you create both secrets yourself before installing and reference them by name (see [Prerequisites](#prerequisites)).
* **Identity & Policy** — A least-privilege policy granting the Metabase identity `reveal` on exactly the secrets it uses, including your pre-created encryption-key secret.
* **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

**Two secrets must exist before you install.** Both hold credentials or long-lived key material, so neither passes through Helm values — a value would sit in plaintext in the release for the life of the install, and the admin login is on a public endpoint. The template creates neither of them.

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

    ```bash theme={null}
    cpln secret create-dictionary --name my-metabase-admin \
      --entry email=admin@example.com \
      --entry password="$(openssl rand -hex 24)"
    ```

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

  <Step title="Create the encryption-key secret">
    An [opaque secret](/guides/create-secret/opaque) with encoding `plain` whose entire payload is a random string of at least 16 characters:

    ```bash theme={null}
    printf '%s' "$(openssl rand -hex 24)" | cpln secret create-opaque --name my-metabase-encryption-key --encoding plain -f -
    ```

    Set `encryptionKey.secretName` to the name you used, and store a copy of the key somewhere safe outside Control Plane.
  </Step>

  <Step title="Read either secret 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-metabase-admin -o yaml
    ```
  </Step>
</Steps>

| Key in the admin secret | What it is                                                                                                                                    |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`                 | The admin account's login email.                                                                                                              |
| `password`              | Its password. It must pass Metabase's own check — letters **and** digits, 8+ characters (the `openssl rand -hex` value above satisfies this). |

<Warning>
  **Neither the email nor the password may contain a double quote (`"`) or a backslash (`\`).** Both values are embedded in the JSON body of the first-boot setup call, and either character breaks it. The container checks this at startup and refuses to run setup instead of sending a malformed request — measured firing **32 seconds** after install, with a log line that names the offending secret:

  ```text theme={null}
  cpln-bootstrap: ERROR: the email and password in the my-metabase-admin secret must not contain a
  double quote or a backslash — they are embedded in the first-boot setup API JSON body
  ```

  The workload then stays unready and its public endpoint answers `503`, so a bad credential never results in a reachable, half-configured instance. Fix the secret's contents and redeploy.
</Warning>

<Warning>
  Losing or changing the encryption key means re-entering every saved database connection — Metabase can no longer decrypt them. Rotation is only possible offline, using Metabase's `rotate-encryption-key` command. Back the key up before installing.
</Warning>

<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-metabase --gvc GVC_NAME -o yaml
  ```

  ```text theme={null}
  The secret my-metabase-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-metabase --gvc GVC_NAME` shortcuts it to roughly 90 seconds.
</Warning>

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

Once both secrets exist, 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 roughly 10–15 minutes to fully converge; single mode is ready in about 2 minutes. Metabase itself reached ready **261 seconds** after a default HA install.

<Note>
  For the first few minutes of an HA-mode install, the Metabase container may log Java stack traces (`An attempt by a client to checkout a Connection has timed out`) and restart while the Patroni cluster elects a leader. It resolves itself once the database is routable — the traces are not a failed install.
</Note>

## Configuration

The default `values.yaml` for this template:

```yaml theme={null}
image: metabase/metabase:v0.63.1.3

resources:
  cpu: 1000m
  memory: 2Gi
  minCpu: 500m
  minMemory: 1Gi

encryptionKey:
  secretName: my-metabase-encryption-key # name of your pre-created opaque secret

# Admin account, created automatically on first boot (no public /setup window).
# REQUIRED PREREQUISITE SECRET — CREATE IT BEFORE YOU INSTALL. A `dictionary`
# secret holding exactly two keys, `email` and `password`; the login form it
# guards sits on the public endpoint. The password must satisfy Metabase's own
# check (letters + digits, 8+ chars), and neither value may contain a double
# quote or a backslash — both are embedded in the first-boot setup API call.
admin:
  secretName: my-metabase-admin # name of your pre-created dictionary secret
  firstName: Metabase
  lastName: Admin

siteName: Metabase # instance name shown in the UI and emails

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

internalAccess: # internal firewall scope (in-GVC API 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 app database
  enabled: true
  postgres:
    username: metabase
    password: change-me-metabase-db-password # change before installing
    database: metabase
  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: metabase-pg-backup-bucket
      region: us-east-1
      cloudAccountName: my-s3-cloud-account
      policyName: metabase-pg-backup-policy
      prefix: postgres/backups
    gcp:
      bucket: metabase-pg-backup-bucket
      cloudAccountName: my-gcs-cloud-account
      prefix: postgres/backups
    minio:
      endpoint: http://my-minio-workload:9000
      bucket: metabase-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: metabase
    password: change-me-metabase-db-password # change before installing
    database: metabase
  config:
    credentialsSecretName: my-metabase-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: metabase-pg-backup-bucket
      region: us-east-1
      cloudAccountName: my-s3-cloud-account
      policyName: metabase-pg-backup-policy
      prefix: postgres/backups
    gcp:
      bucket: metabase-pg-backup-bucket
      cloudAccountName: my-gcs-cloud-account
      prefix: postgres/backups
    minio:
      endpoint: http://my-minio-workload:9000
      bucket: metabase-pg-backup-bucket
      credentialsSecretName: my-metabase-minio-credentials # dictionary secret holding accessKey + secretKey
      prefix: postgres/backups
```

### Metabase Instance

* `image` — The Metabase open-source container image.
* `resources` — CPU and memory for the Metabase container. The template caps the JVM at 75% of the container memory limit (`JAVA_OPTS: -XX:MaxRAMPercentage=75.0`), so raising `resources.memory` also raises the Java heap. The 2 GiB default is sized for the JVM — lowering it is not recommended.
* `encryptionKey.secretName` — Name of your pre-created opaque secret holding the key that encrypts saved data-source credentials. See [Prerequisites](#prerequisites).
* `admin.secretName` — Name of the dictionary secret you created in [Prerequisites](#prerequisites), holding the `email` and `password` of the admin account. The credentials never pass through values; the template references your secret and grants the workload `reveal` on exactly it.
* `admin.firstName` / `admin.lastName` — The admin's display name. These are ordinary values (not credentials) and, like the secret's contents, may not contain double quotes or backslashes — these two are checked at render.
* The account is created automatically on first boot against the local setup API, so there is no unauthenticated setup page at any point: the workload only becomes ready — and only starts receiving traffic — once setup is complete, and if setup fails it never becomes ready at all (fail-closed). Setup runs exactly once; editing the secret afterwards does not modify the existing account, so change the password in the Metabase UI instead.
* `siteName` — The instance name shown in the UI and in emails Metabase sends.

### Access

* `publicAccess.enabled` — Serve the UI and API on the canonical `*.cpln.app` HTTPS endpoint. It is **deliberately on by default**: Metabase is a browser tool people open directly, everything behind the endpoint is gated by its own login, and that login is now a credential you created rather than a published default. Set to `false` for an internal-only instance (external requests are blocked at the edge; in-GVC callers still reach it per `internalAccess`). A firewall change takes roughly 30 seconds to a few minutes to propagate, so re-test rather than trusting the first response.
* `internalAccess.type` — Internal firewall scope of the Metabase 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`. |

### App 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`). Metabase 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 admin login out of Helm values. Earlier versions shipped an email 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`                                                                             |
| ------------------------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------- |
| Admin login                                       | `admin.email` and `admin.password` values | `admin.secretName` — a dictionary secret you create, holding `email` and `password` |
| Chart-created admin secret                        | Held the admin credentials                | None; the template creates no admin secret at all                                   |
| `admin.firstName` / `admin.lastName` / `siteName` | Values                                    | Unchanged                                                                           |
| `encryptionKey.secretName`                        | Prerequisite opaque secret                | Unchanged                                                                           |
| 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}
  metabase: admin.password was REMOVED in 1.1.0. Put it in a `dictionary` secret (key: `password`)
  together with `email`, 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.** The account already lives in the app database and first-boot setup never runs again, so the secret's contents only matter to a fresh install. Change the password in the Metabase UI (account settings). If the install is still carrying the published `1.0.x` default (`change-me-metabase-1`), treat that password as compromised and change it now.
</Note>

To upgrade an existing install:

<Steps>
  <Step title="Create the admin login secret">
    Follow [Prerequisites](#prerequisites). Your existing encryption-key secret stays exactly as it is.
  </Step>

  <Step title="Drop the removed keys from your values">
    Remove `admin.email` and `admin.password`, and set `admin.secretName` instead. Leave `admin.firstName`, `admin.lastName`, and `siteName` alone.
  </Step>

  <Step title="Upgrade">
    The single Metabase replica restarts; questions, dashboards, and users are in the app database and are untouched.
  </Step>
</Steps>

## Connecting

| What                                 | Value                                                                                                                  |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| UI / API (public)                    | `https://<canonical>.cpln.app` — `status.canonicalEndpoint` of `{release}-metabase`                                    |
| Internal (same GVC)                  | `http://{release}-metabase.{gvc}.cpln.local:3000`                                                                      |
| Login                                | The `email` / `password` in your `admin.secretName` secret — `cpln secret reveal my-metabase-admin -o yaml`            |
| App database (internal, HA mode)     | `{release}-postgres-ha-proxy.{gvc}.cpln.local:5432`, credentials in the `{release}-postgres-config` secret             |
| App database (internal, single mode) | `{release}-postgres.{gvc}.cpln.local:5432`, credentials in the secret named by `postgres.config.credentialsSecretName` |

### Adding Data Sources

The databases you analyze are added inside Metabase after install (**Admin → Databases**) — the template never installs or touches them. To analyze a database running on Control Plane, use its internal endpoint as the host, e.g. `{workload}.{gvc}.cpln.local:5432`. Any database Metabase can reach — inside or outside Control Plane — works as a data source. Saved connection credentials are encrypted at rest with your encryption key.

## Backing Up

Database backups are optional and disabled by default. They cover the app database — the questions, dashboards, users, and saved connections that make up your Metabase 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:PutObject",
              "s3:DeleteObject",
              "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 — the backup identity is granted access to the bucket keylessly (no stored credentials).
      </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

* **Back up the encryption-key secret** — losing or changing it means re-entering every saved database connection; rotation is only possible offline via Metabase's `rotate-encryption-key` command.
* **Create both prerequisite secrets before installing** — a missing one 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.
* **A weak or malformed admin password keeps the workload unready by design** — Metabase's own check requires letters and digits, 8+ characters, and the container refuses to run setup if the email or password contains a double quote or a backslash. A failed bootstrap is fail-closed rather than exposing an open setup page.
* **Metabase is single-replica in this template** — the default HA PostgreSQL backend removes the database as a failure point.
* **Upgrades restart the single replica** — expect a few minutes of UI downtime per Helm upgrade; the HA app database keeps running and no data is lost.
* **Uninstall deletes the app-database volume sets** — all questions, dashboards, and users. Enable backups if the data matters.
* **This template ships the open-source image only** — Pro/Enterprise features (SSO, sandboxing, config-file init) are not available.

## External References

<CardGroup cols={2}>
  <Card title="Metabase Documentation" icon="book" href="https://www.metabase.com/docs/latest/">
    Official Metabase documentation
  </Card>

  <Card title="Environment Variables" icon="gear" href="https://www.metabase.com/docs/latest/configuring-metabase/environment-variables">
    Metabase environment variables reference
  </Card>

  <Card title="Encrypting Database Details" icon="lock" href="https://www.metabase.com/docs/latest/databases/encrypting-details-at-rest">
    How the encryption key protects saved connection credentials
  </Card>

  <Card title="Metabase in Production" icon="server" href="https://www.metabase.com/learn/metabase-basics/administration/administration-and-operation/metabase-in-production">
    Upstream guidance on running Metabase in production
  </Card>

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