Grafana as code: grafana.ini and Terraform

Grafana · Advanced · 12 min

Reproducible Grafana means every environment can be rebuilt from a repo, and every change is a reviewed diff.

Server configuration is grafana.ini (defaults in defaults.ini, overrides in custom.ini or /etc/grafana/grafana.ini). Sections you touch most: [server] (root_url, domain, serve_from_sub_path), [database], [security] (admin_password, secret_key, cookie flags), [auth.*] providers, [users] (default_theme, viewers_can_edit), [dashboards] (min_refresh_interval), [unified_alerting], [smtp], [log]. Any key maps to GF_<SECTION>_<KEY> in upper case, so containers are configured with environment variables, and values can reference $__env{NAME} or $__file{/run/secrets/x} to keep secrets out of the file. The effective configuration is visible under Administration, General, Settings.

For the objects inside Grafana, file provisioning works for data sources and dashboards, but the Terraform provider (grafana/grafana) covers everything with state and diffs:

  • grafana_folder, grafana_dashboard (with config_json = file("...")), grafana_library_panel.
  • grafana_data_source with json_data_encoded and secure_json_data_encoded.
  • grafana_contact_point, grafana_notification_policy, grafana_mute_timing, grafana_rule_group.
  • grafana_team, grafana_user, grafana_folder_permission, grafana_service_account and its tokens.

Authenticate the provider with a service account token of a dedicated admin account. The UI helps you bootstrap: alert rules, contact points and policies have Export buttons that emit YAML, JSON or HCL; dashboards export JSON that you commit next to the Terraform.

The same resources can be driven by the HTTP API from scripts, by Grizzly (a CLI that applies Grafana resources from Jsonnet or YAML), or by Git Sync, where Grafana itself reads dashboards from a repository and turns UI edits into pull requests. Whatever the tool, the rule is the same: the repo is the source of truth and the UI is read-only for managed objects.

Example

terraform {
  required_providers {
    grafana = { source = "grafana/grafana", version = "~> 3.0" }
  }
}

provider "grafana" {
  url  = "https://grafana.example.com"
  auth = var.grafana_sa_token           # service account token, Admin role
}

resource "grafana_data_source" "prom" {
  type = "prometheus"
  name = "Prometheus"
  uid  = "prom-main"
  url  = "http://prometheus:9090"
  json_data_encoded = jsonencode({ timeInterval = "15s" })
}

resource "grafana_folder" "services" {
  title = "Services"
}

resource "grafana_dashboard" "api" {
  folder      = grafana_folder.services.uid
  config_json = file("${path.module}/dashboards/api-overview.json")
}

resource "grafana_contact_point" "api_slack" {
  name = "api-slack"
  slack {
    url   = var.slack_webhook
    title = "{{ .CommonLabels.alertname }}"
  }
}

Remember

  • grafana.ini keys map to GF_SECTION_KEY environment variables; use $__env and $__file for secrets
  • The Terraform provider manages folders, dashboards, data sources, alerting and permissions with diffs
  • Export buttons in the UI give you the YAML/HCL to start from

Watch out for

  • Editing a Terraform-managed dashboard in the UI; the next apply reverts it and the change is lost.

Check yourself

  1. Which environment variable sets [server] root_url in a container?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: grafana.com/docs/grafana/latest/alerting/set-up/provision-alerting-resources/terraform-provisioning/

Builds on

Unlocks

Nothing builds on this yet.