Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

Terraform Remote Execution

Remote execution runs terraform plan and terraform apply on a managed infrastructure (Terraform Cloud, HCP Terraform, or CI/CD runners) rather than on a developer’s local machine. The benefits: consistent environment, centralized audit logs, no dependency on who has provider credentials on their laptop, and native integration with policy-as-code.


Why Remote Execution

Local ExecutionRemote Execution
Runs in developer’s shellRuns in a consistent, clean environment
Developer needs provider credentialsCentralized credential management
No visibility when someone runs applyCentralized run history and logs
Difficult to enforce policiesPolicy-as-code enforced before apply
”Works on my machine” variabilitySame Terraform version, same env for all

Terraform Cloud Remote Execution

HCP Terraform (formerly Terraform Cloud) provides remote execution as a managed service:

main.tf
terraform {
cloud {
organization = "mycompany"
workspaces {
name = "production-aws"
}
}
}
Terminal window
# Authenticate with Terraform Cloud
terraform login
# Initialize — downloads modules, sets up remote execution
terraform init
# This plan runs remotely in Terraform Cloud
terraform plan
# This apply runs remotely with approval workflow
terraform apply

When you run terraform plan locally, the plan actually executes in Terraform Cloud’s infrastructure. Streaming output appears in your terminal, but the execution happens remotely.


Execution Modes

Terraform Cloud supports two execution modes:

Remote (Default)

Plans and applies run in Terraform Cloud’s managed infrastructure:

# Workspace settings in Terraform Cloud UI:
# Settings > General > Execution Mode: Remote

Local

State is stored remotely in Terraform Cloud, but plan/apply runs locally:

# Useful when you need local provider access or custom environment
# Settings > General > Execution Mode: Local

Agent

Plans and applies run on your own infrastructure via Terraform Cloud Agents — useful for private network resources:

Terminal window
# Install and register a Terraform Cloud Agent
docker run -d \
-e TFC_AGENT_TOKEN="your-agent-token" \
-e TFC_AGENT_NAME="private-network-agent" \
hashicorp/tfc-agent:latest

API-Driven Workflow

Trigger Terraform runs from CI/CD without the Terraform CLI:

Terminal window
# Create and start a run via Terraform Cloud API
WORKSPACE_ID="ws-abc123"
TFC_TOKEN="your-token"
# Queue a plan
curl \
--header "Authorization: Bearer $TFC_TOKEN" \
--header "Content-Type: application/vnd.api+json" \
--request POST \
--data '{
"data": {
"attributes": {
"message": "Queued from GitHub Actions",
"is-destroy": false
},
"type": "runs",
"relationships": {
"workspace": {
"data": { "type": "workspaces", "id": "'$WORKSPACE_ID'" }
}
}
}
}' \
https://app.terraform.io/api/v2/runs

CLI-Driven Workflow in CI/CD

The most common pattern — run the Terraform CLI in a CI/CD job that streams output back:

.github/workflows/terraform.yml
name: Terraform
on:
push:
branches: [main]
pull_request:
jobs:
plan:
name: Terraform Plan
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.9.5"
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
- name: Terraform Init
run: terraform init -input=false
- name: Terraform Plan
id: plan
run: |
terraform plan -no-color -input=false -out=tfplan 2>&1 | tee plan-output.txt
- name: Comment Plan on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs')
const plan = fs.readFileSync('plan-output.txt', 'utf8')
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan\n\`\`\`terraform\n${plan.slice(-60000)}\n\`\`\``
})
apply:
name: Terraform Apply
runs-on: ubuntu-latest
needs: plan
if: github.ref == 'refs/heads/main'
environment: production # Requires manual approval in GitHub Environments
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.9.5"
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- run: terraform init -input=false
- run: terraform apply -auto-approve -input=false

Atlantis: Pull Request Automation

Atlantis runs Terraform inside your own infrastructure and comments plan/apply output on GitHub/GitLab PRs:

# atlantis.yaml — project configuration
version: 3
projects:
- name: production
dir: environments/production
workspace: production
autoplan:
when_modified:
- "*.tf"
- "modules/**/*.tf"
apply_requirements:
- approved # Require PR approval before apply
- mergeable # Require branch to be mergeable
- name: staging
dir: environments/staging
workspace: staging
autoplan:
when_modified: ["*.tf"]

Atlantis workflow in pull requests:

  1. Push changes to a branch
  2. Atlantis comments terraform plan output on the PR
  3. Reviewer approves the PR and comments atlantis apply
  4. Atlantis runs apply and reports results
  5. Merge the PR

Securing Remote Execution

# Use OIDC to avoid static credential management
# CI runner proves identity to AWS without stored keys
resource "aws_iam_role" "ci_runner" {
name = "TerraformCIRole"
assume_role_policy = jsonencode({
Statement = [{
Effect = "Allow"
Principal = {
Federated = "arn:aws:iam::${local.account_id}:oidc-provider/token.actions.githubusercontent.com"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:myorg/*:*"
}
}
}]
})
}