Skip to main content
The Terraform Cloud job agent creates workspaces and triggers runs, enabling infrastructure-as-code deployments with webhook-based status tracking.

How It Works

  1. Ctrlplane renders a workspace configuration from your template
  2. The workspace is created or updated via Terraform Cloud API
  3. Variables are synced to match your template
  4. A webhook notification configuration (ctrlplane-webhook) is created on the workspace (idempotent)
  5. A run is triggered with auto-apply
  6. Terraform Cloud sends webhook notifications as the run progresses
  7. The ctrlplane API receives webhooks and updates job status in the database
This is a fire-and-forget dispatch model — the workspace-engine does not poll or maintain long-running goroutines. Status tracking is handled entirely by TFC webhooks, making it resilient to engine restarts.

Prerequisites

  • Terraform Cloud or Terraform Enterprise account
  • API token with workspace, run, and notification configuration permissions
  • VCS connection (optional, for Git-based workflows)
  • A reachable webhook endpoint (the ctrlplane API must be accessible from TFC)

Configuration

Job Agent Setup

Create a job agent with type tfe:
type: JobAgent
name: terraform-cloud
agentType: tfe

Deployment Configuration

type: Deployment
name: infrastructure
jobAgent: terraform-cloud
jobAgentConfig:
  organization: your-org
  address: https://app.terraform.io
  token: "{{.variables.tfe_token}}"
  webhookUrl: https://ctrlplane.example.com/api/tfe/webhook
  triggerRunOnChange: true
  template: |
    name: {{.deployment.slug}}-{{.resource.identifier}}
    description: "Managed by Ctrlplane"
    execution_mode: remote
    auto_apply: true
    terraform_version: "1.6.0"
    working_directory: environments/{{.environment.name}}
    vcs_repo:
      identifier: your-org/infrastructure
      branch: main
      oauth_token_id: ot-xxxxxxxxxx
    variables:
      - key: environment
        value: {{.environment.name}}
        category: terraform
      - key: version
        value: {{.version.tag}}
        category: terraform
FieldRequiredDefaultDescription
organizationYesTerraform Cloud organization
addressYesTerraform Cloud/Enterprise URL
tokenYesAPI token
templateYesGo template for workspace configuration
webhookUrlYesCtrlplane API endpoint for TFC notifications (e.g. https://ctrlplane.example.com/api/tfe/webhook)
triggerRunOnChangeNotrueWhether to create a TFC run on dispatch. When false, only the workspace and variables are synced.

Environment Variables

VariableWhereDescription
TFE_WEBHOOK_SECRETAPIHMAC secret for verifying incoming TFC webhooks
TFE_WEBHOOK_SECRETWorkspace EngineSame secret, used when creating notification configs on TFC
Both the API and workspace-engine must share the same TFE_WEBHOOK_SECRET.

Webhook Status Mapping

When TFC sends a notification, the webhook handler maps the trigger to a ctrlplane job status:
TFC TriggerExample Run StatusCtrlplane Status
run:createdpendingpending
run:planningplanninginProgress
run:needs_attentionplanned (confirmable), policy_overrideactionRequired
run:applyingapplyinginProgress
run:completedapplied, planned_and_finishedsuccessful
run:errorederroredfailure

Workspace Template

The template defines the Terraform Cloud workspace:
FieldTypeDescription
namestringWorkspace name (required)
descriptionstringWorkspace description
execution_modestringremote, local, or agent
auto_applyboolAuto-apply after plan
terraform_versionstringTerraform version to use
working_directorystringSubdirectory for Terraform files
vcs_repoobjectVCS repository settings
variablesarrayWorkspace variables

VCS Repository Settings

vcs_repo:
  identifier: org/repo
  branch: main
  oauth_token_id: ot-xxxxxxxxxx
  ingress_submodules: false
  tags_regex: ""

Variable Configuration

variables:
  - key: aws_region
    value: us-east-1
    category: terraform # or "env"
    hcl: false
    sensitive: false
    description: "AWS region"

Template Context

The template has access to the full dispatch context:
VariableDescription
.deploymentDeployment details
.environmentEnvironment details
.resourceTarget resource (config, metadata)
.releaseRelease details
.versionDeployment version (tag, name)
.variablesMerged deployment variables

triggerRunOnChange: false

When triggerRunOnChange is set to false, the dispatcher will:
  1. Upsert the workspace
  2. Sync variables
  3. Ensure the notification config exists
  4. Skip creating a run
This is useful when you want VCS pushes to trigger runs instead of ctrlplane creating them directly. The webhook notification config is still created so that run status updates flow back to ctrlplane.
Note: Correlating VCS-triggered runs back to ctrlplane jobs (by workspace name/ID instead of run ID) is a planned follow-up.

Example: Multi-Environment Infrastructure

type: Deployment
name: vpc-infrastructure
jobAgent: terraform-cloud
jobAgentConfig:
  organization: "{{.variables.tfe_org}}"
  address: "{{.variables.tfe_address}}"
  token: "{{.variables.tfe_token}}"
  webhookUrl: "{{.variables.ctrlplane_webhook_url}}"
  template: |
    name: vpc-{{.environment.name}}-{{.resource.metadata.region}}
    description: "VPC for {{.environment.name}} in {{.resource.metadata.region}}"
    execution_mode: remote
    auto_apply: {{if eq .environment.name "production"}}false{{else}}true{{end}}
    terraform_version: "1.6.0"
    working_directory: modules/vpc

    vcs_repo:
      identifier: {{.variables.github_org}}/infrastructure
      branch: {{.version.tag}}
      oauth_token_id: {{.variables.vcs_oauth_token}}

    variables:
      - key: environment
        value: {{.environment.name}}
        category: terraform
      - key: region
        value: {{.resource.metadata.region}}
        category: terraform
      - key: vpc_cidr
        value: {{.resource.config.vpc_cidr}}
        category: terraform
      - key: AWS_ACCESS_KEY_ID
        value: {{.variables.aws_access_key}}
        category: env
        sensitive: true
      - key: AWS_SECRET_ACCESS_KEY
        value: {{.variables.aws_secret_key}}
        category: env
        sensitive: true

Example: Agent-Based Execution

For private infrastructure, use agent execution mode:
template: |
  name: private-{{.resource.identifier}}
  execution_mode: agent
  agent_pool_id: {{.variables.agent_pool_id}}
  auto_apply: true

  variables:
    - key: target_host
      value: {{.resource.config.host}}
      category: terraform

Terraform Provider Configuration

When using the ctrlplane Terraform provider:
resource "ctrlplane_job_agent" "tfc" {
  name = "terraform-cloud"

  terraform_cloud {
    address            = "https://app.terraform.io"
    organization       = "your-org"
    token              = var.tfc_token
    webhook_url        = "https://ctrlplane.example.com/api/tfe/webhook"
    trigger_run_on_change = true
    template           = file("workspace-template.yaml")
  }
}

Troubleshooting

Workspace creation fails

  • Verify organization name is correct
  • Check API token has workspace:write permission
  • Ensure workspace name is valid (alphanumeric, hyphens, underscores)

VCS connection errors

  • Verify OAuth token ID is correct
  • Check repository exists and is accessible
  • Ensure branch or tag exists

Run fails to start

  • Check workspace has valid configuration
  • Verify VCS connection is working
  • Review workspace settings in Terraform Cloud UI

Variables not updating

  • Verify variable keys match expected format
  • Check for duplicate variable definitions
  • Sensitive variables won’t show values in UI

Webhook returns 401

  • Check TFE_WEBHOOK_SECRET is set on the API
  • Verify the same secret was used when creating the notification config on TFC
  • Ensure the x-tfe-notification-signature header is present

No webhook notifications received

  • Verify the webhookUrl is reachable from Terraform Cloud
  • Check the notification config exists on the TFC workspace (Settings > Notifications)
  • Review TFC’s notification delivery log for errors
  • For local development, use smee.io or localtunnel to expose your API

Job stays in inProgress

  • Verify webhooks are reaching the API (check API logs for POST /api/tfe/webhook)
  • Check the TFC run status directly in the TFC UI