Skip to main content

Overview

Grafana is the open-source (AGPL-3.0) dashboarding and alerting front end for time-series and SQL data. This template deploys Grafana OSS as a stateless app tier backed by a bundled PostgreSQL app database — every dashboard, user, alert rule and saved datasource lives in that database, so the Grafana workload itself holds no state and can run more than one replica. It exists to visualize data you own: the catalog’s Prometheus, Thanos and Mimir installs, your own SQL databases, and external systems. It is not an observability stack in itself — it ships no collectors, no exporters and no data.
This template ships zero dashboards and zero datasources. A fresh install has an empty dashboard list and an empty datasource list — you connect your own datasources (in the UI or as code) and build or import your own dashboards. Control Plane’s console already provides built-in workload-metrics dashboards for every workload; this template is for the data those dashboards do not cover.
This template deploys into a GVC you already have — it does not create, provision or manage one. Every workload below runs in each location of that GVC. For a Grafana spread across several locations behind one georouted endpoint, use the Grafana Multi-Location template instead.

What Gets Created

The app-database credentials, the admin password and the encryption key are prerequisite secrets you create yourself; the chart references them by name and never creates, modifies or deletes them.

Prerequisites

Three secrets must exist before you install: a dictionary secret with the app-database credentials, and two opaque secrets for the admin password and the encryption key. Grafana’s admin account is a human-facing login, and publicAccess.enabled defaults to true, so that account is reachable from the internet — none of these credentials is a Helm value, and none ever lands in the release. Secrets are org-level, so no --gvc flag is involved.
1

Create the app-database credentials secret

The bundled PostgreSQL initializes with these values and Grafana connects with them, in either database mode:
Set postgresHA.config.credentialsSecretName (HA, the default) or postgres.config.credentialsSecretName (single-instance) to this name. Secret names are org-wide, so give each release its own. PostgreSQL reads it only when its data directory is first initialized — see Rotating the Database Password.
2

Create the admin password secret

Choose your own strong password for the admin login and store it as the secret’s payload with encoding plain. Grafana reads it only when the admin account is first created:
Set admin.passwordSecretName to this name.
3

Create the encryption key secret

Generate a random key — Grafana encrypts every datasource credential it stores with it — and back the key up somewhere safe outside Control Plane. It is read on every boot for the life of the install and can never be rotated:
Set admin.secretKeySecretName to this name.
A missing prerequisite secret wedges the install rather than failing it. cpln helm install reports success while RELEASE_NAME-grafana sits at zero replicas with no containers and therefore no logs. The only place the missing secret is named is status.versions[].message:
The message reads The secret <name> no longer exists. Workload updates are paused until the secret is added or the reference to the secret removed. Create the missing secret and the deployment recovers on its own after several minutes, or run cpln workload force-redeployment RELEASE_NAME-grafana --gvc GVC_NAME to skip the wait.
The admin password and encryption key secrets have different lifecycles — read Admin Credentials before you delete or rotate either one. Optional features each need something created before you install:

Installation

Once the three secrets exist, install the template into your GVC, naming the secrets:
In single-instance mode set postgres.config.credentialsSecretName instead — see App Database.

UI

Browse, install, and manage templates visually

CLI

Manage templates from your terminal

Terraform

Declare templates in your Terraform configurations

Pulumi

Declare templates in your Pulumi programs

Configuration

Grafana Instance

replicas of 2 or more forms an HA tier over the shared app database and requires redis.enabled: true — see Alerting High Availability. image must provide /bin/bash: the chart wraps Grafana’s own entrypoint in a short boot script, and the official image does. User signup, anonymous access and upstream analytics and update checks are disabled by the template and are not configurable.

Admin Credentials

Both credentials live in opaque secrets you create before installing (see Prerequisites); neither value ever passes through Helm. They sit next to each other but their lifecycles are opposites, and deleting the wrong one is destructive: With admin.applyPassword: false the chart drops the password secret from both places it appeared — the container environment and the reveal policy grant — and the encryption-key reference is kept. The retirement sequence is under First Run.
The encryption-key secret is permanent. Deleting it wedges the workload on a missing secret reference, and changing its payload makes every datasource credential already saved in the app database undecryptable. Back the key up outside Control Plane instead of rotating it.

Datasources as Code

You can add datasources in the UI at any time. datasources.definitions additionally lets you declare them as code: each entry is a standard Grafana datasource provisioning entry, rendered verbatim into a provisioning file that the workload mounts at startup. Provisioned datasources are read-only in the Grafana UI — change the value and upgrade the release to change one. For catalog services in the same GVC, use their internal endpoints, substituting both the workload name and the GVC:

Datasource Credentials

datasources.credentialSecrets holds the credentials Grafana authenticates to each datasource with — not user access to Grafana. The provisioning file is plaintext, so credentials never go into it: create a dictionary secret before installing, list it under credentialSecrets, and write $KEY in the definition. Each listed key becomes an environment variable of that name, the chart grants the Grafana identity reveal on exactly that secret, and Grafana interpolates the variable when it loads the file.
The provisioning file itself only ever contains the literal $PG_PASSWORD placeholder — the value stays in the secret. Every entry needs a name and a non-empty keys list; the chart refuses to render otherwise.

Control Plane Metrics as a Datasource

Control Plane’s own metrics store is Prometheus-compatible and holds more than the console’s built-in dashboards display, including custom metrics your workloads expose and cost-relevant series such as egress, cross_zone_traffic and volume_set_*. Adding it as a datasource complements the built-in workload dashboards rather than replacing them. Follow the centralized metrics management guide for the endpoint details, then wire the token in as a datasource credential:
1

Create a service account with readMetrics

Create a service account granted the readMetrics permission on the org and generate a key for it. A workload’s built-in CPLN_TOKEN does not authenticate against the metrics endpoint.
2

Store the key in a dictionary secret

Put the key in a dictionary secret — for example under the key CPLN_METRICS_TOKEN — and list the secret under datasources.credentialSecrets.
3

Add the datasource definition

Point a prometheus-type datasource at https://metrics.cpln.io/metrics/org/YOUR_ORG and send the token as an Authorization header:

SMTP

Grafana sends alert notification emails through the SMTP server you configure; leave smtp.enabled: false if you use webhook, chat or other contact points instead. smtp.passwordSecretName is required whenever smtp.user is set — the chart refuses to render without it. The password stays in an opaque secret you create before installing, and the chart grants the Grafana identity reveal on exactly that secret:

Access

  • publicAccess.enabled — Serve the UI and API on the canonical *.cpln.app HTTPS endpoint (default). Everything behind the endpoint is gated by Grafana’s own login, and Grafana’s root_url is set to that endpoint automatically. Set it to false for an internal-only instance — external requests are then refused while in-GVC callers still reach it per internalAccess, and you reach the UI yourself with cpln port-forward (see Connecting).
  • internalAccess.type — Internal firewall scope of the Grafana workload:
Allow several minutes after changing an access knob before concluding it did not apply.
With publicAccess.enabled: false, Grafana’s root_url is not derived from a canonical endpoint, so absolute links in alert emails from an internal-only instance point at localhost. Keep public access on if you rely on links in email notifications.

App Database

Exactly one of the two database modes must be enabled — the chart enforces this at render and refuses to install otherwise.
In both modes the database credentials come from your prerequisite dictionary secret (username, password and database — see Prerequisites), named by the credentialsSecretName of the database you enable. Grafana is wired to the active database automatically: the HAProxy leader endpoint in HA mode, or the single instance directly. In HA mode the proxy must stay enabled — that endpoint is Grafana’s stable database address, and the chart refuses to render with it disabled. Everything else the bundled databases can do — volume autoscaling, PgBouncer pooling, etcd tuning — is documented on the postgres-highly-available and postgres pages and is set under the same postgresHA.* / postgres.* prefixes. Their backup settings are covered under Backing Up.

Alerting High Availability

Grafana holds no local state, so scaling the app tier is a matter of raising replicas. Alert evaluation is the one thing that must be coordinated: without coordination every replica would evaluate the same rule and send its own notification.
  • Coordination uses Grafana’s Redis Sentinel alerting HA mode against the Sentinel workload deployed by the bundled redis subchart (master name mymaster), not gossip-based peer clustering. Each replica registers under its own peer name, and exactly one notification is delivered per firing alert.
  • The chart refuses to render replicas >= 2 without redis.enabled: true.
  • Redis and Sentinel ship without authentication — the same-GVC internal firewall is the boundary. Enabling Redis or Sentinel authentication under redis.redis.auth or redis.sentinel.auth is rejected at render in this version.
  • Grafana Live push updates are per-instance in multi-replica mode; dashboard auto-refresh and alerting are unaffected.

Connecting

Verify the instance from inside its own container — the Grafana image ships curl:
With publicAccess.enabled: false there is no canonical endpoint; open a tunnel to the UI instead and browse to http://localhost:3000:
The Grafana HTTP API is available on the same addresses for scripted dashboard, datasource and alert-rule management.

First Run

Sign in on the public endpoint as admin.user (default admin) with the password from the admin password secret. Grafana ships no dashboards and no datasources — connect your own in the UI or as code. Grafana applies GF_SECURITY_ADMIN_PASSWORD only when the admin account is first created: on every later boot the secret’s value is ignored, so changing the secret never changes the login, and an upgrade never resets the password you set in the UI. Once you have logged in, the password secret is inert and you can retire it:
1

Log in once

Sign in as admin.user with the password from the secret and confirm you can reach the UI. Change the password in the UI if you wish — that is the only place it changes from now on.
2

Set applyPassword to false and upgrade

With admin.applyPassword: false, the chart drops the password secret from both the container environment and the reveal policy grant. The encryption-key reference is retained.
3

Delete the secret

Nothing references it any more, so the secret can be deleted. Your admin login keeps working; the account lives in the app database.
After that upgrade, logins may return 401 and /api/health may return 503 for a short while. The first helm upgrade after an install can restart the bundled app database even when no database value changed, and Grafana is unreachable while it comes back. This is not the toggle breaking authentication or resetting your password — do not roll back and do not recreate the password secret. Wait for /api/health to report 200 and log in again.

Operations

Backing Up

Database backups are optional and disabled by default. They cover the app database — the dashboards, users, alert rules and saved datasources that make up your Grafana. Enable them on whichever database is active and complete the storage setup for your provider before installing.
In HA mode postgresHA.backup.mode selects logical (a scheduled pg_dump run by the RELEASE_NAME-postgres-ha-backup cron workload) or wal-g (continuous WAL archiving from the database workload itself, with no separate backup workload). The single-instance database takes scheduled logical dumps only. Both come from the bundled postgres-highly-available and postgres subcharts. The steps below name the keys under postgresHA.backup.*; in single-instance mode set the same keys under postgres.backup.*.
1

Create a bucket

Create an S3 bucket. Set postgresHA.backup.aws.bucket and postgresHA.backup.aws.region to match.
2

Set up a Cloud Account

If you do not have one, create a Cloud Account for the AWS account holding the bucket. Set postgresHA.backup.aws.cloudAccountName to its name.
3

Create a bucket-scoped IAM policy

Create an IAM policy with the JSON below (replace YOUR_BUCKET), then set postgresHA.backup.aws.policyName to the policy’s name. No broad managed policy is required.

Restoring a Backup

A restore goes into the database, not into Grafana: everything that makes up your Grafana lives there, so restoring it brings back dashboards, users, datasources and alert rules together. The restore procedures belong to the bundled database — follow Restoring a Backup on the postgres-highly-available page in HA mode, or Restoring a Backup on the postgres page in single-instance mode, substituting this release’s database address (RELEASE_NAME-postgres-ha-proxy.GVC_NAME.cpln.local or RELEASE_NAME-postgres.GVC_NAME.cpln.local, port 5432) and the credentials from your database credentials secret:
Neither restore path has been rehearsed through this template. The logical path (a dump replayed with psql) and the WAL-G path are documented and verified on the database templates’ own pages; the WAL-G path there ends in a manual volume-set swap that this chart does not expose. Run a logical restore from a client that has psql, your cloud CLI and network access to the database address — a workload in the same GVC, for example — not from the Grafana container, which has neither tool. Rehearse the procedure before you rely on it.
Restore into a release that uses the same encryption key: admin.secretKeySecretName decrypts the datasource credentials stored in the dump, and a different key makes every saved datasource credential unreadable.

Rotating the Database Password

PostgreSQL keeps the password it was initialized with, and a running workload keeps the values it started with, so in single-instance mode a rotation takes three steps in order (in HA mode follow Rotating the Database Password in HA Mode instead):
  1. Change it in the database: ALTER ROLE DB_USERNAME PASSWORD 'NEW_PASSWORD'; (DB_USERNAME is the secret’s username; connect as that user from inside the GVC).
  2. Update the secret — export it, change only password, and apply it:
  3. Right away, redeploy Grafana:
Do not redeploy the database: PostgreSQL reads the secret only when it is first initialized, so restarting it only adds downtime. A new value in the secret without ALTER ROLE breaks Grafana’s login to the database.

Rotating the Database Password in HA Mode

The HA database (postgresHA) needs its own order. The replication role repl shares the password, and a member that restarts with the old password can be promoted to leader, which splits the cluster. Move the leader to member -0 first, so it restarts last, after every other member already has the new password. pgx runs a command in one member (LOCATION is your GVC’s location, DB_USERNAME the secret’s username):
Then force-redeploy RELEASE_NAME-grafana, which keeps the old password until it restarts. The database drops connections briefly at each switchover, and Grafana cannot open new connections from the ALTER ROLE until its own redeploy finishes, so rotate in a quiet window. If a member stays starting, or running without streaming, re-copy it from the leader. This copies the whole database, so it costs network traffic in proportion to its size:

Upgrading from 1.1.0 or Earlier

Version 1.2.0 moved the admin password and the encryption key out of Helm values (admin.password, admin.secretKey) into the two prerequisite opaque secrets described under Prerequisites, and stopped creating the chart-owned admin secret those values used to feed. Version 1.1.0 had already renamed resources.cpu and resources.memory to resources.maxCpu and resources.maxMemory. The old keys are not rejected at render — they are simply ignored — so remove them deliberately.
1

Create the encryption key secret with the key the install already uses

The value must be identical to your current admin.secretKey. Any other value makes every datasource credential already saved in the app database undecryptable:
Set admin.secretKeySecretName to this name.
2

Decide what to do with the admin password

The admin account already exists, and Grafana only applies the password when the account is first created, so the password secret’s content changes nothing for an existing install. Either set admin.applyPassword: false and create no password secret at all (the chart requires admin.passwordSecretName only while applyPassword is true), or create one as under Prerequisites and retire it later.
3

Remove the old keys and upgrade

Delete admin.password and admin.secretKey from your values (and rename resources.cpu / resources.memory if you still carry them), then upgrade. Every later migration note below also applies.

Upgrading from 1.2.0 or Earlier

Template versions before 1.2.1 did not compact the etcd cluster inside the bundled highly available database, so etcd’s backend grows with time alone and eventually goes read-only when it reaches its quota — taking PostgreSQL failover with it. Only installs running the HA database are affected (postgresHA.enabled, the default). See etcd History Compaction for the mechanism and symptoms. Upgrading to 1.2.1 or later turns 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.

Upgrading from 1.2.1 or Earlier

Version 1.3.0 removed the single-instance database credential keys postgres.config.username, postgres.config.password and postgres.config.database. postgres.backup.minio.accessKey and postgres.backup.minio.secretKey were replaced by postgres.backup.minio.credentialsSecretName at the same time. Carrying the old keys stops the upgrade at render, before anything is applied:
Put the three values into the app-database credentials secret under Prerequisites using the values the database was initialized with — PostgreSQL enforces the credentials written when the volume was first created, and different values just leave Grafana unable to authenticate — set postgres.config.credentialsSecretName to its name, and remove the three keys. If you back up to MinIO, create the dictionary secret described under Backing Up and set postgres.backup.minio.credentialsSecretName. The HA path, Redis and the admin and encryption-key secrets are unchanged by this step.

Upgrading from 1.3.0

Version 1.4.0 made the same change on the HA path: postgresHA.postgres.username, postgresHA.postgres.password and postgresHA.postgres.database were removed, and the bundled highly available database now reads the dictionary secret named by postgresHA.config.credentialsSecretName. Carrying the old block stops the upgrade at render:
Delete postgresHA.postgres.*, put the same values the cluster already has into the app-database credentials secret under Prerequisites, and set postgresHA.config.credentialsSecretName to its name. If you run several releases in one org, give each its own secret.

Upgrading from 1.4.0

Version 1.4.1 replaced postgresHA.backup.minio.accessKey and postgresHA.backup.minio.secretKey with postgresHA.backup.minio.credentialsSecretName. Only installs backing up the HA database to MinIO or another S3-compatible endpoint are affected; the old keys stop the upgrade at render:
Create the dictionary secret described under Backing Up with the same access key and secret key, set postgresHA.backup.minio.credentialsSecretName to its name, remove the two old keys, and upgrade.

Upgrading from 1.4.1

Version 1.5.0 removed postgres.credentials: the database credentials are now a prerequisite secret, the same one the bundled database template requires on its own. Carrying the old key forward fails the render. 1.4.1 created the database secret itself, so this upgrade deletes it. Before upgrading, create a secret under a new name holding the values your database was initialized with, not new ones, or Grafana loses its database connection:
Then upgrade with postgres.credentials removed and the credentialsSecretName of the mode you use (postgresHA.config.credentialsSecretName by default) set to that name. Your current values are in postgres.credentials of the 1.4.1 release, and also in the old secret (cpln secret reveal my-grafana-db-credentials -o yaml, or the name your release set in credentialsSecretName) until the upgrade deletes it. The upgrade restarts the bundled PostgreSQL too (it now reads the new secret), so Grafana can briefly lose its database; it reconnects by itself. The new version of a workload can also wait several minutes with is not allowed to reveal the secret in get-deployments while access to the new secret propagates; the previous version keeps serving meanwhile, and it clears by itself.

Upgrading from 1.5.0 or Earlier

1.5.1 moves the HA database (postgresHA) to controlplanecorporation/patroni-postgres:0.8, which carries pgvector 0.8.7 (CVE-2026-103484). The fix takes effect when the upgrade restarts the Patroni members; existing data needs nothing. Nothing limits that rollout, so expect a brief interruption of writes while the leader changes. Grafana itself restarts without a gap (the new replica serves before the old one stops); while the leader changes, Grafana can briefly answer valid users with 401. Upgrade in a quiet window. If you pinned postgresHA.image in your values, set it to the 0.8 tag: tags 0.1 to 0.7 are refused at render. Grafana does not use the vector extension; if you created it yourself in a database, \dx keeps reporting the old version until you run ALTER EXTENSION vector UPDATE; there, which only updates that record. Single-instance mode (postgres) is unchanged.

Scaling and Availability

  • replicas scales the Grafana tier; 2 or more requires redis.enabled: true — see Alerting High Availability. Database replicas are set separately with postgresHA.replicas; the single-instance database must stay at one replica.
  • The first helm upgrade after an install can restart the bundled database even when no database value changed, making the UI unavailable briefly; later upgrades that only touch the Grafana workload roll without downtime in multi-replica mode.
  • Rotating a prerequisite secret does not restart anything. After changing the value of a datasource-credential or SMTP secret, run cpln workload force-redeployment RELEASE_NAME-grafana --gvc GVC_NAME so the replicas pick it up. Never rotate the encryption key, and change the admin password in the UI rather than in its secret.
  • Confirm an upgrade is live from the deployment, not from the Helm output — cpln workload get-deployments RELEASE_NAME-grafana --gvc GVC_NAME -o yaml — especially after adding a new secret reference (enabling SMTP with a password, adding datasources.credentialSecrets, turning on backups).
  • Uninstall deletes every volume set the chart created — the database and, in HA mode, the consensus store — so the data does not survive a reinstall. The GVC is yours and is left alone, and the prerequisite secrets you created survive. Enable backups if the data matters.

Troubleshooting

Symptom: cpln helm install succeeded, but RELEASE_NAME-grafana shows no containers and cpln logs returns nothing.Cause: A prerequisite secret named in your values does not exist — the app-database credentials, the admin password, the encryption key, a datasource-credential secret or the SMTP password. The deployment pauses on the unresolved reference.Fix: Read the message naming the secret, then create it:
The message reads The secret <name> no longer exists. Workload updates are paused until the secret is added or the reference to the secret removed. Once the secret exists the deployment recovers on its own, or run cpln workload force-redeployment RELEASE_NAME-grafana --gvc GVC_NAME to skip the wait.
Symptom: Immediately after a helm upgrade — typically the one that sets admin.applyPassword: false — every login fails with 401 and /api/health returns 503.Cause: The first upgrade after an install can restart the bundled app database even when no database value changed, and Grafana cannot authenticate anyone while its database is unreachable. Nothing was reset — the default admin credentials fail in the same way.Fix: Wait for /api/health to return 200 and log in again. Do not roll back and do not recreate the password secret.
Symptom: helm install or helm upgrade stops before applying anything with grafana: replicas >= 2 requires redis.enabled=true — Redis Sentinel coordinates alerting HA; without it every replica evaluates alerts independently and sends duplicate notifications.Cause: replicas is 2 or more and redis.enabled is false.Fix: Set redis.enabled: true alongside replicas — see Alerting High Availability — or return replicas to 1.
Symptom: The install stops with grafana: enable exactly one database — set either postgresHA.enabled or postgres.enabled to true, not both, or the variant naming postgresHA.enabled (production) or postgres.enabled (dev/lightweight).Cause: Both postgresHA.enabled and postgres.enabled are true, or both are false.Fix: Enable exactly one — see App Database. To use the single-instance database, set postgresHA.enabled: false together with postgres.enabled: true.
Symptom: Datasources that worked before now fail their connection test, and Grafana cannot read the credentials it stored for them.Cause: The payload of the secret named by admin.secretKeySecretName was changed, or the secret was deleted and recreated with a different value. GF_SECURITY_SECRET_KEY is read on every boot to decrypt stored datasource credentials; a different key cannot decrypt them.Fix: Restore the original key from your backup of it and run cpln workload force-redeployment RELEASE_NAME-grafana --gvc GVC_NAME. If the original key is gone, re-enter the credentials of every saved datasource.
Symptom: helm upgrade stops at render with config.username was REMOVED in postgres 3.4.0, the postgres block was REMOVED in 2.5.0, backup.minio.accessKey and backup.minio.secretKey were REMOVED, or a message that postgres.credentials was removed.Cause: Your values still carry database or MinIO credential keys that moved in template versions 1.3.0, 1.4.0, 1.4.1 or 1.5.0. The existing release is left untouched and running.Fix: Follow the matching migration note above — Upgrading from 1.2.1 or Earlier, Upgrading from 1.3.0, Upgrading from 1.4.0 or Upgrading from 1.4.1 — keeping the credentials the database already uses.

Important Notes

  • Create the app-database credentials, admin-password and encryption-key secrets before installing. A missing prerequisite secret does not fail the install — the workload sits at zero replicas waiting on it, and status.versions[].message from cpln workload get-deployments RELEASE_NAME-grafana --gvc GVC_NAME -o yaml names it.
  • Rotating the database password needs ALTER ROLE plus a forced redeployment of Grafana (and a leader switchover first in HA mode) — see Rotating the Database Password.
  • The encryption-key secret is permanent: never delete it, never rotate it. Back the key up outside Control Plane.
  • The admin password applies on first boot only. Change it in the Grafana UI; then set admin.applyPassword: false, upgrade, and delete the password secret — see First Run.
  • This template ships no dashboards and no datasources. Control Plane’s console already provides built-in workload-metrics dashboards; this is the pane for data you own.
  • replicas >= 2 requires redis.enabled: true — the chart refuses to render multi-replica without the Sentinel tier that keeps one firing alert from producing one notification per replica.
  • Dashboards, users and alert rules live in the app database and survive Grafana restarts and replica replacement. Uninstall deletes the database volume sets — enable backups if the data matters.
  • Give each release its own app-database credentials secret — secret names are org-wide.
  • Prerequisite secrets you create are not owned by the release — they survive helm uninstall and must be deleted manually if you no longer need them.
  • Keep publicAccess.enabled: true if you rely on links in alert emails — an internal-only instance has no derived root_url and links point at localhost.
  • This template ships Grafana OSS only — Enterprise features such as fine-grained RBAC, reporting and query caching are not available.

External References

Grafana Documentation

Official Grafana documentation

Provisioning Datasources

Reference for the datasource provisioning entries used by this template

Alerting High Availability

How Grafana coordinates alert evaluation across replicas

Configuration Reference

Every Grafana setting and its environment-variable name

Grafana Template

View the source files, default values, and chart definition