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

# Umami

> Deploy Umami on Control Plane using the Template Catalog. Privacy-first, cookieless web and product analytics — a self-hosted Google Analytics alternative — backed by a single-instance or highly available PostgreSQL store. Covers the private-by-default first-run sequence, tracking, and scaling.

## Overview

Umami is a privacy-first, cookieless web and product analytics platform — a self-hosted, MIT-licensed alternative to Google Analytics. This template deploys the stateless Umami v3 app tier backed by PostgreSQL, serving both the analytics dashboard and the tracking endpoint on the same port. You embed a small tracking script on your site; visitor events POST back to the same workload and are stored in PostgreSQL, with no cookies and no personal data collected.

<Warning>
  **A new install is private, and it stays private until you publish it deliberately.** Umami seeds a hardcoded `admin` / `umami` account from its first database migration and offers no environment variable to override it, so `publicAccess.enabled` defaults to `false`. Tracking collects nothing while it is off — browsers on the sites you track must reach the tracking script and the collect endpoint. Publishing is therefore a required second step, not an optional one: follow [First Run](#first-run-sequence) in order.
</Warning>

### Architecture

* **Umami** — A stateless `standard` workload serving the dashboard, API, and tracking/collect endpoint on port `3000`. Runs a single replica by default; set `replicas` to `2` or more for an always-on tier with zero-downtime rolling restarts. All state lives in PostgreSQL, so replicas are independent — no clustering.
* **PostgreSQL (single-instance, default)** — The [postgres](/template-catalog/templates/postgres) template as a subchart: the backing store for all users, websites, sessions, and events.
* **PostgreSQL (HA, optional)** — The [postgres-highly-available](/template-catalog/templates/postgres-highly-available) template instead: 3× Patroni PostgreSQL with automatic failover and an HAProxy leader endpoint, for a durable production store.

### What Gets Created

* **Standard Umami Workload** — One or more stateless replicas serving the UI, API, and tracking endpoint on port `3000`.
* **Database Workloads** — Single mode: one stateful PostgreSQL workload. HA mode: a stateful Patroni PostgreSQL workload, a stateful etcd workload, and a standard HAProxy leader-routing workload.
* **Volume Sets** — The database subchart's persistent volumes (10 GiB by default; per replica in HA mode). Umami itself has none.
* **Identity & Policy** — A least-privilege policy granting the Umami identity `reveal` on exactly two secrets: the app secret you create, and the active database's credential secret.
* **Cron Backup Workload** *(optional)* — When database backups are enabled.

The template creates **no secret of its own**. The app secret is a prerequisite [opaque secret](/guides/create-secret/opaque) you create and own; the database credentials are created by the database subchart.

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

## Prerequisites

**One [opaque secret](/guides/create-secret/opaque) must exist before you install.** It holds Umami's app secret, which signs auth tokens and sessions — anyone holding it can forge a login — so it is never a Helm value and never lands in the release.

<Steps>
  <Step title="Create the app secret">
    Generate a random value and store it as the secret's payload:

    ```bash theme={null}
    printf '%s' "$(openssl rand -base64 32)" | cpln secret create-opaque --name my-umami-app-secret --encoding plain -f -
    ```

    Set `app.appSecretName` to the name you used.
  </Step>

  <Step title="Keep it for the life of the install">
    The value must stay stable — changing it invalidates every issued token and logs every user out. In a multi-replica install all replicas read this one secret, which is what lets a session established against one replica be honored by another.
  </Step>
</Steps>

<Warning>
  **A missing app secret wedges the install rather than failing it.** `cpln helm install` still exits 0 and reports every resource created, but the Umami workload never starts — it sits at zero replicas. `cpln logs` shows **nothing at all**, because no container ever starts; the real reason appears only under `status.versions[].message` of `cpln workload get-deployments {release}-umami --gvc {gvc} -o yaml`, which names the missing secret: `The secret <name> no longer exists. Workload updates are paused until the secret is added or the reference to the secret removed.`

  Creating the secret afterwards **does** release it, without further action, but slowly — measured between 5.5 and 10.5 minutes across five templates, so poll rather than time-box it. To skip the wait, run `cpln workload force-redeployment {release}-umami --gvc {gvc}`.
</Warning>

Nothing else is required for a default install. Optional: a bucket and access setup for one of the supported providers if you enable database backups — see [Backing Up](#backing-up). Change the database password (`postgres.credentials.password` or `postgres.credentials.password`) before installing as well; it ships with a `change-me` placeholder default.

Once the 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 Run Sequence

Umami's bootstrap admin is a hardcoded `admin` / `umami`, seeded by the first database migration, and there is no environment variable to override it — the credentials are published in Umami's own documentation. A public default install would therefore stand on the internet with known credentials, so the install starts private and you publish it once the password is changed.

This is a sequence, not a lockdown: **the tracking endpoint is the product**, and collection does not work until public access is on.

<Steps>
  <Step title="Install with the default private access">
    Install with `publicAccess.enabled: false` (the default). The canonical endpoint refuses external requests, while in-GVC callers still reach the workload per `internalAccess.type`.
  </Step>

  <Step title="Reach the dashboard over a port forward">
    Forward the workload's port to your machine:

    ```bash theme={null}
    cpln port-forward {release}-umami 3000:3000 --gvc {gvc}
    ```

    Then open `http://localhost:3000/login` in a browser and sign in as `admin` / `umami`.

    <Note>
      `cpln port-forward` is a **top-level command**, not a subcommand of `cpln workload`.
    </Note>
  </Step>

  <Step title="Change the admin password">
    In the dashboard, go to **Settings → Profile** and change the password. The old password is rejected from that moment on.
  </Step>

  <Step title="Publish the dashboard and tracking endpoint">
    Set `publicAccess.enabled: true` and run a `helm upgrade`. The firewall change takes up to a couple of minutes to propagate — roughly 50 seconds in a measured run — so re-test the public URL rather than trusting the first response. Your changed password applies over the public endpoint immediately; the old `umami` password is rejected there too.
  </Step>
</Steps>

<Warning>
  **That publishing upgrade is the first upgrade after the install, so it bounces the bundled database.** The first `helm upgrade` after an install re-applies the bundled PostgreSQL even though no database value changed, and Umami is unreachable for roughly two minutes while it restarts. This is expected, not a failed upgrade — do not roll back.

  **`/api/heartbeat` is not the signal to watch during that window.** It returns `200` throughout, because it never touches the database, while the dashboard and all collection are still failing. Confirm recovery with a database-backed request — loading the dashboard — instead.
</Warning>

## Upgrading From 1.0.1

Version `1.1.0` is a security release that renames three values. Carrying a `1.0.1` values file forward **fails at render** with a message naming the replacement, so nothing silently reverts to a default:

| `1.0.1`            | `1.1.0`               | Why                                                                                                                                                                                                 |
| ------------------ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `app.appSecret`    | `app.appSecretName`   | The app secret is now a prerequisite opaque secret you create, referenced by name. `1.0.1` shipped a working signing key as a values default, so every install shared one publicly-known credential |
| `resources.cpu`    | `resources.maxCpu`    | The block exposes both a reservation and a limit, so the limit is named `maxCpu` beside `minCpu`                                                                                                    |
| `resources.memory` | `resources.maxMemory` | Same — `maxMemory` beside `minMemory`                                                                                                                                                               |

Two further changes need no action but are worth knowing:

* **`publicAccess.enabled` now defaults to `false`.** An upgrade that does not set it explicitly makes the install private and **stops data collection**. Set it to `true` explicitly once the admin password is changed — see [First Run](#first-run-sequence).
* **The chart-created `{release}-umami-config` secret is gone.** It existed only to hold the app secret; the template now creates no secret of its own.

<Note>
  The render guards are non-destructive against a running release. Tripping one exits non-zero without creating a new revision, and the workload keeps serving at its current version — you lose nothing by hitting it mid-upgrade.
</Note>

## Choosing a Database Mode

Exactly one of the two backing stores must be enabled — the chart enforces this at render. Umami is wired to the active database automatically.

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

To switch to HA mode, set `postgres.enabled: false` and `postgresHA.enabled: true`.

## Configuration

Key configuration values (see the template's `values.yaml` for the complete set):

```yaml theme={null}
image: ghcr.io/umami-software/umami:3.2.0

replicas: 1 # 1 = proven single-instance; 2+ = always-on, zero-downtime restarts

resources: # per replica
  minCpu: 100m
  maxCpu: 500m
  minMemory: 256Mi
  maxMemory: 512Mi

app:
  # Name of an opaque secret (encoding: plain) holding the app secret, which signs
  # auth tokens and sessions. The secret MUST EXIST BEFORE INSTALL.
  appSecretName: my-umami-app-secret
  disableTelemetry: true # opt out of Umami's anonymous usage telemetry

tracker:
  scriptName: ""      # custom tracker script path, e.g. "s.js" (dodges ad blockers); "" = default /script.js
  collectEndpoint: "" # custom collect API path, e.g. "/api/track"; "" = default /api/send

postgres: # default: single-instance PostgreSQL
  enabled: true
  image: postgres:18
  credentials: # the chart writes these into the secret named below — nothing for you to create
    username: umami
    password: change-me-umami-db # change before installing
    database: umami
  config:
    credentialsSecretName: my-umami-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 # true = scheduled backups of the analytics DB to object storage
    schedule: "0 2 * * *" # daily at 2am UTC
    provider: aws # aws, gcp, or minio
    aws:
      bucket: my-backup-bucket
      region: us-east-1
      cloudAccountName: my-backup-cloudaccount
      policyName: my-backup-policy
      prefix: umami/backups

postgresHA: # durable HA: 3-replica Patroni store with an HAProxy leader endpoint (disable postgres first)
  enabled: false
  postgres:
    username: umami
    password: change-me-umami-db # change before installing
    database: umami
  replicas: 3
  volumeset:
    capacity: 10 # initial capacity in GiB per replica (minimum is 10)
  backup:
    enabled: false # true = scheduled backups to object storage
    mode: logical # logical or wal-g
    provider: aws # aws, gcp, or minio
    aws:
      bucket: my-backup-bucket
      region: us-east-1
      cloudAccountName: my-backup-cloudaccount
      policyName: my-backup-policy
      prefix: umami/backups

publicAccess:
  # false = nothing reaches Umami from the internet. Change the hardcoded admin
  # password over a port forward first, then set this true — tracking needs it.
  enabled: false

internalAccess:
  type: same-gvc # options: none, same-gvc, same-org, workload-list
  workloads: []  # only used when type is same-gvc or workload-list
```

### Application

* `image` — The Umami open-source container image.
* `replicas` — Number of stateless app-tier replicas. `1` is the proven single-instance shape; `2` or more gives an always-on tier where rolling restarts cycle one replica at a time with no downtime. Replicas are independent and share only the database and the app secret.
* `resources` — CPU and memory per replica: `minCpu`/`minMemory` are the reservation, `maxCpu`/`maxMemory` the limit.
* `app.appSecretName` — Name of the prerequisite [opaque secret](/guides/create-secret/opaque) holding the app secret. It must exist before you install, and its value must stay stable — see [Prerequisites](#prerequisites).
* `app.disableTelemetry` — When `true` (default), opts out of Umami's anonymous usage telemetry.

### Tracker

* `tracker.scriptName` — Also serve the tracking script under a custom path (e.g. `s.js` → `/s.js`) instead of only the default `/script.js`. Useful for reducing ad-blocker interception.
* `tracker.collectEndpoint` — Have the tracker POST events to a custom path (e.g. `/api/track`) instead of the default `/api/send`. The custom path is baked into the script Umami serves, so the snippet you embed uses it automatically.

Both default to `""` (standard paths). Custom paths take effect once a replica has fully booted with the new setting; a mid-rollout replica still serves the old path until it cycles.

<Note>
  **Custom tracker paths are aliases, not replacements.** With `scriptName` and `collectEndpoint` set, the default `/script.js` still returns the tracker and the default `/api/send` still accepts and records events. Custom paths help because *your site* loads the unblocked path — they do not close the defaults off.
</Note>

### Access

* `publicAccess.enabled` — Serve the dashboard and tracking endpoint on the canonical `*.cpln.app` HTTPS endpoint. **Defaults to `false`**, which blocks external requests at the edge; in-GVC callers still reach it per `internalAccess`. **Tracking collects nothing while it is off**, so turn it on once the admin password is changed — see [First Run](#first-run-sequence).
* `internalAccess.type` — Internal firewall scope of the Umami 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`. |

<Note>
  Access changes take up to a couple of minutes to propagate — every measured transition returned the **stale** value for the first 25–50 seconds. Re-poll before concluding a knob is broken.
</Note>

### Backing Store

Enable exactly one of `postgres` (single-instance, default) or `postgresHA` (HA) — see [Choosing a Database Mode](#choosing-a-database-mode). In both modes, **change the database password before installing** (`postgres.credentials.password` / `postgres.credentials.password`).

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`, which is off by 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>

## Connecting

| What                             | Value                                                                                                                                      |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Local access (public access off) | `cpln port-forward {release}-umami 3000:3000 --gvc {gvc}`, then `http://localhost:3000`                                                    |
| Public URL                       | `status.canonicalEndpoint` of `{release}-umami` (`cpln workload get {release}-umami -o yaml`) — only when `publicAccess.enabled` is `true` |
| Dashboard / login                | `https://<canonical>.cpln.app/login`                                                                                                       |
| Tracking script                  | `https://<canonical>.cpln.app/script.js` (embed on your site)                                                                              |
| Collect endpoint                 | `https://<canonical>.cpln.app/api/send` (where the tracker POSTs events)                                                                   |
| Internal (same GVC)              | `http://{release}-umami.{gvc}.cpln.local:3000`                                                                                             |
| Default admin                    | `admin` / `umami` — hardcoded; change it before publishing (see [First Run](#first-run-sequence))                                          |
| App secret                       | The payload of your `app.appSecretName` secret; never stored in the Helm release                                                           |

To start collecting data, log in, add a website in the dashboard, then paste the generated `<script>` tag — which loads the tracking script and POSTs to the collect endpoint — into your site's HTML.

## Backing Up

Database backups are optional and disabled by default. They cover the analytics database — the users, websites, sessions, and events that make up your Umami instance. Enable them with `postgres.backup.enabled` or `postgresHA.backup.enabled` (matching your database mode), and complete the storage setup for your provider **before** installing. These values are a pass-through to the backing database template, which owns and documents the backup itself; the backup runs as a scheduled job in that store.

<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 that grants list/get/put/delete on your bucket (`arn:aws:s3:::YOUR_BUCKET` and `arn:aws:s3:::YOUR_BUCKET/*`), then set `backup.aws.policyName` to the policy's name. The backing template's README has the full JSON.
      </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 and grant its service account **Storage Object Admin** (`roles/storage.objectAdmin`) on the bucket. Set `backup.gcp.cloudAccountName` to its name.
      </Step>
    </Steps>
  </Tab>

  <Tab title="S3-compatible (MinIO)">
    <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 and credentials">
        Set `backup.minio.endpoint` to the S3 API address including port. No Cloud Account is required — the credentials authenticate directly. 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, `postgresHA.backup.mode` selects `logical` (scheduled `pg_dump`) or `wal-g` (continuous WAL archiving). The full per-provider walkthrough, including the exact IAM JSON, lives in the backing [postgres](/template-catalog/templates/postgres) / [postgres-highly-available](/template-catalog/templates/postgres-highly-available) template README.

## Important Notes

* **Create the app secret before installing.** A missing prerequisite secret does not fail the install — `helm install` reports success and the workload then sits at zero replicas with no container logs at all. Creating the secret afterwards needs a `cpln workload force-redeployment` to take effect; see [Prerequisites](#prerequisites).
* **Change the hardcoded `admin` / `umami` password before you make the install public.** It is seeded by the first database migration and cannot be overridden at install time. This is why `publicAccess.enabled` defaults to `false` — follow [First Run](#first-run-sequence) in order.
* **Tracking collects nothing until `publicAccess.enabled` is `true`** — browsers on the sites you track must reach the tracking script and the collect endpoint. Publishing is a required step, not an optional one.
* **The upgrade that publishes the install bounces the bundled database for roughly two minutes**, because it is the first upgrade after the install. `/api/heartbeat` stays `200` throughout and is not the signal to watch — it never touches the database.
* **Upgrading from `1.0.1` fails at render if you carry `app.appSecret`, `resources.cpu`, or `resources.memory` forward.** Each guard names its replacement and leaves the running release untouched — see [Upgrading From 1.0.1](#upgrading-from-1-0-1).
* **The app secret must stay stable for the life of the install** — changing its payload logs every user out. Back it up outside Control Plane. It is yours, not the release's: it survives `helm uninstall` and must be deleted manually.
* **Custom tracker paths are aliases** — `/script.js` and `/api/send` stay live and keep recording even when `tracker.scriptName` / `tracker.collectEndpoint` are set.
* **`replicas` ≥ 2 is recommended for production** — replicas are independent and share the database and the app secret, so a session opened against one is honored by the others; rolling restarts cycle one at a time with no downtime.
* **Database volumes survive reinstalls under the same release name; uninstalling deletes them** — all analytics data is lost. Use `postgresHA` and/or enable backups for durable production data.

## External References

<CardGroup cols={2}>
  <Card title="Umami Documentation" icon="book" href="https://umami.is/docs">
    Official Umami documentation
  </Card>

  <Card title="Tracker Configuration" icon="gear" href="https://umami.is/docs/tracker-configuration">
    Configure the tracking script and its options
  </Card>

  <Card title="Collect API" icon="code" href="https://umami.is/docs/api/sending-stats">
    How events are sent to the collect endpoint
  </Card>

  <Card title="Environment Variables" icon="sliders" href="https://umami.is/docs/environment-variables">
    Umami environment variables reference
  </Card>

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