This is documentation for the next version of Grafana documentation. For the latest stable release, go to the latest version.
Use Terraform to provision secure values
Use the Terraform Grafana provider to create and manage secure values in Grafana Secrets Management as code. With Terraform, you keep secrets such as API keys, tokens, passwords, and certificates in version-controlled, declarative infrastructure alongside the rest of your Grafana stack.
This guide covers secure values, which you manage with the grafana_apps_secret_securevalue_v1beta1 resource.
By default, Grafana stores secure values in the built-in system keeper, which requires no setup.
If you configure an additional keeper and set it as the active keeper for your namespace, Grafana stores new secure values there automatically.
For more information, refer to Activate and use the keeper.
To store secure values in an external secret manager instead, provision a keeper.
To create and manage secure values with Terraform, you complete the following tasks:
- Create a service account token to authenticate the Terraform provider.
- Configure the Grafana provider.
- Define one or more secure values.
- Run
terraform applyto provision your secure values.
Before you begin
To provision secure values with Terraform, you need the following:
- A Grafana Cloud instance that you can reach from the machine that runs Terraform.
- Permission to create service account tokens.
You also need the following tools at the minimum supported versions:
If you don’t have the Terraform CLI, refer to the Terraform install documentation.
The grafana/grafana provider downloads automatically when you run terraform init in Provision the secure value with Terraform.
Note
All of the following Terraform configuration files should be saved in the same directory.
Create a service account token
Terraform authenticates against Grafana with a service account token.
To create a service account and token, follow these steps:
Create a service account and token in Grafana. To create them, refer to Service account tokens. You can also refer to Create and manage a Grafana Cloud stack using Terraform to set up a service account and token.
Make sure the service account has the role-based access control (RBAC) actions needed to manage secure values:
secret.securevalues:createsecret.securevalues:readsecret.securevalues:writesecret.securevalues:delete
If you also provision a keeper (refer to Provision a keeper), the service account also needs the keeper actions, which cover activating a keeper:
secret.keepers:createsecret.keepers:readsecret.keepers:writesecret.keepers:delete
Copy the token and store it securely, because you can’t view it again after you leave the page.
Configure the Grafana provider
In your Terraform working directory, create a main.tf file:
terraform {
required_version = ">= 1.11.0"
required_providers {
grafana = {
source = "grafana/grafana"
version = ">= 4.26.0"
}
}
}
variable "grafana_auth" {
type = string
sensitive = true
}
provider "grafana" {
url = "<GRAFANA_URL>"
auth = var.grafana_auth
# Required for app-platform resources, including secure values.
stack_id = <STACK_ID>
}Replace the placeholders as follows:
<GRAFANA_URL>is the URL of your Grafana instance, for examplehttps://my-org.grafana.net.<STACK_ID>is your numeric Grafana Cloud stack ID, for example123456.To find your stack ID, open the Grafana Cloud Portal, select your stack, and read the numeric Instance ID. That value is your
stack_id.You can also retrieve it with a
GETrequest tohttps://grafana.com/api/orgs/<ORG_SLUG>/instances, where<ORG_SLUG>is your Grafana Cloud organization name. The numericidfield in the response is the stack ID. This Cloud API endpoint authenticates with a Cloud Access Policy token, which is separate from the service account token that Terraform uses.
Provide each input variable through a matching environment variable that follows the Terraform TF_VAR_<variable_name> naming convention.
The part after TF_VAR_ is the name of the variable in your configuration: Terraform maps TF_VAR_grafana_auth to the grafana_auth variable, TF_VAR_external_api_key to external_api_key, and so on.
You can also use a secrets manager or a terraform.tfvars file.
Don’t set variables in a file that you commit.
For other authentication options, refer to the Grafana provider documentation. For end-to-end stack management, refer to Create and manage a Grafana Cloud stack using Terraform.
Define a secure value
The following example stores an API key as a secure value and authorizes Synthetic Monitoring to decrypt it.
Add it to main.tf, or to any other .tf file in the same working directory.
Terraform loads all .tf files in the directory together.
variable "external_api_key" {
type = string
sensitive = true
ephemeral = true # Keeps the value out of state and plan files.
}
resource "grafana_apps_secret_securevalue_v1beta1" "external_api_key" {
metadata {
uid = "external-api-key" # The secure value's name.
}
spec {
description = "External API key"
value = var.external_api_key
decrypters = ["synthetic-monitoring"]
}
}Keep the following points in mind for this example:
metadata.uidis the secure value’s name in Grafana. The resource also exposes a separate, read-onlymetadata.uuid, which is the Grafana-generated identifier rather than the name.spec.descriptionis required and limited to 25 characters. Longer values fail at plan with a Terraform length validation error.spec.valueis a write-only argument that requires Terraform 1.11 or later. Terraform sends the plaintext to Grafana on apply, but never writes it to state, plan output, or any other on-disk artifact. Combine it with anephemeral, sensitive variable, as in the preceding example, so the value also stays out ofterraform planoutput.- You don’t pass a
namespace. The provider derives it fromstack_id. - You don’t pass a keeper name. Grafana stores the value in whichever keeper is the active keeper for your namespace, which is the system keeper by default.
Schema reference
The full schema is on the Terraform Registry. The following tables describe the fields you use for secure values.
The metadata block contains the following fields:
The spec block contains the following fields:
The options block is optional and contains the following fields:
At the top level, the resource exposes a read-only id field, which Grafana derives from the secure value’s UUID.
Decrypters
The decrypters list controls which Grafana services can decrypt a secret. If a service isn’t in the list, it can’t read the plaintext value, even if it has access to the secure value’s metadata. An empty or unset list means no service can decrypt the value.
Grafana supports the following decrypters:
Grant the minimum set of decrypters required for your use case.
Provision the secure value with Terraform
To apply the configuration, follow these steps:
Initialize the working directory. This downloads the Grafana provider.
terraform initSet the environment variables, then preview and apply the changes:
export TF_VAR_grafana_auth='<TOKEN>' export TF_VAR_external_api_key='<SECRET>' terraform plan terraform applyReplace the placeholders as follows:
<TOKEN>is the service account token you created in Create a service account token.<SECRET>is the plaintext secret value that Terraform stores as the secure value.
This keeps secrets out of Terraform files. If your shell writes history, prefer a secure secret-injection flow, such as CI/CD secrets, a secret manager, or a nested shell with history disabled.
Terraform shows the execution plan and asks for confirmation:
Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value:After you confirm, Terraform creates the secure value:
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
If a secure value with the same metadata.uid already exists in the namespace, for example from a previous apply or one created outside Terraform, the apply fails with HTTP 409 - AlreadyExists.
To bring the existing secure value under Terraform management instead of recreating it, import it.
To replace it on apply, set overwrite = true in the resource’s options block.
Verify that the plaintext stays out of state
Because spec.value is write-only, Terraform never writes the plaintext to state.
It writes only the value_hash.
To confirm this, search your local state file for the plaintext:
grep -c '<SECRET_VALUE>' terraform.tfstateReplace <SECRET_VALUE> with the plaintext secret you provisioned.
The search returns no matches:
0The stored spec shows value as null and retains only the hash:
"spec": {
"decrypters": ["synthetic-monitoring"],
"description": "External API key",
"ref": null,
"value": null,
"value_hash": "65f087ba121c13dec3fa0cb7697d4309ed5e624ba528a758bb9071bee220825a"
}To verify the result, open Administration > Secrets Management in Grafana, or call the Secrets Management HTTP API directly.
The namespace is stacks-<STACK_ID>:
curl -H "Authorization: Bearer <TOKEN>" \
"<GRAFANA_URL>/apis/secret.grafana.app/v1beta1/namespaces/stacks-<STACK_ID>/securevalues/external-api-key"The response includes the secure value’s metadata, its spec with description and decrypters, and the keeper that stores it.
It never includes the plaintext value:
{
"kind": "SecureValue",
"apiVersion": "secret.grafana.app/v1beta1",
"metadata": {
"name": "external-api-key",
"namespace": "stacks-<STACK_ID>",
"annotations": {
"grafana.app/managedBy": "terraform",
"grafana.app/managerId": "grafana-terraform-provider"
}
},
"spec": {
"description": "External API key",
"decrypters": ["synthetic-monitoring"]
},
"status": {
"version": 1,
"keeper": "system"
}
}There is no value field in the response. The API doesn’t return decrypted secret contents.
Rotate a secure value
To rotate a secret, change the value, or the variable that feeds it, and run terraform apply again.
The provider computes a SHA-256 hash of your configured spec.value at plan time and stores it in value_hash. When the hash changes, Terraform plans an update. When the hash doesn’t change, Terraform plans no change. This lets you rotate secrets without storing plaintext in state.
resource "grafana_apps_secret_securevalue_v1beta1" "external_api_key" {
metadata {
uid = "external-api-key"
}
spec {
description = "External API key"
value = var.external_api_key # Supply the new value here.
decrypters = ["synthetic-monitoring"]
}
}When the value changes, the recomputed value_hash drives an in-place update:
~ spec {
~ value_hash = (sensitive value)
}
Plan: 0 to add, 1 to change, 0 to destroy.If you apply again without changing the value, the hash matches and Terraform plans no change:
No changes. Your infrastructure matches the configuration.Rotation considerations
Keep the following points in mind when you rotate secure values:
valueis write-only. The Grafana API never returns the plaintext, and the provider never reads it back. Onlyvalue_hashis stored in state.- Out-of-band value changes aren’t directly detectable. Because the plaintext is write-only, Terraform can’t read the current secret contents from Grafana. Keep Terraform as your source of truth for rotations.
- You can’t switch a secure value between
valueandrefafter creation. The API rejects updates that try to change mode. To change mode, destroy and recreate the resource. metadata.uidis immutable. Changing it forces Terraform to destroy and recreate the secure value.decryptersordering is significant in Terraform. Keep a stable order in your configuration to avoid noisy plans.- An update is a full replace with
PUT, not a patch. Always providedescriptionanddecrypterstogether withvalueorrefon update. If you omit them, the new spec replaces the old one in full.
Handle the secret value safely
Warning
Never check plaintext secret values into Terraform configuration.
Instead, use one of the following approaches:
- A
sensitiveorephemeralTerraform variable populated from aTF_VAR_*environment variable. - A CI/CD secret injected at apply time.
- A dedicated secret store, such as HashiCorp Vault or AWS Secrets Manager, referenced from Terraform with the appropriate provider data source.
Import an existing secure value
The grafana_apps_secret_securevalue_v1beta1 resource supports import.
The import ID is the secure value name, which is the value of metadata.uid:
terraform import grafana_apps_secret_securevalue_v1beta1.external_api_key external-api-keyAfter you import a secure value, keep the following points in mind:
spec.valueis still write-only, and Terraform never imports it into state.- For value-backed secure values, set
spec.valuein configuration before you apply. The first managed apply sets or rotates the value to whatever you configure. - For reference-backed secure values, set
spec.refin configuration to match the existing reference. - Because you must set exactly one of
valueorref, your configuration must include one of them after import.
Provision a keeper
A keeper defines where Grafana stores the encrypted material for your secure values. By default, Grafana uses the built-in system keeper, which encrypts values at rest in the Grafana database and requires no setup.
To store secrets in an external secret manager instead, provision a keeper with the grafana_apps_secret_keeper_v1beta1 resource.
Grafana currently supports AWS Secrets Manager as a keeper.
Grafana connects to AWS Secrets Manager through cross-account role assumption with AWS Security Token Service, and never stores your AWS credentials. You create an AWS Identity and Access Management (IAM) role in your AWS account that Grafana can assume, and the keeper references that role.
Keeper requirements
In addition to the requirements for secure values, you need the following:
- An AWS account with AWS Secrets Manager enabled in your target region.
- An IAM role named exactly
grafana-secrets-managerthat Grafana can assume, along with its role ARN and external ID. Grafana can assume only a role with that name. For the IAM role and trust policy that the role requires, refer to Configure an AWS Secrets Manager keeper.
Define a keeper
The following example provisions an AWS Secrets Manager keeper. Add it to main.tf, or to any other .tf file in the same working directory.
resource "grafana_apps_secret_keeper_v1beta1" "aws_secrets_manager" {
metadata {
uid = "aws-secrets-manager" # The keeper's name.
}
spec {
description = "AWS Secrets Manager keeper"
aws {
region = "<REGION>"
assume_role {
assume_role_arn = "arn:aws:iam::<ACCOUNT_ID>:role/grafana-secrets-manager"
external_id = "<EXTERNAL_ID>"
}
}
}
}Replace the placeholders as follows:
<REGION>is the AWS region that hosts your secrets, for exampleus-east-1.<ACCOUNT_ID>is the ID of the AWS account that hosts your secrets.<EXTERNAL_ID>is the external ID that your IAM role’s trust policy requires.
The role name in assume_role_arn must be grafana-secrets-manager. Grafana can’t assume a role with any other name.
Apply the keeper with the same terraform apply workflow you use for a secure value, described in Provision the secure value with Terraform.
Unlike a secure value, a keeper has no secret value, so it needs no ephemeral variable of its own: TF_VAR_grafana_auth for the provider is the only variable you export.
Creating a keeper doesn’t make it active, so don’t create secure values in the same apply until you add the activation resource in Activate and use the keeper and make new secrets depend on it.
Keeper schema reference
The full schema is on the Terraform Registry. The following tables describe the fields you use for a keeper.
The metadata block contains the following fields:
The spec block contains the following fields:
The aws block contains the following fields:
The assume_role block contains the following fields:
At the top level, the resource exposes a read-only active status that’s true when the keeper is the active keeper for the namespace.
Activate and use the keeper
Creating a keeper with Terraform doesn’t make it active. Each namespace has at most one active keeper, which determines where Grafana stores new secure values.
To set a keeper as active, use the grafana_apps_secret_keeper_activation_v1beta1 resource and reference the keeper’s uid:
resource "grafana_apps_secret_keeper_activation_v1beta1" "aws_secrets_manager" {
metadata {
uid = grafana_apps_secret_keeper_v1beta1.aws_secrets_manager.metadata.uid
}
}The activation resource already depends on the keeper because it references the keeper’s uid.
Any secure value that should use this keeper must depend on the activation, otherwise Terraform can create the secret while the system keeper is still active:
resource "grafana_apps_secret_securevalue_v1beta1" "external_api_key" {
metadata {
uid = "external-api-key"
}
spec {
description = "External API key"
value = var.external_api_key
decrypters = ["synthetic-monitoring"]
}
depends_on = [grafana_apps_secret_keeper_activation_v1beta1.aws_secrets_manager]
}Keep the following points in mind for this resource:
- Because a namespace has only one active keeper, applying an activation for a different keeper switches which keeper is active.
- Running
terraform destroyon the activation resource reverts the namespace to the built-in system keeper. Destroying the keeper resource doesn’t deactivate it first; destroy the activation resource as well.
You can also set the active keeper outside Terraform, in the Grafana UI under Administration > Secrets Management.
After the keeper is active, Grafana routes new secure values to it automatically.
A value-backed secure value like the one in Define a secure value needs no field changes to use the active keeper, but it still needs depends_on if you create it in the same configuration as the activation.
Destroying a value-backed secure value that lives in AWS Secrets Manager deletes the AWS secret immediately, with no recovery window.
To reference a secret that already exists in AWS Secrets Manager instead of sending a new value, use ref in place of value.
Set ref to the AWS secret name or the full ARN, not a path inside Grafana:
resource "grafana_apps_secret_securevalue_v1beta1" "db_password" {
metadata {
uid = "db-password"
}
spec {
description = "Production DB password"
ref = "grafana-secrets-manager/stacks-<STACK_ID>/db-password/1"
decrypters = ["synthetic-monitoring"]
}
depends_on = [grafana_apps_secret_keeper_activation_v1beta1.aws_secrets_manager]
}Replace <STACK_ID> with the same numeric stack ID you set on the provider.
You can also set ref to the full secret ARN, for example arn:aws:secretsmanager:<REGION>:<ACCOUNT_ID>:secret:grafana-secrets-manager/stacks-<STACK_ID>/db-password/1-AbCdEf.
If you use ref while the system keeper is active, the API returns the following error:
tried to create secure value using reference with system keeper, references can only be used with 3rd party keepersSummary
In this guide, you created a service account token, configured the Grafana provider, and provisioned a secure value with Terraform. You also learned how to rotate and import secure values, and how to provision and activate a keeper to store secrets in an external secret manager.
To learn more about managing Grafana Cloud using Terraform, refer to the Grafana provider documentation.
Next steps
- For the full secure value schema and more examples, refer to
grafana_apps_secret_securevalue_v1beta1on the Terraform Registry. - For the full keeper schema, refer to
grafana_apps_secret_keeper_v1beta1on the Terraform Registry. - For the keeper activation resource, refer to
grafana_apps_secret_keeper_activation_v1beta1on the Terraform Registry. - For concepts, permissions, and limits, refer to Manage secrets.
- For the underlying HTTP API, refer to the Secrets Management API.
- For more about write-only arguments, refer to Ephemeral values in the Terraform documentation.


