Metabase
Open-source business intelligence and analytics platform. Metabase lets teams build interactive dashboards, run SQL queries, and share insights without writing code. It stores all metadata (dashboards, saved questions, user accounts, data source credentials) in a PostgreSQL database.
Key Features
- Interactive dashboards — drag-and-drop visualization builder, no SQL required
- SQL editor — native query editor with autocomplete and result visualization
- PostgreSQL metadata store — bundled subchart or external database
- Encrypted credential storage — data source passwords encrypted with
encryptionSecretKey - JVM tuning — configurable Java memory options and timezone
- S3 backup — scheduled
pg_dumpof the Metabase metadata database to S3-compatible storage - Ingress support — TLS via cert-manager with configurable ingress class
- Gateway API support — optional HTTPRoute for Kubernetes-native traffic routing
- Dual-stack ready Service — optional
ipFamilyPolicyandipFamilies - External Secrets support — ESO integration for the Metabase encryption key
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install metabase helmforge/metabase
OCI registry:
helm install metabase oci://ghcr.io/helmforgedev/helm/metabase
After deploying, access Metabase:
kubectl port-forward svc/<release>-metabase 3000:80
# Open http://localhost:3000 to complete the setup wizard
Deployment Examples
# values.yaml — Metabase with bundled PostgreSQL subchart (default)
metabase:
siteUrl: 'https://metabase.example.com'
encryptionSecretKey: 'a-random-32-char-secret-key-here'
javaOpts: '-Xms512m -Xmx1g'
postgresql:
enabled: true
auth:
password: 'postgres-password'
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: metabase.example.com
paths:
- path: /
pathType: Prefix# values.yaml — Metabase pointing to an existing PostgreSQL instance
metabase:
siteUrl: 'https://metabase.example.com'
encryptionSecretKey: 'a-random-32-char-secret-key-here'
postgresql:
enabled: false
database:
external:
host: postgresql.database.svc
port: '5432'
name: metabase
username: metabase
password: 'db-password'
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: metabase.example.com
paths:
- path: /
pathType: Prefix# values.yaml — Daily backup of the Metabase PostgreSQL metadata database
metabase:
siteUrl: 'https://metabase.example.com'
encryptionSecretKey: 'a-random-32-char-secret-key-here'
postgresql:
enabled: true
auth:
password: 'postgres-password'
backup:
enabled: true
schedule: '0 3 * * *'
s3:
endpoint: https://s3.amazonaws.com
bucket: my-metabase-backups
accessKey: '<set-me>'
secretKey: '<set-me>'# values.yaml — Production-grade setup with secrets, resources, and TLS
metabase:
siteUrl: 'https://metabase.example.com'
existingSecret: metabase-secret
existingSecretKey: encryption-secret-key
javaOpts: '-Xms1g -Xmx2g'
javaTimezone: America/Sao_Paulo
postgresql:
enabled: false
database:
external:
host: postgresql.production.svc
port: '5432'
name: metabase
username: metabase
existingSecret: metabase-db-secret
existingSecretPasswordKey: password
resources:
requests:
memory: 1.5Gi
cpu: 500m
limits:
memory: 2.5Gi
cpu: 2000m
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: metabase.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: metabase-tls
hosts:
- metabase.example.comConfiguration 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 all resources. |
Image
| Parameter | Type | Default | Description |
|---|---|---|---|
image.repository |
string | docker.io/metabase/metabase |
Metabase container image. |
image.tag |
string | "v0.63.13" |
Image tag. |
image.pullPolicy |
string | IfNotPresent |
Image pull policy. |
imagePullSecrets |
array | [] |
Pull secrets for private registries. |
Air-Gapped and Proxied Registries
The chart uses a wait-for-db init container before starting Metabase. Configure waitForDatabase.image.* when your
cluster must pull helper images from an internal registry or proxy.
image:
repository: registry.example.com/proxy/metabase/metabase
tag: v0.63.13
waitForDatabase:
image:
repository: registry.example.com/proxy/library/busybox
tag: '1.37'
| Parameter | Type | Default | Description |
|---|---|---|---|
waitForDatabase.image.repository |
string | docker.io/library/busybox |
Wait-for-db init container image repository. |
waitForDatabase.image.tag |
string | "1.37" |
Wait-for-db init container image tag. |
waitForDatabase.image.pullPolicy |
string | IfNotPresent |
Wait-for-db init container image pull policy. |
Metabase Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
metabase.port |
integer | 3000 |
Internal HTTP port for the Metabase container. |
metabase.encryptionSecretKey |
string | "" |
Secret key used to encrypt saved data source credentials. |
metabase.existingSecret |
string | "" |
Existing secret containing the encryption key. |
metabase.existingSecretKey |
string | encryption-secret-key |
Key inside the existing secret holding the encryption value. |
metabase.siteUrl |
string | "" |
Public URL of the Metabase instance. Used for email links and embeds. |
metabase.aiFeaturesEnabled |
boolean | false |
Enable Metabase AI features after configuring a supported provider. |
metabase.javaTimezone |
string | UTC |
JVM timezone (TZ environment variable). |
metabase.javaOpts |
string | "" |
JVM options for memory tuning (e.g. -Xms512m -Xmx1g). |
metabase.extraEnv |
array | [] |
Extra environment variables injected into the Metabase container. |
If metabase.encryptionSecretKey is empty, Metabase auto-generates one at startup. If the pod is recreated without a
persistent key, all saved data source credentials become unreadable and must be re-entered manually. Always set an
explicit key or use metabase.existingSecret.
Metabase is a JVM application. Without -Xmx, the JVM may claim all available container memory. Set
metabase.javaOpts: '-Xms512m -Xmx1g' and resources.limits.memory to a value at least 20% higher than -Xmx to
avoid OOMKilled restarts.
Database — Embedded Subchart
| Parameter | Type | Default | Description |
|---|---|---|---|
postgresql.enabled |
boolean | true |
Deploy a bundled PostgreSQL subchart for Metabase metadata. |
postgresql.architecture |
string | standalone |
PostgreSQL deployment architecture. |
postgresql.auth.database |
string | metabase |
Database name created by the subchart. |
postgresql.auth.username |
string | metabase |
Database username created by the subchart. |
postgresql.auth.password |
string | "" |
Database password (auto-generated if empty). |
Database — External
| Parameter | Type | Default | Description |
|---|---|---|---|
database.external.host |
string | "" |
External PostgreSQL hostname or IP. |
database.external.port |
string | "5432" |
External PostgreSQL port. |
database.external.name |
string | metabase |
Database name on the external server. |
database.external.username |
string | metabase |
Username for the external database. |
database.external.password |
string | "" |
Password for the external database (plain text — prefer secret). |
database.external.existingSecret |
string | "" |
Existing secret containing the database password. |
database.external.existingSecretPasswordKey |
string | password |
Key inside the existing secret for the password. |
Service
| Parameter | Type | Default | Description |
|---|---|---|---|
service.type |
string | ClusterIP |
Kubernetes service type. |
service.port |
integer | 80 |
Service port exposed to the cluster. |
service.annotations |
object | {} |
Annotations for the Service. |
service.ipFamilyPolicy |
string/null | ~ |
Service IP family policy. |
service.ipFamilies |
array | [] |
Ordered Service IP families. |
Dual-Stack Service
service:
ipFamilyPolicy: PreferDualStack
ipFamilies:
- IPv4
- IPv6
Ingress
| Parameter | Type | Default | Description |
|---|---|---|---|
ingress.enabled |
boolean | false |
Enable an Ingress resource. |
ingress.ingressClassName |
string | traefik |
Ingress class name. |
ingress.annotations |
object | {} |
Annotations for the Ingress (e.g. cert-manager). |
ingress.hosts |
array | [] |
Ingress host and path rules. |
ingress.tls |
array | [] |
TLS configuration (secret name and hosts). |
Gateway API
Set gateway.enabled to render a Kubernetes Gateway API HTTPRoute for Metabase. The chart expects an existing
Gateway and does not create shared Gateway infrastructure.
gateway:
enabled: true
parentRefs:
- name: shared-gateway
namespace: gateway-system
hostnames:
- metabase.example.com
| Parameter | Type | Default | Description |
|---|---|---|---|
gateway.enabled |
boolean | false |
Render a Gateway API HTTPRoute. |
gateway.parentRefs |
array | [] |
Existing Gateway parentRefs, required when enabled. |
gateway.hostnames |
array | [] |
HTTPRoute hostnames. |
gateway.path |
string | / |
HTTPRoute path match value. |
gateway.pathType |
string | PathPrefix |
HTTPRoute path match type. |
gateway.annotations |
object | {} |
Annotations added to the HTTPRoute. |
The older gatewayAPI block remains supported as a deprecated compatibility alias.
Probes
Metabase has a slow startup — the JVM initialization and database migrations can take 2–3 minutes.
| Parameter | Type | Default | Description |
|---|---|---|---|
probes.startup.enabled |
boolean | true |
Enable startup probe (uses /api/health). |
probes.startup.initialDelaySeconds |
integer | 90 |
Startup probe initial delay. |
probes.startup.periodSeconds |
integer | 10 |
Startup probe period. |
probes.startup.timeoutSeconds |
integer | 5 |
Startup probe timeout. |
probes.startup.failureThreshold |
integer | 30 |
Startup probe failure threshold (5 minutes). |
probes.liveness.enabled |
boolean | true |
Enable liveness probe. |
probes.liveness.initialDelaySeconds |
integer | 0 |
Liveness probe initial delay. |
probes.liveness.periodSeconds |
integer | 15 |
Liveness probe period. |
probes.liveness.timeoutSeconds |
integer | 5 |
Liveness probe timeout. |
probes.liveness.failureThreshold |
integer | 3 |
Liveness probe failure threshold. |
probes.readiness.enabled |
boolean | true |
Enable readiness probe. |
probes.readiness.initialDelaySeconds |
integer | 0 |
Readiness probe initial delay. |
probes.readiness.periodSeconds |
integer | 10 |
Readiness probe period. |
probes.readiness.timeoutSeconds |
integer | 5 |
Readiness probe timeout. |
probes.readiness.failureThreshold |
integer | 3 |
Readiness probe failure threshold. |
Backup
The backup CronJob runs pg_dump against the Metabase PostgreSQL metadata database and uploads the archive to
S3-compatible storage. This protects dashboards, saved questions, users, and data source definitions.
| Parameter | Type | Default | Description |
|---|---|---|---|
backup.enabled |
boolean | false |
Enable scheduled S3 backup CronJob. |
backup.schedule |
string | "0 3 * * *" |
Cron schedule for backups. |
backup.suspend |
boolean | false |
Suspend the CronJob without deleting it. |
backup.concurrencyPolicy |
string | Forbid |
CronJob concurrency policy. |
backup.successfulJobsHistoryLimit |
integer | 3 |
Number of successful Job records to keep. |
backup.failedJobsHistoryLimit |
integer | 3 |
Number of failed Job records to keep. |
backup.backoffLimit |
integer | 1 |
Job retry limit. |
backup.archivePrefix |
string | metabase |
Prefix for backup archive filenames. |
backup.images.postgresql |
string | docker.io/library/postgres:18-alpine |
Image used for pg_dump. |
backup.images.uploader |
string | docker.io/helmforge/mc:1.0.0 |
Image used for S3 upload. |
backup.resources |
object | {} |
Resources for backup containers. |
backup.s3.endpoint |
string | "" |
S3-compatible endpoint URL. |
backup.s3.bucket |
string | "" |
Target bucket name. |
backup.s3.prefix |
string | metabase |
Key prefix within the bucket. |
backup.s3.createBucketIfNotExists |
boolean | true |
Create the bucket automatically if it does not exist. |
backup.s3.existingSecret |
string | "" |
Existing secret containing S3 access and secret keys. |
backup.s3.existingSecretAccessKeyKey |
string | access-key |
Key in the existing secret for the S3 access key. |
backup.s3.existingSecretSecretKeyKey |
string | secret-key |
Key in the existing secret for the S3 secret key. |
backup.s3.accessKey |
string | "" |
Inline S3 access key (ignored when existingSecret is set). |
backup.s3.secretKey |
string | "" |
Inline S3 secret key (ignored when existingSecret is set). |
backup.database.host |
string | "" |
Override database host for backup (uses app credentials if empty). |
backup.database.port |
string | "" |
Override database port for backup. |
backup.database.name |
string | "" |
Override database name for backup. |
backup.database.username |
string | "" |
Override database username for backup. |
backup.database.password |
string | "" |
Override database password for backup. |
backup.database.existingSecret |
string | "" |
Existing secret containing backup database credentials. |
backup.database.existingSecretPasswordKey |
string | password |
Key in the existing secret for the backup database password. |
backup.database.postgresDumpArgs |
string | "" |
Extra arguments passed to pg_dump. |
External Secrets
Set externalSecrets.enabled when External Secrets Operator manages the Metabase application secret. The chart requires
metabase.existingSecret so ESO is the only writer for the encryption key.
metabase:
existingSecret: metabase-app-secret
existingSecretKey: encryption-secret-key
externalSecrets:
enabled: true
secretStoreRef:
name: cluster-secrets
kind: ClusterSecretStore
data:
- secretKey: encryption-secret-key
remoteRef:
key: metabase/credentials
property: encryption-secret-key
| Parameter | Type | Default | Description |
|---|---|---|---|
externalSecrets.enabled |
boolean | false |
Render an ExternalSecret resource. |
externalSecrets.apiVersion |
string | external-secrets.io/v1 |
ExternalSecret API version. |
externalSecrets.refreshInterval |
string | "0" |
ExternalSecret refresh interval. |
externalSecrets.secretStoreRef.name |
string | "" |
SecretStore or ClusterSecretStore name. |
externalSecrets.secretStoreRef.kind |
string | SecretStore |
Secret store kind. |
externalSecrets.target.creationPolicy |
string | Owner |
Target Secret creation policy. |
externalSecrets.data |
array | [] |
Remote key mappings for the app Secret. |
Resources and Security
| Parameter | Type | Default | Description |
|---|---|---|---|
resources |
object | {} |
CPU and memory requests and limits. |
podSecurityContext |
object | {} |
Pod-level security context. |
securityContext |
object | {} |
Container-level security context. |
Service Account
| Parameter | Type | Default | Description |
|---|---|---|---|
serviceAccount.create |
boolean | false |
Create a dedicated ServiceAccount. |
serviceAccount.name |
string | "" |
Override the ServiceAccount name. |
serviceAccount.annotations |
object | {} |
Annotations for the ServiceAccount. |
Scheduling
| Parameter | Type | Default | Description |
|---|---|---|---|
nodeSelector |
object | {} |
Node selector for scheduling. |
tolerations |
array | [] |
Tolerations for scheduling. |
affinity |
object | {} |
Affinity rules. |
topologySpreadConstraints |
array | [] |
Topology spread constraints. |
priorityClassName |
string | "" |
PriorityClass for the pod. |
terminationGracePeriodSeconds |
integer | 30 |
Termination grace period. |
podLabels |
object | {} |
Extra labels for the pod. |
podAnnotations |
object | {} |
Extra annotations for the pod. |
Extra
| Parameter | Type | Default | Description |
|---|---|---|---|
extraVolumes |
array | [] |
Extra volumes to attach to the pod. |
extraVolumeMounts |
array | [] |
Extra volume mounts for the container. |
extraInitContainers |
array | [] |
Additional init containers rendered after wait-for-db. |
extraManifests |
array | [] |
Extra Kubernetes manifests deployed alongside the chart. |
Common Issues
Metabase is a JVM application and requires adequate memory. If the pod is killed without a clear error, check
kubectl describe pod <name> for OOMKilled. Set metabase.javaOpts: '-Xms512m -Xmx1g' and configure
resources.limits.memory at least 20% above -Xmx.
Metabase runs database migrations on first boot. With the embedded PostgreSQL subchart, the first startup can take 2–4
minutes. The startup probe is configured with a 5-minute window (failureThreshold: 30 × periodSeconds: 10). Do not
reduce these values on a fresh install.
The S3 backup protects the PostgreSQL data, but encryptionSecretKey is not stored in the database — it is stored in
your values or Kubernetes secret. Back it up separately. Without it, a restored database is unusable for re-encrypting
credentials.
Upgrade Notes
Metabase v0.63.13 is the maintenance update selected by the upstream image
monitor. Back up the Metabase application database, keep the encryption key
stable, and review the
official Metabase changelog before
upgrading. Validate /api/health after rollout; first startup can take several
minutes while database migrations run.