Terraform
Learn how to use the Doppler Terraform Provider to manage your Doppler workplace in your Terraform code
This guide will show you how to use Doppler to securely read and write secrets in Terraform.
Prerequisites
- You have created a project in Doppler
- You are familiar with using Terraform to manage infrastructure
Rename Actions Not Supported
The Doppler provider doesn't currently support rename actions. If you attempt to rename a project or config from Terraform, then what will happen is the original project or config will be deleted and then recreated with the new name. This means that any secrets managed outside of Terraform and any syncs that had been setup will also have been destroyed and need to be recreated.
If you must rename a resource, you should remove the old resources from your Terraform code and then remove those resources from the Terraform state. You can then add resource blocks for the newly named resources and import them back into your Terraform state. The resource ID format used for Doppler objects in Terraform is described at the bottom of each resource in the Terraform registry docs.
create_before_destroy lifecycle argument
Do not use the
create_before_destroy
lifecycle argument with Doppler resources. Unlike other provider resources where you can create a new resource alongside an existing resource, Doppler's resources don't behave in this fashion. There can only be oneSTRIPE_API_KEY
secret in a config, or oneci
environment. If create_before_destroy is used, it will first update the existing resource (rather than create a new one) and will subsequently destroy the only existing copy. After which it will report that everything succeeded, even though the end state is not the desired state.
Secure your Terraform state
If you're using the Doppler Terraform provider for managing secrets, it's important to note that secret values will end up being stored in your Terraform state files. All sensitive fields are marked "sensitive" in Terraform, meaning that they can't be printed to console without jumping through some explicit hoops, but the data itself is still stored in the state file. This is the same behavior you'll find in the AWS Secrets Manager, GCP Secret Manager, and HashiCorp Vault providers. We recommend that you follow HashiCorp's recommendation when it comes to sensitive state data and ensure that your state files are secure β including storing them somewhere with encryption at rest.
Passing Doppler Secrets as Input Variables
The Doppler CLI can be used to pass secrets directly to Terraform without the need for a provider at all.
# Automatically converts Doppler secrets to lowercase Terraform variables
doppler run --name-transformer tf-var -- terraform plan
The --name-transformer tf-var
flag will transform the names of your Doppler secrets into the Terraform Environment Variable format.
For example, a variable named API_KEY
will be converted into TF_VAR_api_key
by the name transformer and Terraform will automatically find this value if you have a Terraform variable named api_key
.
Read on to learn about the Doppler Terraform provider and more complex use cases.
Provider Overview
The Doppler provider is fully compatible with OpenTofu (previously named OpenTF), the open-source fork of Terraform. You can replace
terraform
withtofu
in any example commands to accomplish the same outcomes.
There are two main ways to use the Doppler Terraform Provider:
- Reading Secrets
- Fetch secrets from Doppler and use them in your Terraform configuration
- Use the
doppler_secrets
Data Source
- Managing Resources
- Create, update, and delete Doppler secrets, projects, environments, configs, or service tokens from Terraform
- Use the
doppler_secret
,doppler_project
,doppler_environment
, anddoppler_service_token
Resources
- Managing Projects
Reading Secrets
In this example, we'll use the doppler_secrets
data source to read secrets from a config:
# Install the Doppler provider
terraform {
required_providers {
doppler = {
source = "DopplerHQ/doppler"
}
}
}
# Define a variable so we can pass in our token
variable "doppler_token" {
type = string
description = "A token to authenticate with Doppler"
}
# Configure the Doppler provider with the token
provider "doppler" {
doppler_token = var.doppler_token
}
# Define our data source to fetch secrets
data "doppler_secrets" "this" {}
# Access individual secrets
output "stripe_key" {
# nonsensitive used for demo purposes only
value = nonsensitive(data.doppler_secrets.this.map.STRIPE_KEY)
}
To read Doppler secrets from Terraform, we just need to create a Service Token to provide read-only access to a specific config.
There are several ways to pass variables to Terraform, but for now, we can just provide our Doppler token when prompted by Terraform.
First, run terraform init
to setup your Terraform project, then run terraform apply
to test the configuration:
Terraform used the Doppler provider to fetch the secrets from your config.
All of the secrets that you load from Doppler will be loaded into Terraform as strings. You can use Terraform's built-in parsing functions to convert secrets to their Terraform types:
# Use `tonumber` and `tobool` to parse string values into Terraform primitives
output "max_workers" {
value = nonsensitive(tonumber(data.doppler_secrets.this.map.MAX_WORKERS))
}
# JSON values can be decoded direcly in Terraform
# e.g. FEATURE_FLAGS = `{ "AUTOPILOT": true, "TOP_SPEED": 130 }`
output "json_parsing_values" {
value = nonsensitive(jsondecode(data.doppler_secrets.this.map.FEATURE_FLAGS)["TOP_SPEED"])
}
Reading Secrets from Multiple Projects
In this example, we use multiple Doppler providers with aliases to access secrets from two separate configs. This can be useful when you need to use multiple access tokens that are scoped to a single project and config in your Terraform run.
terraform {
required_providers {
doppler = {
source = "DopplerHQ/doppler"
}
}
}
variable "doppler_token_dev" {
type = string
description = "A token to authenticate with Doppler for the dev config"
}
variable "doppler_token_prd" {
type = string
description = "A token to authenticate with Doppler for the prd config"
}
provider "doppler" {
doppler_token = var.doppler_token_dev
alias = "dev"
}
provider "doppler" {
doppler_token = var.doppler_token_prd
alias = "prd"
}
data "doppler_secrets" "dev" {
provider = doppler.dev
}
data "doppler_secrets" "prd" {
provider = doppler.prd
}
output "port-dev" {
value = nonsensitive(data.doppler_secrets.dev.map.PORT)
}
output "port-prd" {
value = nonsensitive(data.doppler_secrets.prd.map.PORT)
}
Managing Secrets
In this example, we'll use the doppler_secret
resource to write to secrets in our Doppler config:
# Install the Doppler provider
terraform {
required_providers {
doppler = {
source = "DopplerHQ/doppler"
}
}
}
# Define a variable so we can pass in our token
variable "doppler_token" {
type = string
description = "A token to authenticate with Doppler"
}
# Configure the Doppler provider with the token
provider "doppler" {
doppler_token = var.doppler_token
}
# Generate a random password
resource "random_password" "db_password" {
length = 32
special = true
}
# Save the random password to Doppler
resource "doppler_secret" "db_password" {
project = "rocket"
config = "dev"
name = "DB_PASSWORD"
value = random_password.db_password.result
}
# Access the secret value
output "resource_value" {
# nonsensitive used for demo purposes only
value = nonsensitive(doppler_secret.db_password.value)
}
To write Doppler secrets, we'll need to provide a Doppler Personal Token in our Terraform configuration. You can generate a new Personal Token from the Doppler dashboard on the "Tokens" page.
Similar to our example for reading secrets, we'll run terraform init
and then terraform apply
to test the configuration:
Terraform generated a new password and saved it to our Doppler config as DB_PASSWORD.
Terraform will still store all of this data in Terraform state, but having your secrets in Doppler allows for much easier access to these secrets for all developers on your team.
Managing Projects, Environments, and Configs
In this example, we'll use the doppler_project
, doppler_environment
, and doppler_config
resources to manage a project in our Doppler workplace:
# Install the Doppler provider
terraform {
required_providers {
doppler = {
source = "DopplerHQ/doppler"
}
}
}
# Define a variable so we can pass in our token
variable "doppler_token" {
type = string
description = "A token to authenticate with Doppler"
}
# Configure the Doppler provider with the token
provider "doppler" {
doppler_token = var.doppler_token
}
# Create and manage your project
resource "doppler_project" "rocket" {
name = "rocket"
description = "To the moon and back."
}
# Create and manage your environments
resource "doppler_environment" "rocket_dev" {
project = doppler_project.rocket.name
name = "Development"
slug = "dev"
}
resource "doppler_environment" "rocket_stage" {
project = doppler_project.rocket.name
name = "Staging"
slug = "stage"
}
resource "doppler_environment" "rocket_ci" {
project = doppler_project.rocket.name
name = "CI"
slug = "ci"
}
resource "doppler_environment" "rocket_prod" {
project = doppler_project.rocket.name
name = "Production"
slug = "prod"
}
# Create and manage any branch configs you may want
resource "doppler_config" "rocket_ci_github" {
project = doppler_project.rocket.name
environment = doppler_environment.rocket_ci.slug
name = "${doppler_environment.rocket_ci.slug}_github"
}
resource "doppler_config" "rocket_ci_circleci" {
project = doppler_project.rocket.name
environment = doppler_environment.rocket_ci.slug
name = "${doppler_environment.rocket_ci.slug}_circleci"
}
We define a new Doppler project named rocket
and set up four Environments in it (dev
, stage
, ci
, and prod
). We then specify two Branch Configs to manage under the ci
environment.
Similar to previous examples, run terraform init
and then terraform apply
to test the configuration.
Managing Service Tokens
In this example, we'll use the doppler_service_token
resource to manage a service token in our Doppler workplace:
# Install the Doppler provider
terraform {
required_providers {
doppler = {
source = "DopplerHQ/doppler"
}
}
}
# Define a variable so we can pass in our token
variable "doppler_token" {
type = string
description = "A token to authenticate with Doppler"
}
# Configure the Doppler provider with the token
provider "doppler" {
doppler_token = var.doppler_token
}
# Create and manage your project
resource "doppler_project" "rocket" {
name = "rocket"
description = "To the moon and back."
}
# Create and manage your environments
resource "doppler_environment" "rocket_ci" {
project = doppler_project.rocket.name
name = "CI"
slug = "ci"
}
# Create and manage your service token
resource "doppler_service_token" "rocket_ci_token" {
project = doppler_environment.rocket_ci.project
config = doppler_environment.rocket_ci.slug
name = "Rocket CI Token"
access = "read"
}
# Service token key available as `doppler_service_token.rocket_ci_token.key
We define a new Doppler project named rocket
and set up a single ci
environment. We then specify a service token for the rocket
project under the ci
environment. In this case, we're referencing the environment slug because this is the name for the root config in that environment. If you had Branch Configs under that environment, then you would want to reference a doppler_config
resource for that config.
Similar to previous examples, run terraform init
and then terraform apply
to test the configuration.
Managing Integrations and Syncs
In this example, we'll use the doppler_integration_aws_secrets_manager
and doppler_secrets_sync_aws_secrets_manager
resources to setup a secrets sync from Doppler to AWS Secrets Manager.
resource "aws_iam_role" "doppler_secrets_manager" {
name = "doppler_secrets_manager"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = "sts:AssumeRole"
Principal = {
AWS = "arn:aws:iam::299900769157:user/doppler-integration-operator"
},
Condition = {
StringEquals = {
"sts:ExternalId" = "<YOUR_WORKPLACE_SLUG>"
}
}
},
]
})
inline_policy {
name = "doppler_secret_manager"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:PutSecretValue",
"secretsmanager:CreateSecret",
"secretsmanager:DeleteSecret",
"secretsmanager:TagResource",
"secretsmanager:UpdateSecret"
]
Effect = "Allow"
Resource = "*"
# Limit Doppler to only access certain secret names
},
]
})
}
}
resource "doppler_integration_aws_secrets_manager" "prod" {
name = "Production"
assume_role_arn = aws_iam_role.doppler_secrets_manager.arn
}
resource "doppler_secrets_sync_aws_secrets_manager" "backend_prod" {
integration = doppler_integration_aws_secrets_manager.prod.id
project = "backend"
config = "prd"
region = "us-east-1"
path = "/backend/"
}
We start by defining a new AWS IAM role which Doppler will assume to perform sync operations. Note that you'll need to provide your workplace slug (available in all Doppler dashboard URLs as https://dashboard.doppler.com/workplace/:slug/
).
Then, we create a doppler_integration_aws_secrets_manager
resource which uses the new AWS role. Finally, we create a doppler_secrets_sync_aws_secrets_manager
resource to configure the sync between our backend.prd
doppler project and the /backend/doppler
secret in AWS Secrets Manager.
Similar to previous examples, run terraform init
and then terraform apply
to test the configuration.
Other integrations can be connected using similar resources. Check out our Terraform provider docs for all of the available options.
Managing Groups, Group Memberships, and Project Access
In this example, we'll use the doppler_user
data source along with the doppler_group
, doppler_group_member
, and doppler_project_member_group
resources to manage a group, its memberships, and its access to a project:
# Install the Doppler provider
terraform {
required_providers {
doppler = {
source = "DopplerHQ/doppler"
}
}
}
# Define a variable so we can pass in our token
variable "doppler_token" {
type = string
description = "A token to authenticate with Doppler"
}
# Configure the Doppler provider with the token
provider "doppler" {
doppler_token = var.doppler_token
}
# Create and manage your project
resource "doppler_project" "rocket" {
name = "rocket"
description = "To the moon and back."
}
# Data source mappings to specific users
data "doppler_user" "bob" {
email = "[email protected]"
}
data "doppler_user" "jane" {
email = "[email protected]"
}
data "doppler_user" "john" {
email = "[email protected]"
}
# Create and manage your group
resource "doppler_group" "engineering" {
name = "engineering"
}
# Manage your group's memberships
resource "doppler_group_member" "engineering" {
for_each = toset([data.doppler_user.bob.slug, data.doppler_user.jane.slug, data.doppler_user.john.slug])
group_slug = doppler_group.engineering.slug
user_slug = each.value
}
# Give your group access to a project
resource "doppler_project_member_group" "engineering" {
project = "rocket"
group_slug = doppler_group.engineering.slug
role = "collaborator"
environments = ["dev", "stg"]
}
We define a new Doppler project named rocket
. We then setup data sources mapping to existing users in Doppler. We then define a group named engineering
and define its memberships using doppler_group_member
. Finally, we provide the group with access to the rocket
project we defined earlier. Service accounts can be provided with access to a project as well using doppler_project_member_service_account
.
Similar to previous examples, run terraform init
and then terraform apply
to test the configuration.
Things to Watch Out For
Terraform Password Generation
Terraform has a built-in resource called random_password that can be used to seed passwords. If you use this to seed passwords that end up getting used in YAML configuration files, you may want to remove the #
from the override_special
list. Otherwise, you can end up with passwords with a #
character in them which YAML will interpret as a comment when it gets parsed. In a worst case scenario, you may end up with a password like #1sjh13l91
, which is stored in Doppler fine, but when fetched from Doppler and used in a YAML config file will end up loading as null
. It's also worth considering that this generator can create passwords with string sequences like ${
, which is our secret referencing syntax and will cause problems. As such, it might be worthwhile to add $
or {
to the override list as well.
Awesome Work!
Now you know to use the Doppler Terraform provider to securely read and manage secrets for your infrastructure.
Updated 2 months ago