Umami
Deploy Umami on Kubernetes as a privacy-first web analytics platform. The chart uses the official
ghcr.io/umami-software/umami:3.1.0 image and supports a quick bundled PostgreSQL install as well as production
setups with external PostgreSQL, Gateway API or Ingress, External Secrets Operator, dual-stack Service fields,
NetworkPolicy, PodDisruptionBudget, S3-compatible backups, and structured Umami runtime options.
Umami tracks page views, sessions, events, Web Vitals, boards, shares, and session replay data without requiring third-party analytics services. The application stores its state in PostgreSQL and exposes a lightweight tracker script that your websites load from the Umami instance.
Key Features
- Umami 3.1.0 - aligned with the current HelmForge chart
appVersion, including Umami 3.x analytics features. - PostgreSQL first - bundled HelmForge PostgreSQL subchart for quick starts or external PostgreSQL for production.
- External database preparation - optional init container can run
CREATE EXTENSION IF NOT EXISTS pgcrypto;with an admin Secret. - Structured runtime settings - telemetry, updates, bot detection, SSL, client IP header, collect endpoint, CORS, frame allowlists, and tracker script name.
- Secret management - inline values, existing Kubernetes Secrets, or
external-secrets.io/v1resources for APP_SECRET, database password, and S3 credentials. - Routing choices - Ingress or Kubernetes Gateway API
HTTPRoute. - Dual-stack Service - optional
ipFamilyPolicyandipFamiliesfields. - Production controls - NetworkPolicy, PodDisruptionBudget, scheduling controls, resource settings, and disabled ServiceAccount token mount by default.
- S3 backup - scheduled PostgreSQL
pg_dumpuploaded to S3-compatible object storage.
Architecture
Production Request Flow
Website tracker requests reach Umami through Gateway API or Ingress, then the Service routes to one or more Umami pods backed by PostgreSQL.
Secrets and Backup Flow
External Secrets can materialize runtime credentials, while the backup CronJob dumps PostgreSQL data and uploads the archive to S3-compatible storage.
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install umami helmforge/umami --namespace umami --create-namespace
OCI registry:
helm install umami oci://ghcr.io/helmforgedev/helm/umami --namespace umami --create-namespace
Default local access:
kubectl port-forward -n umami svc/umami-umami 3000:80
Open http://localhost:3000/ and sign in with Umami’s upstream initial credentials:
- Username:
admin - Password:
umami
Change the default admin password immediately after the first login. The chart does not replace Umami’s initial
bootstrap credentials for you.
Development vs Production
The default chart values are intentionally simple and development-friendly:
postgresql.enabled=truereplicaCount=1- generated APP_SECRET and PostgreSQL password
- ClusterIP Service only
- no public ingress or Gateway API route
- no NetworkPolicy or PDB
- telemetry and update checks disabled
This is useful for local clusters, demos, and CI smoke tests. For production, use a stable APP_SECRET, external or operator-managed PostgreSQL, TLS termination, resource requests and limits, backup or database-native backup, and NetworkPolicy rules aligned with your cluster networking.
Deployment Examples
# values.yaml - development install with bundled PostgreSQL
postgresql:
enabled: true
umami:
appSecret: 'replace-with-a-stable-secret'replicaCount: 2
umami:
existingSecret: umami-app
existingSecretKey: app-secret
forceSSL: true
clientIpHeader: x-forwarded-for
trackerScriptName: stats
collectApiEndpoint: /api/send
postgresql:
enabled: false
database:
external:
host: postgres-primary.database.svc.cluster.local
port: '5432'
name: umami
username: umami
existingSecret: umami-db
existingSecretPasswordKey: database-password
init:
enabled: true
adminUsername: postgres
adminExistingSecret: postgres-admin
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
pdb:
enabled: true
minAvailable: 1
networkPolicy:
enabled: true
ingress:
allowSameNamespace: true
egress:
enabled: true
allowDNS: true
allowSameNamespaceDatabase: true
allowHTTPS: truepostgresql:
enabled: false
database:
external:
host: postgres-primary.database.svc.cluster.local
port: '5432'
name: umami
username: umami
existingSecret: umami-db
existingSecretPasswordKey: database-password
init:
enabled: true
image: docker.io/library/postgres:18-alpine
adminUsername: postgres
adminExistingSecret: postgres-admin
adminExistingSecretPasswordKey: postgres-password
sql: |
CREATE EXTENSION IF NOT EXISTS pgcrypto;The bundled PostgreSQL path creates pgcrypto through the subchart init script. For external databases, enable
database.external.init.enabled when the Umami application user cannot create extensions.
gatewayAPI:
enabled: true
parentRefs:
- name: public-gateway
namespace: gateway-system
hostnames:
- analytics.example.com
umami:
forceSSL: true
clientIpHeader: x-forwarded-forpostgresql:
enabled: false
externalSecrets:
enabled: true
apiVersion: external-secrets.io/v1
secretStoreRef:
name: platform-secrets
kind: ClusterSecretStore
app:
enabled: true
targetName: umami-app
appSecretRemoteRef:
key: prod/umami/app
property: appSecret
database:
enabled: true
targetName: umami-db
passwordRemoteRef:
key: prod/umami/database
property: passwordbackup:
enabled: true
schedule: '0 3 * * *'
s3:
endpoint: https://s3.example.com
bucket: umami-backups
prefix: production
existingSecret: umami-backup-s3Routing
Use Gateway API when your cluster has a Gateway controller and shared listeners:
gatewayAPI:
enabled: true
parentRefs:
- name: public-gateway
namespace: gateway-system
hostnames:
- analytics.example.com
Use Ingress when your platform standardizes on networking.k8s.io/v1 Ingress:
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: analytics.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: umami-tls
hosts:
- analytics.example.com
Service Dual Stack
The Service supports Kubernetes dual-stack fields:
service:
ipFamilyPolicy: PreferDualStack
ipFamilies:
- IPv4
- IPv6
Dual stack only works when the cluster, CNI, and Service CIDRs are configured for IPv4 and IPv6.
Umami Runtime Settings
| Value | Env Var | Default | Purpose |
|---|---|---|---|
umami.disableTelemetry | DISABLE_TELEMETRY | true | Disables upstream telemetry. |
umami.disableUpdates | DISABLE_UPDATES | true | Disables upstream update checks. |
umami.disableBotCheck | DISABLE_BOT_CHECK | false | Disables bot filtering when required for controlled tests. |
umami.cloudMode | CLOUD_MODE | false | Hides users, teams, and websites settings pages for managed deployments. |
umami.forceSSL | FORCE_SSL | false | Trusts proxy HTTPS and emits secure URLs. |
umami.clientIpHeader | CLIENT_IP_HEADER | "" | Header used to resolve client IP behind proxies. |
umami.collectApiEndpoint | COLLECT_API_ENDPOINT | "" | Custom collect endpoint for tracker requests. |
umami.corsMaxAge | CORS_MAX_AGE | "" | CORS preflight cache duration. |
umami.allowedFrameUrls | ALLOWED_FRAME_URLS | "" | Space-delimited frame allowlist. |
umami.debug | DEBUG | "" | Debug namespaces, for example umami:prisma. |
umami.trackerScriptName | TRACKER_SCRIPT_NAME | "" | Custom tracker script file name. |
Use umami.extraEnv for upstream settings that are not yet modeled directly by the chart.
trackerScriptName and collectApiEndpoint can reduce ad-blocker false positives. Update the website snippet after
changing either value.
Umami’s BASE_PATH is a build-time setting for the upstream application image. The HelmForge chart does not set it as
a runtime value for the stock image.
Secrets
The chart can create generated Secrets, consume existing Kubernetes Secrets, or render External Secrets Operator resources. For production, prefer a stable APP_SECRET stored outside the Helm release:
umami:
existingSecret: umami-app
existingSecretKey: app-secret
Changing APP_SECRET invalidates active sessions and tokens. Do not rely on generated secrets for production upgrades or disaster recovery.
NetworkPolicy
NetworkPolicy is opt-in because egress needs vary by platform:
networkPolicy:
enabled: true
ingress:
allowSameNamespace: true
egress:
enabled: true
allowDNS: true
allowSameNamespaceDatabase: true
databasePort: 5432
allowHTTPS: true
When PostgreSQL, S3, OIDC, or other integrations live outside the namespace, add explicit extraTo peers for your CNI
and network model.
Backup
The backup CronJob runs pg_dump against the Umami PostgreSQL database and uploads the archive to S3-compatible
storage. It protects Umami users, websites, dashboards, events, session replay data, shares, and related PostgreSQL
state.
| Value | Default | Description |
|---|---|---|
backup.enabled | false | Render the backup CronJob. |
backup.schedule | "0 3 * * *" | Cron schedule. |
backup.concurrencyPolicy | Forbid | Prevent overlapping backups by default. |
backup.archivePrefix | umami | Archive filename prefix. |
backup.images.postgresql | docker.io/library/postgres:18-alpine | Image used for pg_dump. |
backup.images.uploader | docker.io/helmforge/mc:1.0.0 | MinIO client image used for upload. |
backup.s3.endpoint | "" | S3-compatible endpoint. |
backup.s3.bucket | "" | Target bucket. |
backup.s3.existingSecret | "" | Existing Secret with S3 credentials. |
backup.database.existingSecret | "" | Optional backup-specific database password Secret. |
backup.database.postgresDumpArgs | "" | Extra pg_dump arguments. |
A scheduled dump is not enough for production readiness. Test restore into a separate PostgreSQL database and verify Umami can start against the restored data.
Configuration Reference
Core
| Parameter | Type | Default | Description |
|---|---|---|---|
nameOverride | string | "" | Override the chart name. |
fullnameOverride | string | "" | Override the full release name. |
commonLabels | object | {} | Extra labels added to chart resources. |
replicaCount | integer | 1 | Number of Umami pods. Use external PostgreSQL before scaling above one. |
Image
| Parameter | Type | Default | Description |
|---|---|---|---|
image.repository | string | ghcr.io/umami-software/umami | Umami container image repository. |
image.tag | string | 3.1.0 | Umami image tag. |
image.pullPolicy | string | IfNotPresent | Image pull policy. |
imagePullSecrets | array | [] | Pull secrets for private registries. |
Database
| Parameter | Type | Default | Description |
|---|---|---|---|
postgresql.enabled | boolean | true | Deploy bundled HelmForge PostgreSQL. |
postgresql.architecture | string | standalone | PostgreSQL subchart architecture. |
postgresql.auth.database | string | umami | Database created by the subchart. |
postgresql.auth.username | string | umami | Application database user. |
postgresql.auth.password | string | "" | PostgreSQL password, generated when empty. |
postgresql.serviceAccount.automountServiceAccountToken | boolean | false | Disable API token mount in PostgreSQL pods by default. |
database.external.host | string | "" | External PostgreSQL host. |
database.external.port | string | "5432" | External PostgreSQL port. |
database.external.name | string | umami | External database name. |
database.external.username | string | umami | External database user. |
database.external.password | string | "" | Inline external database password. Prefer Secret or External Secrets. |
database.external.existingSecret | string | "" | Existing Secret with the database password. |
database.external.existingSecretPasswordKey | string | password | Password key in the existing Secret. |
database.external.init.enabled | boolean | false | Run external database preparation before Umami starts. |
database.external.init.image | string | docker.io/library/postgres:18-alpine | PostgreSQL client image for init. |
database.external.init.adminUsername | string | postgres | Admin user used only by init. |
database.external.init.adminExistingSecret | string | "" | Secret containing the admin password. |
database.external.init.sql | string | CREATE EXTENSION IF NOT EXISTS pgcrypto; | SQL executed by the init container. |
Service, Ingress, Gateway, and PDB
| Parameter | Type | Default | Description |
|---|---|---|---|
service.type | string | ClusterIP | Kubernetes Service type. |
service.port | integer | 80 | Service port. |
service.annotations | object | {} | Service annotations. |
service.ipFamilyPolicy | string | "" | Optional Service IP family policy. |
service.ipFamilies | array | [] | Optional Service IP families. |
ingress.enabled | boolean | false | Render Ingress. |
ingress.ingressClassName | string | traefik | Ingress class name. |
ingress.hosts | array | [] | Host/path rules. |
ingress.tls | array | [] | TLS entries. |
gatewayAPI.enabled | boolean | false | Render Gateway API HTTPRoute. |
gatewayAPI.parentRefs | array | [] | HTTPRoute parent references. |
gatewayAPI.hostnames | array | [] | HTTPRoute hostnames. |
gatewayAPI.matches | array | path prefix / | HTTPRoute path matches. |
gatewayAPI.filters | array | [] | Optional HTTPRoute filters. |
pdb.enabled | boolean | false | Render PodDisruptionBudget. |
pdb.minAvailable | integer | 1 | Minimum available pods when enabled. |
pdb.maxUnavailable | string | "" | Alternative disruption budget value. |
External Secrets
| Parameter | Type | Default | Description |
|---|---|---|---|
externalSecrets.enabled | boolean | false | Render ExternalSecret resources. |
externalSecrets.apiVersion | string | external-secrets.io/v1 | External Secrets API version. |
externalSecrets.refreshInterval | string | 1h | Reconciliation refresh interval. |
externalSecrets.secretStoreRef.name | string | "" | SecretStore or ClusterSecretStore name. |
externalSecrets.secretStoreRef.kind | string | SecretStore | Store kind. |
externalSecrets.app.enabled | boolean | false | Manage APP_SECRET with External Secrets. |
externalSecrets.database.enabled | boolean | false | Manage external database password with External Secrets. |
externalSecrets.backup.enabled | boolean | false | Manage S3 backup credentials with External Secrets. |
Probes, Security, and Scheduling
| Parameter | Type | Default | Description |
|---|---|---|---|
probes.startup.path | string | /api/heartbeat | Startup probe path. |
probes.startup.initialDelaySeconds | integer | 30 | Startup probe initial delay. |
probes.liveness.path | string | /api/heartbeat | Liveness probe path. |
probes.readiness.path | string | /api/heartbeat | Readiness probe path. |
resources | object | {} | Container resource requests and limits. |
podSecurityContext | object | {} | Pod-level security context. |
securityContext | object | {} | Container security context. |
serviceAccount.create | boolean | false | Create a dedicated ServiceAccount. |
serviceAccount.automountServiceAccountToken | boolean | false | Mount API token into Umami and backup pods. |
nodeSelector | object | {} | Node selector. |
tolerations | array | [] | Tolerations. |
affinity | object | {} | Affinity rules. |
topologySpreadConstraints | array | [] | Topology spread constraints. |
priorityClassName | string | "" | PriorityClass name. |
podLabels | object | {} | Extra pod labels. |
podAnnotations | object | {} | Extra pod annotations. |
extraVolumes | array | [] | Extra volumes. |
extraVolumeMounts | array | [] | Extra volume mounts. |
extraManifests | array | [] | Additional Kubernetes manifests. |
Common Issues
This usually means APP_SECRET changed. Set umami.appSecret, umami.existingSecret, or
externalSecrets.app.enabled=true with a durable external secret source.
Enable database.external.init.enabled with an admin Secret or create the extension manually before installing the
chart.
Set umami.trackerScriptName to a neutral name such as stats and optionally set umami.collectApiEndpoint. Update
website snippets after changing these values.
Upgrade Notes
Umami 3.x includes schema migrations for newer analytics features such as Boards, Shares, Web Vitals, and Session Replay. Back up PostgreSQL before upgrading live deployments, especially when moving from Umami 2.x.
The chart version 2.0.0 is a breaking modernization release. Review production values for renamed or newly structured
settings, External Secrets behavior, Gateway API, NetworkPolicy, and backup credentials before upgrading.