Terraform can manage HashiCorp Vault, but the relationship is less about "managing Vault" and more about Vault managing itself through Terraform.

Let’s see Vault running. Imagine you have a Vault server already humming along. You want to define a new secret engine, say KV v2, and grant a specific role read-only access to a particular path within that engine.

provider "vault" {
  address = "http://127.0.0.1:8200"
  token   = "s.your_root_token_here" # In production, use a proper auth method!
}

resource "vault_mount" "kv_v2" {
  path        = "secret"
  type        = "kv"
  options = {
    "version" = "2"
  }
}

resource "vault_policy" "readonly_secret" {
  name = "readonly-secret"
  policy = <<-EOT
    path "secret/data/*" {
      capabilities = ["read"]
    }
  EOT
}

resource "vault_auth_method" "userpass" {
  type = "userpass"
}

resource "vault_identity_entity" "example" {
  name = "example-entity"
}

resource "vault_identity_group" "example" {
  name = "example-group"
}

resource "vault_identity_group_alias" "example_userpass" {
  name           = "example-user"
  mount_accessor = vault_auth_method.userpass.accessor
  identity_id    = vault_identity_entity.example.id
}

resource "vault_identity_group_membership" "example" {
  group_id = vault_identity_group.example.id
  member_id = vault_identity_entity.example.id
}

This Terraform code defines a KV v2 secret engine mounted at /secret, a policy named readonly-secret that allows reading data under secret/data/*, and sets up a basic identity structure with a userpass auth method and an entity/group.

The core problem Terraform solves here is declarative infrastructure for Vault. Instead of manually clicking through the Vault UI or running vault CLI commands, you define your desired Vault state in .tf files. Terraform’s vault provider then translates these declarations into API calls to Vault. This means your Vault configuration becomes version-controlled, auditable, and repeatable. You can track changes, roll back to previous states, and spin up identical Vault configurations in different environments.

Internally, the vault provider works by authenticating to your Vault instance (using the address and token or other configured auth methods) and then interacting with Vault’s HTTP API. For vault_mount, it calls the /sys/mounts/{mount_path} endpoint. For vault_policy, it uses /hsm/policy/{policy_name} or /sys/policy/{policy_name} depending on the Vault version. For authentication methods and identity resources, it leverages the respective API endpoints under /sys/auth/{method_type} and /identity/. Terraform tracks the state of these resources, so subsequent terraform apply commands will either create, update, or destroy Vault resources to match your configuration.

The vault provider supports a wide range of Vault features, including secret engines, authentication methods, policies, identity entities and groups, secrets (though this is often better handled by specific secret-engine Terraform resources or dynamic secrets), and more. You can even manage the Vault server itself if you’re using Terraform to provision the underlying infrastructure (e.g., on AWS or Kubernetes) and then use the vault provider to configure it.

What most people don’t realize is that while you can define static secrets in Terraform, it’s generally an anti-pattern for sensitive data. The true power lies in managing Vault’s capabilities and access control, and then leveraging Vault’s dynamic secrets capabilities. Terraform can provision an app role, for example, and then your application can use that app role to dynamically generate short-lived credentials for other services (like AWS or databases) directly from Vault. Terraform’s role here is to set up that dynamic secret generation mechanism.

The next logical step is to explore how to manage dynamic secrets using Terraform, such as provisioning an aws or database secret engine and defining roles for dynamic credential generation.

Want structured learning?

Take the full Vault course →