> For the complete documentation index, see [llms.txt](https://developer.harness.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.harness.io/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/run-a-terraform-plan-with-the-terraform-plan-step.md).

# Preview with the Terraform Plan step

Configure the Terraform Plan step to preview infrastructure changes before applying them.

The Terraform Plan step runs a Terraform plan to preview infrastructure changes before applying them. You can use it with a Terraform Apply step to apply the plan, or standalone to preview changes only.

This topic explains how to configure the Terraform Plan step in your deployment pipeline.

***

## What you will learn from this topic

* How to configure the [Terraform Plan step](#configure-the-terraform-plan-step) in your Deploy stage.
* How to connect Harness to your [Terraform repository](#configuration-file-repository) using Git, S3, or Artifactory.
* How to use [Provisioner Identifiers](#provisioner-identifier) to link Plan, Apply, and Rollback steps.
* How to configure [optional settings](#optional-configuration) like workspaces, var files, backend configuration, and targets.
* How to export [JSON and human-readable plan outputs](#export-json-representation-of-terraform-plan) for use in subsequent steps.

***

## Before you begin

* **Harness project access**: View, Create/Edit, and Execute permissions on Pipelines and Environments. For more information, refer to [RBAC in Harness](https://developer.harness.io/docs/platform/role-based-access-control/rbac-in-harness) to configure roles.
* **Terraform or OpenTofu installed on delegate**: Terraform or OpenTofu must be installed on the delegate to execute Terraform commands. For more information, refer to [Build custom delegate images with third-party tools](https://developer.harness.io/docs/platform/delegates/install-delegates/build-custom-delegate-images-with-third-party-tools) to install Terraform.
* **Terraform configuration files**: Your Terraform scripts must be accessible via a Harness connector. For more information, refer to [Connect to a Git repo](https://developer.harness.io/docs/platform/connectors/code-repositories/connect-to-code-repo) for Git connectors or [Artifactory Connector Settings Reference](https://developer.harness.io/docs/platform/connectors/cloud-providers/ref-cloud-providers/artifactory-connector-settings-reference) for Artifactory.
* **Secret Manager configured**: Terraform plans contain sensitive data. Harness encrypts the plan using a Secret Manager before storing it. For more information, refer to [Harness Secrets Manager overview](https://developer.harness.io/docs/platform/secrets/secrets-management/harness-secret-manager-overview) to configure a Secret Manager.
* **Understanding of Terraform concepts**: This guide assumes familiarity with Terraform workspaces, backends, modules, and state files. For more information, refer to [Terraform documentation](https://developer.hashicorp.com/terraform/docs).

***

## Configure the Terraform Plan step

You can add the Terraform Plan step in two locations within your **Deploy** stage:

* **Environment > Dynamic Provisioning section**: Use this location when provisioning target infrastructure for deployment. You must pair it with a Terraform Apply step to apply the plan. For detailed workflow examples, refer to [Provision infrastructure dynamically with Terraform](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/provision-infra-dynamically-with-terraform.md).
* **Execution steps**: Use this location for ad hoc planning or standalone plan previews. The Terraform Apply step is optional in this flow.

***

## Basic settings

Configure the following basic settings for the Terraform Plan step.

### Name

In **Name**, enter a name for the step (for example, **plan**).

The name is important because you can use it in [expressions](https://developer.harness.io/docs/platform/variables-and-expressions/harness-variables) to refer to settings in this step.

For example, if the stage name is **Terraform** and the step name is **plan**, and you want to reference its timeout setting, use:

`<+pipeline.stages.Terraform.spec.execution.steps.plan.timeout>`

### Timeout

In **Timeout**, enter how long Harness should wait to complete the Terraform Plan step before failing it.

### Run on Remote Workspace

Select **Run on Remote Workspace** to execute Terraform commands in a Terraform Cloud or Terraform Enterprise workspace instead of on the Harness Delegate.

When enabled, Harness triggers a run in your remote workspace and monitors its status. The workspace must be configured with your Terraform configuration and variables.

The remote backend is supported only when the **Configuration Type** is **Inline**.

Example remote backend configuration:

```hcl
terraform {
  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "your-organization"
    workspaces {
      name = "your-workspace"
    }
  }
}
```

### Command

In **Command**, select **Apply** or **Destroy**.

* **Apply**: Use this option if you will apply the plan later with a [Terraform Apply step](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/run-a-terraform-plan-with-the-terraform-apply-step.md). This indicates the plan can be used with a Terraform Apply step.
* **Destroy**: Use this option if you will destroy resources later with a [Terraform Destroy step](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/remove-provisioned-infra-with-terraform-destroy.md).

### Provisioner Identifier

The **Provisioner Identifier** is a unique label that links this Terraform Plan step to Terraform Apply, Destroy, and Rollback steps.

In **Provisioner Identifier**, enter a unique value.

The most common use is between Terraform Plan and Terraform Apply steps. The Terraform Apply step uses the same Provisioner Identifier to apply the plan from the Plan step.

You also use the same Provisioner Identifier with the [Terraform Destroy step](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/remove-provisioned-infra-with-terraform-destroy.md) to remove provisioned resources.

You also use the same Provisioner Identifier with the [Terraform Rollback step](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/rollback-provisioned-infra-with-the-terraform-rollback-step.md) to roll back provisioned resources.

{% hint style="warning" %}
**Provisioner Identifier scope**

The Provisioner Identifier is project-wide. You can reference it across pipelines in the same project. Coordinate with your team to ensure identifiers are unique and not reused across pipelines to avoid conflicts.
{% endhint %}

### Secret Manager

Terraform plans contain sensitive data. Harness encrypts the plan using a Secret Manager before storing it.

In **Secret Manager**, select the Secret Manager to use for encrypting and storing the Terraform plan file.

For more information, refer to [Harness Secrets Manager overview](https://developer.harness.io/docs/platform/secrets/secrets-management/harness-secret-manager-overview).

A Terraform plan is a sensitive file that could be misused to alter resources if someone has access to it. Harness avoids this issue by never passing the Terraform plan file as plain text. Harness only passes the Terraform plan between the Harness Manager and Delegate as an encrypted file using a Secrets Manager.

When the `terraform plan` command runs on the Harness Delegate, the Delegate encrypts the plan and saves it to the Secrets Manager you selected. When the plan is applied, the Harness Manager passes the encrypted data to the Delegate, which decrypts and applies it using the `terraform apply` command.

{% hint style="info" %}
**Secret size limitations**

Some Secret Managers (HashiCorp Vault, Azure Key Vault, AWS Secrets Manager) have maximum secret size limitations. For example, AWS Secrets Manager has a 64KB limit. If your Terraform plan exceeds this limit, use a Key Management Service like Google Cloud KMS or AWS KMS instead.
{% endhint %}

### Configuration File Repository

The **Configuration File Repository** is where your Terraform scripts and configuration files are located.

Perform the following steps to connect to your Terraform repository:

1. In **Configuration File Repository**, select **Specify Config File** or select the edit icon.

   The **Terraform Config File Store** settings appear.
2. Select the provider where your files are hosted (GitHub, GitLab, Bitbucket, AWS S3, Artifactory, etc.).
3. Select or create a Connector for your repository. Harness supports the following connector types for Terraform configuration files:
   * **Git providers** (GitHub, GitLab, Bitbucket, etc.): Store Terraform scripts in version-controlled repositories. For more information, refer to [Connect to a Git repo](https://developer.harness.io/docs/platform/connectors/code-repositories/connect-to-code-repo).
   * **AWS S3**: Store Terraform scripts in S3 buckets. For more information, refer to [Add an AWS connector](https://developer.harness.io/docs/platform/connectors/cloud-providers/add-aws-connector).
   * **Artifactory**: Store Terraform scripts as archived artifacts in Artifactory repositories. For more information, refer to [Artifactory Connector Settings Reference](https://developer.harness.io/docs/platform/connectors/cloud-providers/ref-cloud-providers/artifactory-connector-settings-reference) (see **Artifactory with Terraform Scripts**).

***

## Optional configuration

The following settings are available under **Optional Configuration** in the Terraform Plan step.

### Workspace

A Terraform workspace is a separate instance of state data, allowing you to manage multiple environments (Dev, QA, Production) from the same Terraform configuration.

Harness supports Terraform [workspaces](https://www.terraform.io/docs/state/workspaces.html). A Terraform workspace is a logical representation of your infrastructure, such as Dev, QA, Stage, or Production.

Workspaces are useful when testing changes before moving to production infrastructure. Each workspace isolates its state from other workspaces.

Example script with workspace configuration:

```hcl
locals {  
  counts = {  
    "default"    = 1  
    "production" = 3  
  }  
}  
  
resource "aws_instance" "my_service" {  
  ami           = "ami-7b4d7900"  
  instance_type = "t2.micro"  
  count         = lookup(local.counts, terraform.workspace, 2)  
  
  tags = {  
    Name = terraform.workspace  
  }  
}
```

In **Workspace**, enter the name of the workspace to use. You can also use a [stage variable](https://developer.harness.io/docs/platform/variables-and-expressions/harness-variables) in **Workspace** to specify a different workspace name each time the pipeline runs.

### Connector credentials

You can use a connector to authenticate with the target cloud provider. This optional configuration takes the connector reference and the Terraform step uses it to authenticate with the cloud provider targeted for infrastructure provisioning.

This connector configuration is available in the Terraform Plan step. It also appears in Terraform Apply and Terraform Destroy steps when **Configuration Type** is set to **Inline**.

#### AWS Connector

{% hint style="info" %}
**Requirements**

This feature requires Harness Delegate version 81202 or later. Contact [Harness Support](mailto:support@harness.io) to enable this feature.
{% endhint %}

You can use an AWS connector to have the Terraform Plan and Apply steps assume a role to perform infrastructure provisioning. It takes the AWS connector, a region, and a Role ARN. The Terraform step uses these parameters to authenticate with the AWS account targeted for infrastructure provisioning.

By default, AWS assumes the role session duration as 900 seconds. To increase the session duration, use the `HARNESS_AWS_ASSUME_ROLE_DURATION` environment variable (requires Harness Delegate version 82700 or later). Set the value in seconds in the environment variable section of the Terraform step.

When configured, these fields can be passed as a fixed value, runtime input, or an expression.

<details>

<summary>Sample YAML</summary>

```yaml
- step:
    type: TerraformApply
    name: Apply
    identifier: Apply
    spec:
      provisionerIdentifier: provision
      configuration:
        type: Inline
        spec:
          workspace: <+input>
          configFiles: {}
          providerCredential:
            type: Aws
            spec:
              connectorRef: <+input>
              region: <+input>
              roleArn: <+input>
    timeout: 10m
```

</details>

#### Azure Connector

Harness Terraform steps support authenticating with Azure using Azure connectors for target provisioning. This enables seamless integration with Azure infrastructure when running Terraform Plan, Apply, and Destroy steps with inline Terraform configuration.

**Supported authentication methods:**

* Manual credentials
* Delegate-based credentials
* OIDC token-based authentication

{% hint style="info" %}
**Certificate-based authentication is not supported yet.**
{% endhint %}

For more information, refer to [Azure Connector Settings Reference](https://developer.harness.io/docs/platform/connectors/cloud-providers/add-a-microsoft-azure-connector).

<details>

<summary>Sample YAML</summary>

```yaml
- step:
    type: TerraformPlan
    name: TerraformPlan_1
    identifier: TerraformPlan_1
    spec:
      provisionerIdentifier: planoidc
      configuration:
        command: Apply
        configFiles:
          store:
            spec:
              connectorRef: githubConnector
              repoName: play
              gitFetchType: Branch
              branch: main
              folderPath: tf/azure
            type: Github
        providerCredential:
          type: Azure
          spec:
            connectorRef: AzureConnector
            subscriptionId: 20xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
```

</details>

#### GCP Connector

{% hint style="info" %}
**Requirements**

This feature requires Harness Delegate version 88303 or later.
{% endhint %}

Harness Terraform steps support authenticating with Google Cloud Platform (GCP) using GCP connectors for target infrastructure provisioning.

For information on setting up a GCP connector, refer to [Connect to Google Cloud Platform (GCP)](https://developer.harness.io/docs/platform/connectors/cloud-providers/connect-to-google-cloud-platform-gcp).

**Supported authentication methods:**

* **Manual Credentials (ManualConfig)**: Use GCP service account credentials stored in Harness Secret Manager. The connector references a secret containing the GCP Service Account Key in JSON format.
* **Inherit From Delegate (InheritFromDelegate)**: Use credentials from the delegate's IAM role, such as a GCE instance service account or GKE workload identity. At least one delegate selector must be specified.
* **OIDC Authentication (OidcAuthentication)**: Enable federated authentication using OpenID Connect for workload identity federation. This is the recommended approach for production environments as it eliminates long-lived credentials.

<details>

<summary>Manual Credentials Connector YAML example</summary>

This example uses a GCP service account key stored in Harness Secret Manager:

```yaml
- step:
    type: TerraformPlan
    name: TerraformPlan_Manual
    identifier: TerraformPlan_Manual
    spec:
      provisionerIdentifier: gcp_terraform_provision
      configuration:
        command: Apply
        configFiles:
          store:
            spec:
              connectorRef: githubConnector
              repoName: terraform-repo
              gitFetchType: Branch
              branch: main
              folderPath: tf/gcp
            type: Github
        providerCredential:
          type: Gcp
          spec:
            connectorRef: gcpManualConnector  # References GCP connector with manual credentials
            project: my-gcp-project
    timeout: 10m
```

GCP Connector configuration (Manual Credentials):

```yaml
connector:
  name: GCP Manual Credentials
  identifier: gcpManualConnector
  type: Gcp
  spec:
    credential:
      type: ManualConfig
      spec:
        secretKeyRef: gcpServiceAccountKey  # Secret containing GCP service account JSON key
    executeOnDelegate: false
```

</details>

<details>

<summary>Inherit From Delegate Connector YAML example</summary>

This example uses credentials from the delegate's IAM role (GCE instance service account or GKE workload identity):

```yaml
- step:
    type: TerraformPlan
    name: TerraformPlan_InheritFromDelegate
    identifier: TerraformPlan_InheritFromDelegate
    spec:
      provisionerIdentifier: gcp_terraform_provision
      configuration:
        command: Apply
        configFiles:
          store:
            spec:
              connectorRef: githubConnector
              repoName: terraform-repo
              gitFetchType: Branch
              branch: main
              folderPath: tf/gcp
            type: Github
        providerCredential:
          type: Gcp
          spec:
            connectorRef: gcpInheritConnector  # References GCP connector that inherits from delegate
            project: my-gcp-project
    timeout: 10m
```

GCP Connector configuration (Inherit From Delegate):

```yaml
connector:
  name: GCP Inherit From Delegate
  identifier: gcpInheritConnector
  type: Gcp
  spec:
    credential:
      type: InheritFromDelegate
      spec:
        delegateSelectors:
          - gcp-delegate  # At least one delegate selector is required
    executeOnDelegate: true
```

</details>

<details>

<summary>OIDC Authentication Connector YAML example</summary>

This example uses OpenID Connect for workload identity federation (recommended for production):

```yaml
- step:
    type: TerraformPlan
    name: TerraformPlan_OIDC
    identifier: TerraformPlan_OIDC
    spec:
      provisionerIdentifier: gcp_terraform_provision
      configuration:
        command: Apply
        configFiles:
          store:
            spec:
              connectorRef: githubConnector
              repoName: terraform-repo
              gitFetchType: Branch
              branch: main
              folderPath: tf/gcp
            type: Github
        providerCredential:
          type: Gcp
          spec:
            connectorRef: gcpOidcConnector  # References GCP connector with OIDC authentication
            project: my-gcp-project
    timeout: 10m
```

GCP Connector configuration (OIDC Authentication):

```yaml
connector:
  name: GCP OIDC Authentication
  identifier: gcpOidcConnector
  type: Gcp
  spec:
    credential:
      type: OidcAuthentication
      spec:
        workloadPoolId: projects/123456789/locations/global/workloadIdentityPools/harness-pool
        providerId: harness-oidc-provider
        serviceAccountEmail: terraform-sa@my-gcp-project.iam.gserviceaccount.com
        gcpProjectId: my-gcp-project
    executeOnDelegate: true
```

</details>

### Terraform var files

You can specify Terraform variables inline and fetch remote variable files during runtime. For more information, refer to [Specify Terraform variables](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/optional-tf-var-files.md).

### Backend configuration

The **Backend Configuration** section contains the [remote state](https://www.terraform.io/docs/language/state/remote.html) values.

You can use an inline or remote state file.

#### Remote backend config file

Perform the following steps to use a remote backend config file:

1. In **Backend Configuration**, select **Remote**.
2. Select **Specify Terraform Backend Config File Store**.
3. Select your provider (GitHub, Artifactory, S3, etc.) and select or create a Connector to the repository where the files are located.

**For Git providers:**

1. In **Git Fetch Type**, select **Latest from Branch** or **Specific Commit ID**.
2. In **Branch**, enter the name of the branch.
3. In **File Path**, add the file path from the root of the repository to the backend config file.
4. Select **Submit**.

**For AWS S3:**

1. In **Region**, select the region where your bucket is stored.
2. In **Bucket**, select the bucket where your backend config file is stored.
3. In **File Path**, add the file path from the root of the bucket to the backend config file.

You can also use files in the [Harness File Store](/continuous-delivery/use-continuous-delivery/cd-building-blocks/services/add-inline-manifests-using-file-store.md).

To use the same remote state file set in the Terraform Plan step, Terraform Apply steps must use the same Provisioner Identifier.

For an example of how the config file should look, refer to [Backend Configuration](https://developer.hashicorp.com/terraform/language/settings/backends/configuration#file) from HashiCorp.

#### Inline State

Enter values for each backend config (remote state variable).

For example, if your `config.tf` file has the following backend:

```hcl
terraform {
  backend "gcs" {
    bucket = "tf-state-prod"
    prefix = "terraform/state"
  }
}
```

In **Backend Configuration**, provide the required configuration variables for the backend type. For a remote backend configuration, the variables should be in a `.tfvars` file format:

```hcl
bucket = "tf-state-prod"  
prefix = "terraform/state"
```

For more information, refer to [GCS Standard Backend](https://www.terraform.io/docs/language/settings/backends/gcs.html#configuration-variables) from Terraform.

### Targets

You can use the **Targets** setting to target one or more specific modules in your Terraform script, similar to using the `terraform plan -target` command.

For more information, refer to [Resource Targeting](https://www.terraform.io/docs/commands/plan.html#resource-targeting) from Terraform.

Identify the module using the standard format `module.name`, such as `terraform plan -target="module.s3_bucket"`.

If you have multiple modules in your script and you do not select one in **Targets**, all modules are used.

### Environment variables

If your Terraform script uses [environment variables](https://www.terraform.io/docs/cli/config/environment-variables.html), you can provide values for those variables here.

Example:

```bash
TF_LOG_PATH=./terraform.log  
TF_VAR_alist='[1,2,3]'
```

You can use Harness encrypted text for values. For more information, refer to [Add text secrets](https://developer.harness.io/docs/platform/secrets/add-use-text-secrets).

### Export JSON representation of Terraform plan

Enable this setting to export a JSON representation of the Terraform plan.

In subsequent **Execution** steps, such as a [Shell Script step](/continuous-delivery/use-continuous-delivery/cd-building-blocks/cd-steps/utilities/shell-script-step.md), you can reference the Terraform plan using this expression format:

`<+execution.steps.[Terraform Plan step Id].plan.jsonFilePath>`

Example usage in a Shell Script step:

```bash
cat "<+execution.steps.Plan_Step.plan.jsonFilePath>"
```

If the Terraform Plan step is in **Dynamic Provisioning** in **Infrastructure**, and the step Id is `TfPlan`, the expression is:

`<+infrastructure.infrastructureDefinition.provisioner.steps.TfPlan.plan.jsonFilePath>`

JSON representation of the Terraform plan can be accessed across different stages. In this case, the fully qualified name (FQN) is required:

`<+pipeline.stages.TfStage.spec.execution.steps.TfPlan.plan.jsonFilePath>`

When the Terraform Plan step is in a step group:

`<+pipeline.stages.[stage name].spec.execution.steps.[step group name].steps.[step name].plan.jsonFilePath>`

{% hint style="info" %}
**When Run on Remote Workspace is selected, you will not be able to export the JSON representation of the Terraform plan.**
{% endhint %}

#### Scope of Expression

JSON representation of the Terraform plan is available only between the Terraform Plan step and subsequent Terraform Apply step. The expression will fail to resolve if used after the Terraform Apply step.

If used across stages, the Terraform Plan step can be in one stage and the Terraform Apply step in a subsequent stage. The expression will resolve successfully in this case.

The JSON of the Terraform Plan step is not available after Rollback.

### Export human readable representation

Enable this option to view the Terraform plan file path and contents as human-readable JSON in subsequent steps, such as a [Shell Script step](/continuous-delivery/use-continuous-delivery/cd-building-blocks/cd-steps/utilities/shell-script-step.md).

Once you enable this option and run a Deploy stage with the Terraform Plan step, you can view the **Output** tab and copy the **Output Value** for the **humanReadableFilePath** output.

The format for the expression is:

`<+terraformPlanHumanReadable."pipeline.stages.[stage Id].spec.execution.steps.[step Id].[Provisioner Identifier]_planHumanReadable">`

Example:

```bash
echo "<+terraformPlanHumanReadable."pipeline.stages.tf.spec.execution.steps.tf.tf_planHumanReadable">"
cat "<+terraformPlanHumanReadable."pipeline.stages.tf.spec.execution.steps.tf.tf_planHumanReadable">"
```

If there are no changes in the plan, the standard Terraform message is shown:

```
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.
```

### Store Terraform plan on delegate

Here is a video demo of how to store Terraform Plan files on delegate:

{% hint style="info" %}
**Requirements**

This setting requires Harness Delegate version 24.04.82705 or later.
{% endhint %}

Use this option when your Secret Manager has file size limits or you want to avoid storing plans in Secret Manager.

{% hint style="warning" %}
**Plans are lost if the delegate pod restarts.**
{% endhint %}

Enable **Store terraform plan on delegate** to store the Terraform Plan on Harness Delegate temporarily. This is useful if you do not want to save Terraform Plan files in Secrets Manager or if there is a file size limit on Secrets Manager.

After you enable this option, select **Inherit From Plan** in the Terraform Apply or Terraform Destroy step's **Configuration Type** field and enter the Provisioner Identifier used by the Terraform Plan step.

**Important notes:**

* You must execute the Terraform Plan and Terraform Apply/Destroy steps on the same pod. If your delegate has multiple pod replicas, make sure to [run the Terraform steps on the same pod](https://developer.harness.io/docs/platform/delegates/manage-delegates/run-all-pipeline-steps-in-one-pod).
* Harness stores Terraform Plan files at `/opt/harness-delegate/./tf-plan/` inside the working directory.
* If delegate pods are restarted, Terraform plan files are lost. To prevent this, set up a persistent volume with `ReadWriteMany` access mode if you have multiple pod replicas.
* The Terraform plan file is cleaned up after the Terraform Apply/Destroy step executes or after pipeline execution completes.

### Skip state storage

{% hint style="info" %}
**Requirements**

This feature requires Harness Delegate version 812xx or later.
{% endhint %}

While running Terraform commands on the delegate, Harness by default detects if there is a local state file in the Terraform working directory.

If a local state file is identified, at the end of execution it is saved on Harness storage with a key based on the provisioner identifier. That state file is downloaded in the Terraform working directory for subsequent executions, and the updated state is uploaded after execution ends.

This method allows maintaining the state of infrastructure even if there is no Terraform backend configured. This is more for testing purposes. For production environments, Harness advises you configure a backend in your Terraform config files. For more information, refer to [Backend Configuration](https://developer.hashicorp.com/terraform/language/settings/backends/configuration) from HashiCorp.

With **Skip state storage** enabled, Harness skips the local state upload and download operations.

This option is useful only if you do not have a Terraform backend configured in your Terraform config files. If you have a Terraform backend configured, the Terraform CLI will not create local state files.

### Create remote workspace with prefix

{% hint style="info" %}
**Requirements**

This option requires Harness Delegate version 86400 or later.
{% endhint %}

When using a [remote backend](https://developer.hashicorp.com/terraform/language/backend/remote) with a workspace **prefix**, Terraform does not automatically create the workspace if it does not already exist. This can cause pipeline failures with errors like:

`Error: Currently selected workspace "my-app-dev" does not exist`

To address this, Harness provides the **Create remote workspace with prefix** option. When enabled:

* If the remote workspace does **not** exist, Harness automatically creates it and continues execution.
* If the remote workspace **does** exist, Harness exports it to the `TF_WORKSPACE` environment variable so Terraform uses it.
* If both the step configuration and environment variable specify a workspace, the **step configuration takes precedence**.

{% hint style="info" %}
To enable automatic workspace selection when a workspace is configured in the step settings, this flag **must** be enabled.

If you prefer not to use this flag, you can manually configure the workspace using the `TF_WORKSPACE` environment variable.

Rollback does not delete workspaces created using this option. Workspace cleanup must be handled manually.
{% endhint %}

<details>

<summary>Sample YAML</summary>

```yaml
- step:
    type: TerraformPlan
    name: TerraformPlan
    identifier: TerraformPlan
    timeout: 10m
    spec:
      provisionerIdentifier: <+input>
      configuration:
        command: Apply
        configFiles: {}
        secretManagerRef: <+input>
        skipStateStorage: false
        createRemoteWorkspaceWithPrefix: true
        skipRefreshCommand: false
```

</details>

## Command line options

This setting allows you to set Terraform CLI options for Terraform commands. For example: `-lock=false`, `-lock-timeout=0s`.

### Skip Terraform refresh

Select **Skip Terraform refresh** to skip running `terraform refresh` before generating the plan.

By default, Terraform refreshes the state before planning to detect changes made outside of Terraform. Enable this option to skip the refresh step, which can speed up plan execution but may result in a plan that does not account for manual changes to your infrastructure.

### Access detailed exit code output

Harness automatically captures the detailed exit code from the Terraform CLI and makes it available as a step output. The detailed exit code follows the standard [terraform plan -detailed-exitcode](https://www.terraform.io/cli/commands/plan#other-options) convention:

* `0`: succeeded with empty diff (no changes)
* `1`: error
* `2`: succeeded with non-empty diff (changes present)

You can access the detailed exit code in subsequent steps using the following expression format:

`<+pipeline.stages.STAGE_ID.spec.execution.steps.STEP_ID.plan.detailedExitCode>`

Example:

`<+pipeline.stages.TfStage.spec.execution.steps.TfPlan.plan.detailedExitCode>`

***

## Advanced settings

In **Advanced**, you can configure the following options:

* [Delegate Selector](https://developer.harness.io/docs/platform/delegates/manage-delegates/select-delegates-with-selectors): Select specific delegates to run this step.
* [Conditional Execution](https://developer.harness.io/docs/platform/pipelines/step-skip-condition-settings): Define conditions for when to execute this step.
* [Failure Strategy](https://developer.harness.io/docs/platform/pipelines/failure-handling/define-a-failure-strategy-on-stages-and-steps): Define what happens when the step fails.
* [Looping Strategy](https://developer.harness.io/docs/platform/pipelines/looping-strategies/looping-strategies-matrix-repeat-and-parallelism): Run the step multiple times with different configurations.
* [Policy Enforcement](https://developer.harness.io/docs/platform/governance/policy-as-code/harness-governance-overview): Apply governance policies to this step.

***

## Working directory cleanup

Each Terraform step runs in a specific working directory on the delegate.

The Terraform working directory is located at `/opt/harness-delegate/./terraform-working-dir/`.

To that directory path, Harness adds additional directories named after the organization, account, project, and provisioner identifier such that the final working directory is `/opt/harness-delegate/./terraform-working-dir/org-name/account-name/project-name/provisionerId/`.

In this final working directory, Harness stores the Terraform configuration and all fetched files such as var-files and backend-config.

Once the Terraform step execution is complete, Harness cleans up the main working directory `/opt/harness-delegate/./terraform-working-dir/`. This cleanup happens immediately after the step completes successfully or fails.

If you generate local resources on the delegate in the directory where Terraform configurations are located, those resources are also removed. If you need those resources, generate them outside the Terraform working directory.

***

## YAML example

<details>

<summary>Terraform Plan step YAML</summary>

```yaml
- step:
    type: TerraformPlan
    name: Terraform Plan
    identifier: Terraform_Plan
    timeout: 10m
    spec:
      provisionerIdentifier: my_terraform_provisioner
      configuration:
        command: Apply
        configFiles:
          store:
            type: Github
            spec:
              connectorRef: GitHub_Connector
              gitFetchType: Branch
              branch: main
              folderPath: terraform/infrastructure
              repoName: my-org/my-terraform-repo
        varFiles:
          - varFile:
              type: Inline
              identifier: terraform_vars
              spec:
                content: |
                  environment = "dev"
                  region      = "us-west-2"
        secretManagerRef: harnessSecretManager
        # Store plan on delegate instead of Secret Manager (recommended for large plans)
        storeTfPlanOnDelegate: true
        # Skip Harness state storage if using remote backend
        skipStateStorage: false
        # Skip refresh to speed up planning
        skipRefreshCommand: false
      # Use delegate selectors to ensure Plan and Apply run on same pod
      delegateSelectors:
        - my-delegate-selector
```

**Key configuration options:**

* `provisionerIdentifier`: Links this Plan step to Apply, Destroy, and Rollback steps
* `storeTfPlanOnDelegate: true`: Stores plan file on delegate at `/opt/harness-delegate/tf-plan/` instead of Secret Manager (use when Secret Manager has size limits or to avoid null pointer issues)
* `skipStateStorage: true`: Skips Harness local state upload/download (use when you have a remote backend configured or to avoid state version conflicts)
* `delegateSelectors`: Ensures Terraform Plan and Apply steps run on the same delegate pod (required when `storeTfPlanOnDelegate: true`)

</details>

***

## Troubleshooting

<details>

<summary>Terraform Plan step fails with 'Provisioner Identifier not found' error</summary>

Ensure the Provisioner Identifier is unique within the project. If using with Terraform Apply, verify both steps use the exact same identifier (case-sensitive).

</details>

<details>

<summary>Terraform Plan step fails with Secret Manager encryption error</summary>

Verify the Secret Manager is configured correctly and the delegate can access it. Check that the Terraform plan size does not exceed the secret size limit (e.g., AWS Secrets Manager has a 64KB limit).

</details>

<details>

<summary>Terraform Plan step fails to fetch configuration files from Git repository</summary>

Verify the Git connector is configured correctly with valid credentials. Check the branch name and folder path are correct. For SSH authentication, ensure the SSH key has no passphrase.

</details>

<details>

<summary>Terraform Plan remote workspace does not exist error</summary>

Enable 'Create remote workspace with prefix' in the Terraform Plan step settings, or manually create the workspace in your Terraform Cloud/Enterprise instance before running the pipeline.

</details>

<details>

<summary>Terraform execution on a Docker delegate managed by ECS fails with NoCredentialProviders error</summary>

When your Docker delegate is managed by ECS and set to assume an IAM role, ensure the delegate has permissions to assume the role. If using a proxy, ensure the proxy instance also has permissions. Set HTTP\_PROXY and HTTPS\_PROXY environment variables in Terraform steps if needed.

</details>

***

## Next steps

* [Terraform Apply step](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/run-a-terraform-plan-with-the-terraform-apply-step.md): Apply the plan generated by this step.
* [Terraform Rollback step](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/rollback-provisioned-infra-with-the-terraform-rollback-step.md): Roll back provisioned infrastructure if needed.
* [Terraform Destroy step](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/remove-provisioned-infra-with-terraform-destroy.md): Remove provisioned infrastructure.
* [Provision infrastructure dynamically with Terraform](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/provision-infra-dynamically-with-terraform.md): Complete dynamic provisioning workflow.

{% @harness-feedback/feedback %}
