Terraform Cloud Workflows

Production engineering guide for terraform cloud workflows covering patterns, implementation strategies, and operational best practices.

Terraform Cloud Workflows orchestrate infrastructure provisioning, validation, and deployment through a declarative, version-controlled pipeline. They are essential when teams need consistent, auditable, and repeatable infrastructure changes across environments—especially in multi-team, multi-environment, or compliance-driven setups. Workflows trigger on code changes, PRs, manual runs, or scheduled intervals, and they execute a sequence of steps that can include plan, apply, refresh, and custom tasks, all with full visibility and state management.

Defining a Workflow

A Terraform Cloud Workflow is a sequence of steps defined in a terraform-cloud.yml file at the root of a Git repository. This file is evaluated on every push, pull request, or manual run, and it determines which jobs execute, in what order, and with what configuration.


version: 1
workflows:
  - name: "plan-and-apply"
    plan:
      enabled: true
      message: "Plan: {{ git.branch }}"
      workspace: "prod"
      refresh: true
      auto-apply: false
      target: "aws.ec2.instance"
    apply:
      enabled: true
      workspace: "prod"
      auto-apply: true
      skip-refresh: false
    trigger:
      - push
      - pull_request
    environment:
      - "prod"
      - "staging"

The plan and apply steps are the core primitives. Each step may be enabled independently, and their behavior is governed by a set of configuration keys:

When a pull request is opened, Terraform Cloud detects the terraform-cloud.yml file, evaluates the workflow, and creates a new run with a plan step. If auto-apply is enabled, the plan is automatically applied when the PR is merged.

Triggering Workflows

Workflows are triggered by Git events. The most common are push and pull_request, but schedule is also supported. A schedule trigger runs the workflow on a cron-like schedule.

trigger:
  - push
  - pull_request
  - schedule: "0 0 * * *"

The schedule field uses standard cron syntax: minute hour day-of-month month day-of-week. When a schedule trigger fires, Terraform Cloud creates a run with no Git context, so {{ git.branch }} is master or main unless explicitly set.

The trigger section supports a filter block to refine which events fire a workflow:

trigger:
  - pull_request:
      branches: ["main", "develop"]
      paths: ["modules/", "environments/prod/"]
      types: ["opened", "synchronized"]

This filter runs the workflow only on pull requests to main or develop, when changes are made to modules/ or environments/prod/, and when the PR is opened or synchronized (i.e., new commits pushed).

If a workflow is triggered by a pull_request, the plan step automatically uses the PR’s base branch as the target for the plan. The apply step can be configured to run only on PR merge, using on: merge.

Running Custom Tasks

Beyond plan and apply, workflows can include custom tasks using run steps. These are shell commands executed in a container environment, with access to the Terraform state and environment variables.

run:
  - name: "validate-terraform"
    command: |
      terraform validate
      terraform fmt -check
    environment:
      TF_VAR_region: "us-west-2"
    secrets:
      - AWS_ACCESS_KEY_ID
      - AWS_SECRET_ACCESS_KEY
    working-directory: "environments/prod"

The run step supports:

Secrets are loaded from the Terraform Cloud workspace and made available in the environment. The terraform CLI is available by default, and the current state is available at ./terraform.tfstate.

If a run step fails, the entire workflow fails unless continue-on-error: true is set.

run:
  - name: "lint-and-test"
    command: |
      make lint
      make test
    continue-on-error: true
    environment:
      CI: "true"
      ENV: "staging"

The continue-on-error flag allows the workflow to proceed even if this step fails, useful for non-critical tasks like linting or publishing docs.

Managing Workspace State and Dependencies

Workflows interact with Terraform Cloud workspaces through state management. Each plan or apply step updates the workspace state, and the state is persisted across runs.

When a plan step runs, it:

  1. Fetches the latest state from the workspace.
  2. Runs terraform plan with the provided options.
  3. Uploads the plan to the workspace and saves it as a draft.
  4. Updates the terraform.tfstate file in the workspace.

If auto-apply is enabled, the apply step:

  1. Fetches the plan from the workspace.
  2. Applies it with terraform apply.
  3. Updates the state and marks the run as complete.

State is shared across runs. A run step can access the state from a previous plan or apply step, even across different workflows.

Handling Failures and Rollbacks

A common failure is a missing terraform-cloud.yml file. If the file is missing, Terraform Cloud uses a default workflow: plan on main, apply on main, with auto-apply: true. This default is not configurable, so teams must be aware that missing the file means a simplified, less flexible pipeline.

Another failure occurs when target is used in apply but not in plan. The apply step runs terraform apply with the specified targets, but the plan was generated without those targets. This leads to a mismatch: the plan includes more resources than are applied, and the state may not be consistent.

A silent failure is refresh: true on apply with skip-refresh: true. If both are set, the apply step runs terraform refresh before apply, but skip-refresh is ignored. The apply step always refreshes unless skip-refresh: false is explicitly set.

Workflows can include a rollback step after a failure:

run:
  - name: "rollback"
    command: |
      terraform state pull
      terraform apply -input=false -target=aws.ec2.instance
    condition: "failed"
    environment:
      TF_VAR_rollback: "true"

The condition: "failed" key runs this step only if a previous step failed. The run step is executed in the rollback environment, and it can use the current state to revert changes.

Advanced Configuration and Integration

Workflows can be versioned and managed via a version field in the terraform-cloud.yml file. The version: 1 key enables a new set of behavior, such as support for trigger.filter and run.condition.

A common pattern is to use run steps to publish artifacts:

run:
  - name: "publish-outputs"
    command: |
      echo "Publishing outputs..."
      jq -r '.outputs | to_entries[] | .key + " = " + .value.value' outputs.json
    outputs:
      - "terraform_outputs"
    environment:
      TF_OUTPUTS: "outputs.json"

The outputs key in a run step defines a list of output keys to export. These outputs are available to downstream workflows or CI systems.

Workflows can be triggered from external systems using the Terraform Cloud API. A POST /api/v2/workflows/run request with a JSON body triggers a workflow:

{
  "data": {
    "type": "workflow-runs",
    "attributes": {
      "workspace-id": "ws-abc123",
      "trigger": "manual",
      "variables": {
        "TF_VAR_region": "eu-central-1"
      }
    }
  }
}

The response includes a run-id and a status, and the run can be monitored via the GET /api/v2/workflow-runs/{run-id} endpoint.

Best Practices and Common Pitfalls

Use refresh: true on apply when the state is expected to be up-to-date, but the infrastructure may have changed externally (e.g., via AWS console or other tools). This prevents drift.

Avoid auto-apply: true on plan steps that are not intended to be final. A plan step with auto-apply: true creates a plan and immediately applies it, but if the plan is meant to be reviewed, this can lead to unreviewed changes.

A frequent mistake is using target without refresh. The plan is generated for a subset of resources, but the state may be stale. The apply step applies only the target resources, but the state may reflect changes to other resources, leading to inconsistent state.

Use continue-on-error: true on run steps that are not critical—e.g., documentation, linting, or testing. This allows the main workflow to proceed even if these steps fail.

When multiple workflows run in parallel, ensure that workspace names are unique across environments. A plan step on prod and an apply step on staging may run simultaneously, and their state updates may interfere if the workspace names are not carefully chosen.

Finally, always define message templates in plan and apply steps. Use {{ git.branch }}, {{ git.commit_message }}, and {{ git.author }} to provide rich context in the run history. This helps teams quickly diagnose why a run was triggered and what changes it includes.

Terraform Cloud Workflows are not just a pipeline—they are a declarative, version-controlled, and observable infrastructure automation engine. They become the single source of truth for how infrastructure is built, validated, and deployed.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.