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 plan as a speculative plan on pull requests
  • Run terraform plan and queue for apply on merge to main
  • 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.