Terraform Cloud and Workspaces Configuration
Terraform Cloud provides hosted remote state management, collaborative plan/apply workflows, and team-based access controls that transform Terraform from a local CLI tool into a shared infrastructure automation platform. By configuring workspaces with VCS integration, variable sets, and policy enforcement, teams can manage infrastructure changes safely with full audit trails and approval workflows.
Prerequisites
- Terraform CLI 1.1+
- Terraform Cloud account (free tier available at app.terraform.io)
- GitHub, GitLab, or Bitbucket account for VCS integration
- Cloud provider credentials (AWS, Azure, GCP, etc.)
# Install Terraform CLI
wget https://releases.hashicorp.com/terraform/1.8.0/terraform_1.8.0_linux_amd64.zip
unzip terraform_1.8.0_linux_amd64.zip
sudo mv terraform /usr/local/bin/
terraform version
# Login to Terraform Cloud
terraform login
# Opens browser for authentication - creates ~/.terraform.d/credentials.tfrc.json
Terraform Cloud Setup
# Configure Terraform CLI for Terraform Cloud
cat > ~/.terraformrc <<EOF
credentials "app.terraform.io" {
token = "your-tfe-token"
}
EOF
# Or use the TFE_TOKEN environment variable
export TFE_TOKEN="your-tfe-token"
# Create an organization in Terraform Cloud (via UI or API)
curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
https://app.terraform.io/api/v2/organizations \
-d '{
"data": {
"type": "organizations",
"attributes": {
"name": "mycompany",
"email": "[email protected]",
"session-timeout": 120,
"session-remember": 20160,
"cost-estimation-enabled": true,
"send-passing-statuses-for-untriggered-speculative-plans": false
}
}
}' | jq '.data.id'
Workspace Configuration
Configure Terraform to use Terraform Cloud workspaces:
# main.tf - Configure cloud backend
terraform {
cloud {
organization = "mycompany"
workspaces {
# Single workspace by name
name = "production-networking"
# Or use tags to work with multiple workspaces
# tags = ["networking", "production"]
}
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.5.0"
}
provider "aws" {
region = var.aws_region
}
Create workspaces via API or Terraform:
# Create workspace via API
TF_ORG="mycompany"
WORKSPACE_NAME="production-networking"
curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/organizations/${TF_ORG}/workspaces" \
-d "{
\"data\": {
\"type\": \"workspaces\",
\"attributes\": {
\"name\": \"${WORKSPACE_NAME}\",
\"description\": \"Production AWS networking resources\",
\"auto-apply\": false,
\"terraform-version\": \"1.8.0\",
\"working-directory\": \"infra/networking\",
\"execution-mode\": \"remote\",
\"allow-destroy-plan\": false,
\"queue-all-runs\": false,
\"speculative-enabled\": true
}
}
}" | jq '.data.id'
# Initialize Terraform with Cloud backend
terraform init
VCS Integration
Connect workspaces to a Git repository for automated plan/apply on push:
# Step 1: Create OAuth client for GitHub
# Via Terraform Cloud UI: Organization Settings > VCS Providers > Add a VCS Provider
# Or via API:
curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/organizations/${TF_ORG}/oauth-clients" \
-d '{
"data": {
"type": "oauth-clients",
"attributes": {
"service-provider": "github",
"http-url": "https://github.com",
"api-url": "https://api.github.com",
"oauth-token-string": "your-github-personal-access-token"
}
}
}' | jq '.data.relationships.oauth-tokens.data[0].id'
# Step 2: Link workspace to VCS repository
OAUTH_TOKEN_ID="ot-xxxx"
REPO_IDENTIFIER="mycompany/infrastructure"
curl -s -X PATCH \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/workspaces/${WORKSPACE_ID}" \
-d "{
\"data\": {
\"type\": \"workspaces\",
\"attributes\": {
\"vcs-repo\": {
\"identifier\": \"${REPO_IDENTIFIER}\",
\"oauth-token-id\": \"${OAUTH_TOKEN_ID}\",
\"branch\": \"main\",
\"ingress-submodules\": false
}
}
}
}"
After VCS integration, Terraform Cloud will:
- Run
terraform planas a speculative plan on pull requests - Run
terraform planand queue for apply on merge tomain - Comment plan output on pull requests automatically
Remote State Management
# Access state from another workspace (cross-workspace references)
data "terraform_remote_state" "networking" {
backend = "remote"
config = {
organization = "mycompany"
workspaces = {
name = "production-networking"
}
}
}
# Use outputs from the networking workspace
resource "aws_instance" "app_server" {
ami = var.ami_id
instance_type = "t3.medium"
subnet_id = data.terraform_remote_state.networking.outputs.private_subnet_id
vpc_security_group_ids = [
data.terraform_remote_state.networking.outputs.app_security_group_id
]
tags = {
Name = "app-server"
Environment = "production"
}
}
# Share state outputs between workspaces
# In networking workspace outputs.tf:
output "private_subnet_id" {
value = aws_subnet.private.id
description = "Private subnet ID for application servers"
}
# Terraform Cloud also supports workspace sharing
# Enable via UI: Workspace Settings > Remote State Sharing
# Or via API to share with specific workspaces
Variable Sets and Configuration
Variable sets allow sharing variables across multiple workspaces:
# Create a variable set for AWS credentials
curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/organizations/${TF_ORG}/varsets" \
-d '{
"data": {
"type": "varsets",
"attributes": {
"name": "AWS Production Credentials",
"description": "AWS credentials for production environment",
"global": false
}
}
}' | jq '.data.id'
VARSET_ID="varset-xxxx"
# Add AWS_ACCESS_KEY_ID to the variable set (environment variable)
curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/varsets/${VARSET_ID}/relationships/vars" \
-d '{
"data": {
"type": "vars",
"attributes": {
"key": "AWS_ACCESS_KEY_ID",
"value": "AKIAIOSFODNN7EXAMPLE",
"category": "env",
"sensitive": false
}
}
}'
# Add AWS_SECRET_ACCESS_KEY (sensitive - value hidden after set)
curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/varsets/${VARSET_ID}/relationships/vars" \
-d '{
"data": {
"type": "vars",
"attributes": {
"key": "AWS_SECRET_ACCESS_KEY",
"value": "your-secret-key",
"category": "env",
"sensitive": true
}
}
}'
# Assign variable set to specific workspaces
curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/varsets/${VARSET_ID}/relationships/workspaces" \
-d "{
\"data\": [
{\"type\": \"workspaces\", \"id\": \"${WORKSPACE_ID}\"}
]
}"
Workspace-specific variables:
# Add Terraform variables to a specific workspace
WORKSPACE_ID="ws-xxxx"
# terraform.tfvars equivalent - workspace-level variable
curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/workspaces/${WORKSPACE_ID}/vars" \
-d '{
"data": {
"type": "vars",
"attributes": {
"key": "environment",
"value": "production",
"category": "terraform",
"hcl": false,
"sensitive": false,
"description": "Deployment environment name"
}
}
}'
# HCL variable (list/map)
curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/workspaces/${WORKSPACE_ID}/vars" \
-d '{
"data": {
"type": "vars",
"attributes": {
"key": "allowed_cidrs",
"value": "[\"10.0.0.0/8\", \"172.16.0.0/12\"]",
"category": "terraform",
"hcl": true,
"sensitive": false
}
}
}'
Run Triggers and Dependencies
Configure workspace dependencies for ordered applies:
# Create a run trigger: when networking workspace completes a run,
# automatically queue a run in app workspace
APP_WORKSPACE_ID="ws-app-xxxx"
NETWORKING_WORKSPACE_ID="ws-networking-xxxx"
curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/workspaces/${APP_WORKSPACE_ID}/run-triggers" \
-d "{
\"data\": {
\"relationships\": {
\"sourceable\": {
\"data\": {
\"id\": \"${NETWORKING_WORKSPACE_ID}\",
\"type\": \"workspaces\"
}
}
}
}
}"
# Now when networking applies successfully, app workspace run is triggered automatically
Team Access and Policy Enforcement
# Create teams and assign workspace permissions
# Create team
TEAM_ID=$(curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/organizations/${TF_ORG}/teams" \
-d '{
"data": {
"type": "teams",
"attributes": {
"name": "platform-engineers",
"organization-access": {
"manage-workspaces": true,
"manage-policies": false,
"manage-vcs-settings": false
}
}
}
}' | jq -r '.data.id')
# Assign workspace permission to team
curl -s -X POST \
-H "Authorization: Bearer $TFE_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/team-workspaces" \
-d "{
\"data\": {
\"type\": \"team-workspaces\",
\"attributes\": {
\"access\": \"write\" // read, plan, write, or admin
},
\"relationships\": {
\"team\": {\"data\": {\"type\": \"teams\", \"id\": \"${TEAM_ID}\"}},
\"workspace\": {\"data\": {\"type\": \"workspaces\", \"id\": \"${WORKSPACE_ID}\"}}
}
}
}"
# Sentinel policy (Terraform Cloud Business/Plus tier)
# policies/enforce-tags.sentinel
cat > enforce-tags.sentinel <<'EOF'
import "tfplan/v2" as tfplan
# Require specific tags on all AWS resources
required_tags = ["Environment", "Team", "CostCenter"]
get_resources = func(resource_type) {
return filter tfplan.resource_changes as _, rc {
rc.type is resource_type and rc.mode is "managed" and
(rc.change.actions contains "create" or rc.change.actions contains "update")
}
}
all_aws_instances = get_resources("aws_instance")
# Check all instances have required tags
main = rule {
all all_aws_instances as _, instance {
all required_tags as tag {
instance.change.after.tags contains tag
}
}
}
EOF
Troubleshooting
Authentication failures:
# Re-authenticate
terraform logout
terraform login
# Verify token
curl -s -H "Authorization: Bearer $TFE_TOKEN" \
https://app.terraform.io/api/v2/account/details | jq '.data.attributes.username'
# Check CLI config
cat ~/.terraform.d/credentials.tfrc.json
Workspace not finding state:
# List workspaces in organization
curl -s -H "Authorization: Bearer $TFE_TOKEN" \
"https://app.terraform.io/api/v2/organizations/${TF_ORG}/workspaces" | \
jq '.data[].attributes.name'
# Verify workspace name matches terraform block
terraform workspace show
VCS-triggered runs not appearing:
# Check webhook delivery in GitHub:
# Repository Settings > Webhooks > Recent Deliveries
# Re-deliver webhook or check Terraform Cloud organization settings
# Verify the webhook URL is correct and the token has repo access
# Check run queue manually
curl -s -H "Authorization: Bearer $TFE_TOKEN" \
"https://app.terraform.io/api/v2/workspaces/${WORKSPACE_ID}/runs" | \
jq '.data[] | {id: .id, status: .attributes.status, created: .attributes."created-at"}' | \
head -20
Variable set not applying:
# Verify variable set is assigned to workspace
curl -s -H "Authorization: Bearer $TFE_TOKEN" \
"https://app.terraform.io/api/v2/varsets/${VARSET_ID}/relationships/workspaces" | \
jq '.data[].id'
# Check for variable precedence conflicts
# Workspace variables override variable set variables of the same name
curl -s -H "Authorization: Bearer $TFE_TOKEN" \
"https://app.terraform.io/api/v2/workspaces/${WORKSPACE_ID}/vars" | \
jq '.data[].attributes | {key: .key, category: .category}'
Conclusion
Terraform Cloud workspaces transform infrastructure management into a collaborative, audited workflow where plan outputs are visible to the whole team, applies require approval, and state is never stored locally. Variable sets eliminate credential duplication across workspaces, run triggers enforce deployment ordering, and VCS integration ensures infrastructure changes follow the same pull request process as application code. Start by migrating your most critical Terraform state to Terraform Cloud, then progressively add VCS integration and policy enforcement as your team grows.


