This guide walks you through deploying infrastructure with Terraform using DeployHQ's [Custom Action](Article: custom-action) protocol. Custom Actions run CLI tools inside Docker containers during your deployment pipeline, making it straightforward to manage infrastructure as code directly from DeployHQ.

## Prerequisites

Before you begin, ensure you have:

* **Beta Features Enabled:** Custom Actions require beta features to be enabled in your [account settings](Article: #682)
* **A Terraform Project:** A repository containing your Terraform configuration files (`.tf` files)
* **Cloud Provider Credentials:** Access keys for your target provider (AWS, GCP, or Azure)
* **Remote State Backend (Recommended):** An S3 bucket, GCS bucket, or Azure Storage Account configured for remote state

## Setting Up the Server

1. Open your project in DeployHQ and navigate to **Servers**.
2. Click **New Server** and select **Custom Action** as the protocol.
3. Under **Image Source**, select **Curated Template**.
4. Choose **Terraform** from the template dropdown. This sets the Docker image to `hashicorp/terraform:latest`.
5. Enter your Terraform commands in the **Commands** field:

   ```text
   cd /data/infrastructure
   terraform init -input=false
   terraform plan -input=false -out=tfplan
   terraform apply -input=false tfplan
   ```

6. Set **Halt on error** to stop the deployment if any command fails.
7. Click **Create Server**.

## Configuring Credentials

Terraform authenticates with cloud providers using environment variables. Add these to your server's [environment variables](Article: #163) in DeployHQ.

### AWS

| Variable | Value |
|----------|-------|
| `AWS_ACCESS_KEY_ID` | Your AWS access key |
| `AWS_SECRET_ACCESS_KEY` | Your AWS secret key |
| `AWS_DEFAULT_REGION` | Target region (e.g., `us-east-1`) |

### Google Cloud

| Variable | Value |
|----------|-------|
| `GOOGLE_CREDENTIALS` | Service account JSON key (full contents) |
| `GOOGLE_PROJECT` | Your GCP project ID |

### Azure

| Variable | Value |
|----------|-------|
| `ARM_CLIENT_ID` | Service principal application ID |
| `ARM_CLIENT_SECRET` | Service principal password |
| `ARM_SUBSCRIPTION_ID` | Your Azure subscription ID |
| `ARM_TENANT_ID` | Your Azure tenant ID |

## Example Commands

### Basic Apply

The simplest setup runs init, plan, and apply in sequence:

```text
cd /data/infrastructure
terraform init -input=false
terraform apply -input=false -auto-approve
```

### Plan and Apply with Saved Plan

For safer deployments, save the plan to a file and apply it explicitly:

```text
cd /data/infrastructure
terraform init -input=false
terraform plan -input=false -out=tfplan
terraform apply -input=false tfplan
```

### Targeted Apply

Apply changes to specific resources only:

```text
cd /data/infrastructure
terraform init -input=false
terraform apply -input=false -auto-approve -target=aws_ecs_service.app
```

## Remote State Configuration

Running Terraform through DeployHQ means each deployment starts in a fresh container. You must use a remote state backend to persist your state file between runs.

Add a backend block to your Terraform configuration:

```hcl
terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "deployhq/terraform.tfstate"
    region = "us-east-1"
  }
}
```

Other supported backends include GCS (`gcs`), Azure Storage (`azurerm`), and Terraform Cloud (`remote`).

## Multi-Environment Usage

To deploy different environments (staging, production), you can use Terraform workspaces or variable files.

### Using Variable Files

Store environment-specific variables in separate `.tfvars` files and reference them in your commands:

```text
cd /data/infrastructure
terraform init -input=false
terraform apply -input=false -auto-approve -var-file=environments/production.tfvars
```

You can use DeployHQ [text variables](Article: #165) to make this dynamic:

```text
cd /data/infrastructure
terraform init -input=false
terraform apply -input=false -auto-approve -var-file=environments/%server%.tfvars
```

### Using Workspaces

Select a workspace before applying:

```text
cd /data/infrastructure
terraform init -input=false
terraform workspace select production || terraform workspace new production
terraform apply -input=false -auto-approve
```

## Troubleshooting

**"Backend initialization required":**

* Ensure your remote state backend credentials are configured as environment variables on the server
* Verify the backend bucket or storage account exists and is accessible

**"Error acquiring the state lock":**

* A previous deployment may still hold the lock. Wait for it to complete, or use `terraform force-unlock LOCK_ID` if you are sure no other operation is running

**"No changes. Infrastructure is up-to-date":**

* This is normal when your infrastructure matches your configuration. The deployment will still succeed.

**Credential errors:**

* Double-check that environment variables are set correctly on the DeployHQ server
* Ensure the credentials have sufficient permissions for the resources being managed
* For AWS, verify the IAM user or role has the required policies attached

**Timeout errors:**

* Custom Actions have a 30-minute timeout per command. Large infrastructure changes may exceed this limit.
* Consider breaking large changes into smaller, targeted applies
