Grafana Series - GaC 2 - Grafana Terraform Provider Basics

This article was last updated on: May 17, 2026 am

Series Articles

Overview

In the previous article, I summarized my tool selection:

  • Grafana Terraform provider
  • Jsonnet

Today, let’s start with a brief introduction to the Grafana Terraform provider.

Grafana Terraform Provider

The Grafana provider offers configuration management resources for Grafana. It is currently the officially maintained IaC tool with the most comprehensive coverage of Grafana resources.

The Grafana Terraform Provider is built on top of grafana-api-golang-client.

With the Grafana Terraform Provider, we can manage:

  • Alerting
    • Resources
      • grafana_contact_point
      • grafana_message_template
      • grafana_mute_timing
      • grafana_notification_policy
      • grafana_rule_group
  • Cloud
    • Resources
      • grafana_cloud_access_policy
      • grafana_cloud_access_policy_token
      • grafana_cloud_api_key
      • grafana_cloud_plugin_installation
      • grafana_cloud_stack
      • grafana_cloud_stack_api_key
      • grafana_cloud_stack_service_account
      • grafana_cloud_stack_service_account_token
      • grafana_machine_learning_holiday
      • grafana_machine_learning_job
      • grafana_machine_learning_outlier_detector
    • DataSources
      • grafana_cloud_ips
      • grafana_cloud_organization
      • grafana_cloud_stack
  • Grafana Enterprise
    • Resources
      • grafana_builtin_role_assignment
      • grafana_data_source_permission (AWS Managed Grafana also supports this feature)
      • grafana_report
      • grafana_role
      • grafana_role_assignment
      • grafana_team_external_group
  • Grafana OSS
    • Resources
      • grafana_annotation
      • grafana_api_key
      • grafana_dashboard
      • grafana_dashboard_permission
      • grafana_data_source
      • grafana_folder
      • grafana_folder_permission
      • grafana_library_panel
      • grafana_organization
      • grafana_organization_preferences
      • grafana_playlist
      • grafana_service_account
      • grafana_service_account_permission
      • grafana_service_account_token
      • grafana_team
      • grafana_team_preferences
      • grafana_user
    • DataSources
      • grafana_dashboard
      • grafana_dashboards
      • grafana_data_source
      • grafana_folder
      • grafana_folders
      • grafana_library_panel
      • grafana_organization
      • grafana_organization_preferences
      • grafana_team
      • grafana_user
      • grafana_users
  • OnCall
    • Omitted
  • SLO
    • Omitted
  • Synthetic Monitoring
    • Omitted

Hands-On

Since Grafana resources are relatively straightforward and independent — unlike AWS, which often involves complex interdependencies — the organization of Grafana TF code can be kept simple:

  • You can use a single AllInOne .tf file
  • Or split by resource type into something like:
1
2
3
4
5
6
7
8
├── dashboard.tf
├── datasource.tf
├── grafana-ds-info.auto.tfvars.json
├── jsonnet (jsonnet folder, all dashboard-related content goes here)
├── main.tf
├── outputs.tf
├── variables.tf
└── versions.tf

Let’s walk through the details using the second organizational structure.

Creating the Grafana Provider

In main.tf, create the Grafana Provider:

1
2
provider "grafana" {
}

If you only have one Grafana instance, the configuration above is all you need.

If you have multiple Grafana instances, you can use the alias parameter of the Grafana provider. Here’s how:

1
2
3
provider "grafana" {
alias = "aws-managed-grafana"
}

When using resources later, you can specify the provider to distinguish between instances, like this:

1
2
3
4
5
6
# provision folder
resource "grafana_folder" "play-grafana" {
provider = grafana.aws-managed-grafana
uid = "play-grafana"
title = "play-grafana"
}

│ 📝Notes:
│ For brevity in the following examples, we won’t show the multi-provider scenario.
│ Resources will not include the provider field.

To use Grafana with Terraform, you need to provide at least a URL and an API key. These can be supplied via environment variables as follows:

1
2
export GRAFANA_URL=https://<your-grafana-domain>/
export GRAFANA_AUTH=<your-grafana-apikey>

The value of GRAFANA_AUTH can be a Grafana API key, basic auth in the format username:password, or you can follow this link to create a Grafana API key.

Additionally, Grafana Cloud, Synthetic Monitoring, and Grafana OnCall each have their own dedicated API keys or tokens, which we won’t cover in detail here.

Creating a Grafana Organization

│ 📝Notes:

│ Since I primarily use AWS Managed Grafana, which only has a single default org (org 1) and doesn’t expose the ability to create multiple organizations, I rarely use this resource.

If you do need this resource, you can create an org.tf file with the following content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Create organization
resource "grafana_organization" "my_org" {
name = "my_org"
}

// Create resources within the organization
provider "grafana" {
alias = "my_org"
org_id = grafana_organization.my_org.org_id
}

resource "grafana_folder" "my_folder" {
provider = grafana.my_org

title = "Test Folder"
}

Creating DataSources

The required parameters for this resource vary depending on the selected data source type (specified via the type parameter).

These can be created in datasource.tf.

Below are simple examples for creating:

  • stackdriver
  • influxdb
  • cloudwatch
  • zabbix
  • ES
  • Prometheus
  • Jaeger

Stackdriver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
resource "grafana_data_source" "arbitrary-data" {
type = "stackdriver"
name = "sd-arbitrary-data"

json_data_encoded = jsonencode({
"tokenUri" = "https://oauth2.googleapis.com/token"
"authenticationType" = "jwt"
"defaultProject" = "default-project"
"clientEmail" = "client-email@default-project.iam.gserviceaccount.com"
})

secure_json_data_encoded = jsonencode({
"privateKey" = "-----BEGIN PRIVATE KEY-----\nprivate-key\n-----END PRIVATE KEY-----\n"
})
}

Influxdb

1
2
3
4
5
6
7
8
9
10
11
12
13
resource "grafana_data_source" "influxdb" {
type = "influxdb"
name = "myapp-metrics"
url = "http://influxdb.example.net:8086/"
basic_auth_enabled = true
basic_auth_username = "username"
database_name = influxdb_database.metrics.name

json_data_encoded = jsonencode({
authType = "default"
basicAuthPassword = "mypassword"
})
}

Cloudwatch

Creating with AKSK (Access Key / Secret Key):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
resource "grafana_data_source" "cloudwatch" {
type = "cloudwatch"
name = "cw-example"

json_data_encoded = jsonencode({
defaultRegion = "us-east-1"
authType = "keys"
})

secure_json_data_encoded = jsonencode({
accessKey = "123"
secretKey = "456"
})
}

Creating with an IAM role (external):

1
2
3
4
5
6
7
8
9
10
11
resource "grafana_data_source" "cloudwatch" {
type = "cloudwatch"
name = "example_cw"

json_data_encoded = jsonencode({
assumeRoleArn = "arn:aws:iam::<the-aws-id>:role/<...>"
authType = "ec2_iam_role"
defaultRegion = "us-east-1"
externalId = "<the-aws-id>"
})
}

Zabbix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
resource "grafana_data_source" "zabbix" {
type = "alexanderzobnin-zabbix-datasource"
name = "Zabbix-example"
url = "http://<zabbix-domain>/api_jsonrpc.php"

json_data_encoded = jsonencode({
trends = true
username = "Admin"
})

secure_json_data_encoded = jsonencode({
password = "Password"
})
}

│ 🐾 Note:

│ The Zabbix type is alexanderzobnin-zabbix-datasource.
│ A prerequisite is installing the Zabbix Grafana Plugin.

Jaeger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
resource "grafana_data_source" "jaeger-example" {
type = "jaeger"
name = "example_jaeger"
uid = "example_jaeger"
url = "http://<jaeger-domain>/trace/"

json_data_encoded = jsonencode({
"nodeGraph" : {
"enabled" : true
}
})
}

data "grafana_data_source" "jaeger-example" {
name = grafana_data_source.jaeger-example.name
uid = grafana_data_source.jaeger-example.uid
}

📝 The data “grafana_data_source” “jaeger-example” block above exposes the Jaeger DataSource UID for use by ES.

Of course, if you specify the uid directly when creating the Jaeger DataSource, as shown below, you can hardcode it when referencing it from other DataSources.

1
uid  = "example_jaeger"

ES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
resource "grafana_data_source" "elasticsearch-example" {
type = "elasticsearch"
name = "es_example"
uid = "es_example"
url = "http://<es_host>:9200"
// This is the ES index
database_name = "[example.*-]YYYY.MM.DD"

json_data_encoded = jsonencode({
esVersion = "6.0.0"

interval = "Daily"
includeFrozen = false
maxConcurrentShardRequests = 256
timeField = "@timestamp"

logLevelField = "level"
logMessageField = "_source"
dataLinks = [
{
datasourceUid = data.grafana_data_source.jaeger-example.uid
// Or datasourceUid = "example_jaeger"
field = "trace_id",
url = "${"$"}{__value.raw}"
}
]
})
}

There are a few things to note here:

  • database_name = “[example.*-]YYYY.MM.DD” — when the type is ES, database_name refers to the ES index name.
  • dataLinks — this links to the Jaeger DataSource via a data link: datasourceUid = data.grafana_data_source.jaeger-example.uid (the Jaeger DataSource was created in the previous section).
  • url = “${”$“}{__value.raw}” — pay special attention here. The actual value passed to Grafana is ${__value.raw}, but this also happens to be Terraform’s template/variable interpolation syntax. Writing it directly would cause Terraform to try to resolve it as a variable, resulting in an error. By using ${“$”} to escape the $, it becomes $ + {__value.raw}, producing the correct ${__value.raw} for Grafana.

Prometheus

Basic configuration:

1
2
3
4
5
6
7
8
9
10
resource "grafana_data_source" "prometheus" {
type = "prometheus"
name = "example_prom"
uid = "example_prom"
url = "http://my-instances.com"

json_data_encoded = jsonencode({
httpMethod = "POST"
})
}

Configuration for Mimir, the official Prometheus-compatible implementation by Grafana:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
resource "grafana_data_source" "prometheus" {
type = "prometheus"
name = "mimir"
url = "https://my-instances.com"
basic_auth_enabled = true
basic_auth_username = "username"

json_data_encoded = jsonencode({
httpMethod = "POST"
prometheusType = "Mimir"
prometheusVersion = "2.4.0"
})

secure_json_data_encoded = jsonencode({
basicAuthPassword = "password"
})
}

Creating Dashboards

In dashboard.tf, here’s an example of creating a dashboard:

1
2
3
resource "grafana_dashboard" "metrics" {
config_json = file("grafana-dashboard.json")
}

You can also create one like this:

1
2
3
4
5
6
resource "grafana_dashboard" "metrics" {
config_json = jsonencode({
title = "as-code dashboard"
uid = "ascode"
})
}

🐾 Note:

config_json is a String type that takes the complete Dashboard model JSON.

You can load it directly using file(“grafana-dashboard.json”).

As shown in the second example, jsonencode converts an object to a JSON string.

Summary

In this article, we covered the basics of the Grafana Terraform Provider. It’s fairly straightforward — we used it to:

  • Create a Provider
  • Create an Organization
  • Create Folders
  • Create various common DataSources
  • Create Dashboards

Very clear and intuitive. Hope this helps!

📚️ References