> 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-apply-step.md).

# Provision with the Terraform Apply step

Configure the Terraform Apply step to provision infrastructure in your deployment pipeline.

The Terraform Apply step applies a Terraform plan or script to provision infrastructure in your deployment pipeline. You can use it with a Terraform Plan step to apply a previously generated plan, or standalone to configure and apply Terraform scripts directly.

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

***

## What you will learn from this topic

* How to configure the [Terraform Apply step](#configure-the-terraform-apply-step) to provision infrastructure.
* How to use [Configuration Type](#configuration-type) to inherit from a Plan step or configure inline.
* How to use [Provisioner Identifiers](#provisioner-identifier) to link Apply, Plan, Destroy, and Rollback steps.
* How to configure [connector credentials](#configuration-file-repository) for AWS, Azure, and GCP authentication.
* How to access [Terraform outputs](#access-step-outputs) and [encrypt JSON outputs](#encrypt-json-output) 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.
* **Terraform Backend Configuration** (recommended): Using a remote backend (such as S3, GCS, Azure Blob, or Terraform Cloud) is recommended to manage Terraform state files. For more information, refer to [Backend configuration](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/run-a-terraform-plan-with-the-terraform-plan-step.md#backend-configuration) in the Terraform Plan step documentation.
* **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).
* **Terraform Plan step** (optional): If you want to apply a plan generated by a Terraform Plan step, refer to [Terraform Plan step](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/run-a-terraform-plan-with-the-terraform-plan-step.md) to configure it first.

***

## Configure the Terraform Apply step

You can add the Terraform Apply 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 provisioning or standalone provisioning workflows. The Terraform Plan step is optional in this flow.

When you use the Terraform Apply step with a Terraform Plan step, set **Configuration Type** to **Inherit from Plan** and use the same **Provisioner Identifier**. When you use it without a Plan step, set **Configuration Type** to **Inline** and configure the Terraform scripts directly.

***

## Basic settings

Configure the following basic settings for the Terraform Apply step.

### Name

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

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 **apply**, and you want to reference its timeout setting, use:

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

### Timeout

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

### Run on Remote Workspace

Enable this option to identify whether the Terraform configuration uses a Terraform remote backend.

When enabled, you cannot provide the workspace input in Harness. The workspace will be outlined in your configuration for the remote backend.

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

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

{% hint style="warning" %}
**Configuration Type cannot be set to Inherit from Plan if the Run on Remote Workspace checkbox is checked.**
{% endhint %}

***

### Configuration Type

**Configuration Type** determines whether this step uses its own Terraform configuration (**Inline**) or inherits settings from a previous Terraform Plan step (**Inherit from Plan**).

In **Configuration Type**, select **Inline** or **Inherit from Plan**.

* **Inherit from Plan:** Use this option when you have a Terraform Plan step earlier in the pipeline and want to apply that plan. You will enter the unique Id used by a [Terraform Plan](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/run-a-terraform-plan-with-the-terraform-plan-step.md) step in **Provisioner Identifier**.
* **Inline:** Use this option to configure and run Terraform Apply without a separate Plan step. You will configure this Terraform Apply step to use a Terraform script without any Terraform Plan step.

The sections below cover the **Inline** settings.

***

### Provisioner Identifier

The **Provisioner Identifier** is a unique label that links this Terraform Apply step to a Terraform Plan step (when using Inherit from Plan) or to other Terraform steps like Rollback and Destroy.

Enter a unique value in **Provisioner Identifier**.

The most common use of Provisioner Identifier is between the Terraform Plan and Terraform Apply steps. For the Terraform Apply step, to apply the provisioning from the Terraform Plan step, you use the same Provisioner Identifier.

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

{% hint style="info" %}
**The Provisioner Identifier is a Project-wide setting. You can reference it across Pipelines in the same Project.**

For this reason, it is important that all your Project members know the Provisioner Identifiers. This will prevent one member building a Pipeline from accidentally impacting the provisioning of another member's Pipeline.
{% endhint %}

***

### Configuration file repository

This option is visible when you select **Inline** in **Configuration Type** section.

**Configuration File Repository** is where the Terraform scripts you want to apply are located. In **Configuration File Repository**, click **Specify Config File**.

The **Terraform Config File Store** settings appear.

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

Configure the following optional settings for the Terraform Apply step.

### Encrypt JSON output

The **Encrypt json output** setting encrypts the Terraform JSON output as a Harness secret using the selected Secret Manager.

The secret is ephemeral and is automatically created during pipeline execution and deleted when the pipeline completes. The secret is created at the same scope as the pipeline execution (project, organization, or account level) and appears in the **Secrets** list in Harness during execution.

You can select a Secret Manager connector from any scope (project, organization, or account level) to encrypt the output.

You can use an expression to retrieve the encrypted Terraform JSON output during pipeline execution. This secret expression can be used in subsequent steps to parse, validate, or extract required output values from the JSON.

In **Encrypt json output**, select or create a Secret Manager to use for encryption.

To obtain the expression, do the following:

1. In a pipeline execution that uses this feature, select the **Terraform Apply** step.
2. In the step details, select **Output**.
3. In **Output Name**, locate `TF_JSON_OUTPUT_ENCRYPTED`.
4. Copy the expression. When you paste it, it will look something like `<+pipeline.stages.stage1.spec.execution.steps.TerraformApply_1.output.TF_JSON_OUTPUT_ENCRYPTED>`.

Do not use the **Output Value**, for example `<+secrets.getValue("terraform_output_df1ds123331123122123_LYF5b3")>`.

A secret is masked in Harness logs, but you can write it to a file like this:

```bash
cat <<EOF > apply_outputs.json
<+pipeline.stages.stage1.spec.execution.steps.TerraformApply_1.output.TF_JSON_OUTPUT_ENCRYPTED>
EOF
```

Above example will pass the content of Terraform apply outputs as-is into the file including quotes, etc.

Here is an example of decrypted Terraform JSON output:

```json
{
  "test-output-name1": {
    "sensitive": false,
    "type": "string",
    "value": "test-output-value1"
  },
  "test-output-name2": {
    "sensitive": false,
    "type": "string",
    "value": "test-output-value2"
  }
}
```

**Handling sensitive Terraform outputs**

To extract specific values from the encrypted Terraform output, users can use tools like jq to parse and retrieve individual values from the JSON output.

To parse a file using the jq tool, its content must be in a valid JSON format. jq binary should be installed on the delegate.

For example, use the following command to extract the value for `test-output-name1`:

```bash
jq '.["test-output-name1"].value' file.json
```

***

### Mask sensitive outputs

When a Terraform output is marked `sensitive = true` in your `main.tf` file, Harness automatically detects that flag and masks the value in the **Output** tab. Non-sensitive outputs continue to appear as plain text.

Sensitive values are displayed as encrypted expressions in the output:

```
db_password = ${sweepingOutputSecrets.obtain("db_password","...")}
```

Sensitive outputs remain fully usable in downstream steps using the same expression syntax as non-sensitive outputs:

```
<+pipeline.stages.STAGE_ID.spec.execution.steps.STEP_ID.output.OUTPUT_NAME>
```

When Harness resolves the expression in a downstream step, the value is decrypted and injected at runtime but stays masked in logs.

{% hint style="info" %}
**This feature applies when the Encrypt json output secret manager is not configured. If a secret manager is set, Harness encrypts the entire JSON output using that path instead.**
{% endhint %}

***

## Command line options

Use the following command line options to customize Terraform execution behavior.

### Add command line options

The command line options setting lets you set Terraform CLI options for Terraform commands depending on the Terraform step type.

For example: `-lock=false`, `-lock-timeout=0s`.

***

### Skip Terraform refresh

The Terraform refresh command will not run when this setting is selected.

***

## Access step outputs

The Terraform Apply step output is available after the Terraform Apply step completes. The output is exposed to other steps and resources in Harness.

You can find the output in the **Output** tab of the step.

To use an output, copy the expression path for the output key as shown in the following image:

The expression is in the form `<+pipeline.stages.STAGE_ID.spec.execution.steps.STEP_ID.output.OUTPUT_NAME>`.

When you use the expression in another step, the expression resolves to the value.

For example, if your Terraform configuration outputs a variable named `TEST_OUTPUT_NAME1`, you can reference it in a subsequent step using:

```
<+pipeline.stages.stage-provisioning.spec.execution.steps.TerraformApply_1.output.TEST_OUTPUT_NAME1>
```

You can use these output expressions in:

* Shell Script steps to process output values
* HTTP steps to pass values to external APIs
* Pipeline variables to pass values between stages
* Conditional execution settings to control pipeline flow based on Terraform outputs

***

## Advanced settings

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

* [Delegate Selector](https://developer.harness.io/docs/platform/delegates/manage-delegates/select-delegates-with-selectors)
* [Conditional Execution](https://developer.harness.io/docs/platform/pipelines/step-skip-condition-settings)
* [Failure Strategy](https://developer.harness.io/docs/platform/pipelines/failure-handling/define-a-failure-strategy-on-stages-and-steps)
* [Looping Strategy](https://developer.harness.io/docs/platform/pipelines/looping-strategies/looping-strategies-matrix-repeat-and-parallelism)
* [Policy Enforcement](https://developer.harness.io/docs/platform/governance/policy-as-code/harness-governance-overview)

***

## 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 that are named after the organization, account, project, and provisionerId (from the step) 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 any local resources on the delegate in the directory where Terraform configurations are located, those resources are also removed. If you need those resources, make sure to generate them outside the Terraform working directory.

***

## YAML example

<details>

<summary>Terraform Apply step YAML</summary>

**Inherit from Plan (most common):**

```yaml
- step:
    type: TerraformApply
    name: Terraform Apply
    identifier: Terraform_Apply
    timeout: 10m
    spec:
      provisionerIdentifier: my_terraform_provisioner
      configuration:
        type: InheritFromPlan
      # Must use same delegate selector as Terraform Plan step
      delegateSelectors:
        - my-delegate-selector
```

**Inline configuration (standalone):**

```yaml
- step:
    type: TerraformApply
    name: Terraform Apply
    identifier: Terraform_Apply
    timeout: 10m
    spec:
      provisionerIdentifier: my_terraform_provisioner
      configuration:
        type: Inline
        spec:
          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
      delegateSelectors:
        - my-delegate-selector
```

**Key configuration:**

* `type: InheritFromPlan`: Most common - applies the plan from a Terraform Plan step
* `type: Inline`: Standalone apply without a separate plan step
* `provisionerIdentifier`: Must match the identifier in Terraform Plan step when using InheritFromPlan
* `delegateSelectors`: **REQUIRED** when using `storeTfPlanOnDelegate: true` in Plan step - ensures Apply runs on the same delegate pod where the plan file is stored

</details>

***

## Troubleshooting

<details>

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

Ensure the Provisioner Identifier in the Terraform Apply step exactly matches the identifier in the Terraform Plan step. Provisioner Identifiers are case-sensitive.

</details>

<details>

<summary>Configuration Type set to Inherit from Plan but Terraform Apply cannot find the plan</summary>

Verify that the Terraform Plan step completed successfully and used the same Provisioner Identifier. Check that both steps are in the same pipeline stage.

</details>

<details>

<summary>Remote workspace does not exist error when using Terraform Apply with remote backend</summary>

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

</details>

<details>

<summary>Terraform Apply state lock timeout in Harness deployment pipeline</summary>

Another process or pipeline may be holding the state lock. Wait for the lock to be released, or use Terraform CLI to manually unlock the state if the lock is stale.

</details>

***

## Next steps

* [Terraform Plan step](/continuous-delivery/use-continuous-delivery/provision-infrastructure/terraform-infra/run-a-terraform-plan-with-the-terraform-plan-step.md): Preview infrastructure changes before applying them.
* [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 %}
