Terraform Stacks configuration for deploying HashiCorp Vault across multiple projects and environments using Terraform Cloud.
+---------------------+
| Terraform Cloud |
| Stack: vault-projects|
+----------+----------+
|
+---------------------+---------------------+
| | |
+------+------+ +------+------+ +------+------+
| project1 | | project2 | | project3 |
+------+------+ +------+------+ +------+------+
| | |
+----+----+ +----+----+ +----+----+
| | | | | |
nprod prod nprod prod nprod prod
Each project/environment deployment gets:
- Its own Vault namespace (
<env>/<project>) - A KV v2 secrets engine
- Base policies (admin, readonly, writer)
- Optionally enabled auth methods (pick what you need)
- Optional secrets sync to external destinations
.
├── modules/ # Reusable Terraform modules
│ ├── vault-namespace/ # Core: namespace, KV engine, base policies
│ ├── auth-aws/ # AWS IAM auth method
│ ├── auth-kubernetes/ # Kubernetes auth method
│ ├── auth-github/ # GitHub auth method
│ ├── auth-oidc/ # OIDC/JWT auth method
│ └── secrets-sync/ # Vault secrets sync destinations
│
├── components/ # Root module used by the stack
│ └── vault-project-config/ # Composes all child modules with feature flags
│ ├── main.tf # Conditionally includes each auth module
│ ├── variables.tf # All input variables (feature flags + configs)
│ ├── outputs.tf
│ └── providers.tf
│
└── stacks/ # Terraform Stack definitions
└── vault-projects/
├── components.tfstack.hcl # Stack component referencing the root module
├── providers.tfstack.hcl # Vault provider configuration
├── variables.tfstack.hcl # Stack-level input variables
├── outputs.tfstack.hcl # Stack outputs
└── deployments.tfdeploy.hcl # All project/env deployment definitions
Each project picks which auth methods to enable via boolean feature flags:
| Auth Method | Flag | Use Case |
|---|---|---|
| AWS | enable_auth_aws |
EC2 instances, Lambda, ECS tasks authenticating via IAM |
| Kubernetes | enable_auth_kubernetes |
Pods authenticating via service account tokens |
| GitHub | enable_auth_github |
Developers authenticating via GitHub personal tokens |
| OIDC | enable_auth_oidc |
SSO login via Google, Azure AD, Okta, etc. |
| Secrets Sync | enable_secrets_sync |
Sync Vault secrets to AWS SM, GitHub Actions, etc. |
The included deployments.tfdeploy.hcl shows three project archetypes:
| Project | Auth Methods | Description |
|---|---|---|
| project1 | AWS + Kubernetes | Microservices on K8s with AWS workloads |
| project2 | GitHub + OIDC + Secrets Sync | CI/CD focused, developer access via GitHub/SSO |
| project3 | AWS + Kubernetes + OIDC + Secrets Sync | Full-featured, all integrations |
- Navigate to your TFC organization
- Go to Projects & workspaces > New > Stack
- Configure:
- Name:
vault-projects - VCS connection: Connect to your repository
- Stack configuration directory:
stacks/vault-projects
- Name:
- Click Create stack
Create variable sets in TFC to hold sensitive values shared across deployments. Variable sets can be scoped to specific stacks or the entire organization.
Create two variable sets, one per Vault environment:
Variable Set: vault-nprod-credentials
| Key | Category | Sensitive | Value |
|---|---|---|---|
vault_token |
Terraform | Yes | Your nprod Vault token |
Variable Set: vault-prod-credentials
| Key | Category | Sensitive | Value |
|---|---|---|---|
vault_token |
Terraform | Yes | Your prod Vault token |
For projects that need AWS auth or Secrets Sync, create additional variable sets:
Variable Set: project1-aws-credentials
| Key | Category | Sensitive | Value |
|---|---|---|---|
aws_auth_access_key |
Terraform | Yes | AWS access key |
aws_auth_secret_key |
Terraform | Yes | AWS secret key |
Variable Set: project2-oidc-credentials
| Key | Category | Sensitive | Value |
|---|---|---|---|
oidc_client_id |
Terraform | No | OIDC client ID |
oidc_client_secret |
Terraform | Yes | OIDC client secret |
- Go to Settings > Variable sets in your TFC organization
- For each variable set, click Edit and under Scope, select the
vault-projectsstack - Alternatively, scope variable sets to specific deployments within the stack
If you prefer traditional TFC workspaces instead of Stacks, create one workspace per project/environment combination:
Workspaces to create:
├── vault-project1-nprod
├── vault-project1-prod
├── vault-project2-nprod
├── vault-project2-prod
├── vault-project3-nprod
└── vault-project3-prod
For each workspace:
-
Create the workspace:
# Using the TFC CLI or API terraform cloud workspace create vault-project1-nprod \ --project vault-management \ --vcs-repo your-org/your-repo \ --working-directory components/vault-project-config -
Create a
*.auto.tfvarsfile per workspace (or set variables in the TFC UI):# project1-nprod.auto.tfvars project_name = "project1" environment = "nprod" enable_auth_aws = true enable_auth_kubernetes = true enable_auth_github = false enable_auth_oidc = false enable_secrets_sync = false # ... additional config
-
Add a backend configuration in the component:
terraform { cloud { organization = "your-org" workspaces { tags = ["vault", "project1", "nprod"] } } }
-
Set variables in each workspace via the TFC UI or API for sensitive values.
Follow this pattern for consistency:
vault-{project_name}-{environment}
Examples:
vault-project1-nprodvault-project1-prodvault-project2-nprod
Group workspaces under TFC Projects:
TFC Organization
└── Project: vault-management
├── Stack: vault-projects (if using Stacks)
│ ├── Deployment: project1-nprod
│ ├── Deployment: project1-prod
│ ├── Deployment: project2-nprod
│ ├── Deployment: project2-prod
│ ├── Deployment: project3-nprod
│ └── Deployment: project3-prod
│
└── (or individual workspaces if using traditional approach)
├── vault-project1-nprod
├── vault-project1-prod
└── ...
To onboard a new project (e.g., project4):
- Add a new deployment block in
stacks/vault-projects/deployments.tfdeploy.hcl:
deployment "project4-nprod" {
inputs = {
vault_address = "https://vault-nprod.example.com"
vault_token = "<set-via-tfc-variable-set>"
project_name = "project4"
environment = "nprod"
# Pick the auth methods this project needs
enable_auth_aws = false
enable_auth_kubernetes = true
enable_auth_github = true
enable_auth_oidc = false
enable_secrets_sync = false
# GitHub auth config
github_organization = "my-org"
github_team_mappings = {
"project4-team" = {
policies = ["writer"]
}
}
# Kubernetes auth config
kubernetes_host = "https://k8s-nprod.example.com:6443"
kubernetes_ca_cert = "<set-via-tfc-variable-set>"
kubernetes_roles = {
"api" = {
bound_service_account_names = ["project4-api"]
bound_service_account_namespaces = ["project4"]
token_policies = ["readonly"]
}
}
}
}-
Add the corresponding prod deployment following the same pattern.
-
Create any needed variable sets in TFC for secrets.
-
Commit and push - TFC will automatically detect the new deployment and plan it.
To add support for a new auth method (e.g., LDAP):
- Create
modules/auth-ldap/withmain.tf,variables.tf,outputs.tf - Add
enable_auth_ldapvariable tocomponents/vault-project-config/variables.tf - Add the conditional module call in
components/vault-project-config/main.tf - Add the variable to
stacks/vault-projects/variables.tfstack.hcl - Wire it through in
stacks/vault-projects/components.tfstack.hcl - Set
enable_auth_ldap = truein the deployments that need it
- All
vault_token, API keys, and secrets should be set via TFC Variable Sets marked as sensitive, never committed to the repository - The placeholder
<set-via-tfc-variable-set>indeployments.tfdeploy.hclindicates values that must come from variable sets - Use Vault's TFC Workload Identity integration to avoid static tokens entirely (see commented example in
deployments.tfdeploy.hcl) - Prod deployments should have stricter policies than nprod (fewer write permissions, no localhost callback URIs, etc.)
- Consider using TFC run task integrations for policy-as-code checks (e.g., Sentinel or OPA)