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(withconfig_json = file("...")),grafana_library_panel.grafana_data_sourcewithjson_data_encodedandsecure_json_data_encoded.grafana_contact_point,grafana_notification_policy,grafana_mute_timing,grafana_rule_group.grafana_team,grafana_user,grafana_folder_permission,grafana_service_accountand 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.