> ## Documentation Index
> Fetch the complete documentation index at: https://www.qovery.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Terraform

> Deploy and manage Terraform modules natively on Qovery

## Introduction

Terraform Native Service enables you to deploy and manage Terraform or OpenTofu infrastructure code directly within Qovery. This service type allows you to provision cloud resources, configure external services, and manage infrastructure as code (IaC) using the same environment structure as your applications. Terraform executes within Kubernetes pods on your cluster, with automatic state management, variable injection, and integrated deployment workflows.

***

## Demo: Deploy S3 and Connect to Qovery Services

Watch this interactive demo to see how to deploy S3 infrastructure using Terraform and connect it to services deployed with Qovery:

<video src="https://mintcdn.com/qovery/vCq_OBXVLhOatlD8/videos/Deploy_S3_Infrastructure_using_Terraform_with_Qovery.mp4?fit=max&auto=format&n=vCq_OBXVLhOatlD8&q=85&s=a700a4b86a9c2c3b504e7390cc750456" controls data-path="videos/Deploy_S3_Infrastructure_using_Terraform_with_Qovery.mp4" />

***

## Creating a Terraform Service

<Steps>
  <Step title="Open Environment Overview">
    Navigate to the environment where you want to deploy your Terraform infrastructure.
  </Step>

  <Step title="Create New Service">
    Click **New Service** and select **Terraform** from the service type options.
  </Step>

  <Step title="Configure Service Name">
    Provide a **service name** that identifies this Terraform service (e.g., `aws-infrastructure`, `cloudflare-config`).
  </Step>

  <Step title="Select Git Repository">
    Choose the **Git repository** containing your Terraform code. This repository must include your `.tf` configuration files.

    Specify the **branch** and **root path** if your Terraform code is in a subdirectory.
  </Step>

  <Step title="Select Engine">
    Choose the execution engine:

    * **Terraform** - Official HashiCorp Terraform
    * **OpenTofu** - Open-source Terraform fork
  </Step>

  <Step title="Select Terraform Version">
    Choose the **Terraform version** to use for execution. Supported versions depend on your selected engine.
  </Step>

  <Step title="State Management">
    Configure how Terraform stores and manages state files. Qovery supports two state management modes.

    <Tabs>
      <Tab title="Default (Cluster-Managed)">
        By default, Terraform **state is managed inside the Kubernetes cluster**. State files are stored securely within the cluster and managed automatically by Qovery.

        This default configuration requires no additional setup and is ideal for getting started quickly.

        **Benefits:**

        * Zero configuration required
        * Automatic state management
        * Secure storage within your cluster
        * No external dependencies
      </Tab>

      <Tab title="Custom Backend (AWS S3)">
        If you prefer to manage your Terraform state in your own backend, you can configure a custom backend. **The standard for AWS is to use an S3 backend.**

        **Prerequisites**

        Before configuring your S3 backend, you need to:

        1. **Create an S3 bucket** in your AWS account to store the Terraform state
        2. **Enable bucket versioning** on your S3 bucket to allow state recovery in case of accidental deletions
        3. **Configure S3 state locking** using the `use_lockfile` option (this avoids the need for a DynamoDB table)
        4. **Prepare AWS credentials** with appropriate permissions to access the S3 bucket

        **Backend Configuration**

        Add a backend configuration block to your `main.tf` file in your Git repository:

        ```hcl theme={null}
        terraform {
          backend "s3" {
            bucket         = "my-terraform-backend"
            key            = "terraform.tfstate"
            region         = "eu-west-3"
            use_lockfile   = true
            encrypt        = true
          }
        }
        ```

        **Configuration parameters:**

        * `bucket` - Name of your S3 bucket for storing state files
        * `key` - Path to the state file within the bucket (typically `terraform.tfstate`)
        * `region` - AWS region where your S3 bucket is located
        * `use_lockfile` - Set to `true` to use S3's native locking mechanism instead of DynamoDB (recommended)
        * `encrypt` - Set to `true` to enable server-side encryption for the state file (recommended)

        <Info>
          Using `use_lockfile = true` eliminates the need to set up a DynamoDB table for state locking, simplifying your infrastructure. This is the recommended approach as DynamoDB-based locking is deprecated and will be removed in a future version of Terraform.
        </Info>

        <Tip>
          **Best Practices:**

          * Enable **bucket versioning** on your S3 bucket to allow state recovery in case of accidental deletions or corruption
          * Use `encrypt = true` to enable server-side encryption for your state files
          * Never hardcode sensitive values in your backend configuration
        </Tip>

        **Configuring AWS Credentials**

        To allow Terraform to access your S3 backend, you must provide AWS credentials as **environment variables** in the service configuration:

        ```text theme={null}
        AWS_ACCESS_KEY_ID=AKIA...
        AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
        AWS_DEFAULT_REGION=eu-west-3
        ```

        <Tip>
          Store these credentials as **Secrets** in Qovery to ensure they are encrypted and never exposed in logs.
        </Tip>

        **Required IAM Permissions**

        The AWS credentials must have the following IAM permissions on your S3 bucket:

        ```json theme={null}
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "s3:ListBucket"
              ],
              "Resource": "arn:aws:s3:::my-terraform-backend"
            },
            {
              "Effect": "Allow",
              "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
              ],
              "Resource": "arn:aws:s3:::my-terraform-backend/*"
            }
          ]
        }
        ```

        <Info>
          The `s3:DeleteObject` permission is required for managing the lock file when using `use_lockfile = true`.
        </Info>

        <Warning>
          **Using Cluster Credentials with Custom Backend**

          If you choose to use the default cluster credentials (instead of providing custom AWS credentials), be aware that the cluster's IAM policy **only allows access to S3 buckets with names prefixed with `qovery-`**.

          You have two options:

          1. **Name your bucket with the `qovery-` prefix** (e.g., `qovery-my-terraform-backend`)
          2. **Provide custom AWS credentials** (recommended) with appropriate permissions for your bucket name

          This restriction exists to prevent accidental access to unrelated S3 buckets in your AWS account.
        </Warning>

        **Migration from Default State**

        If you're migrating from the default cluster-managed state to a custom S3 backend:

        1. Add the backend configuration to your `main.tf`
        2. Configure the AWS credentials as environment variables
        3. Use the **Migrate State** action from the service's Action Toolbar to transfer the existing state to your S3 bucket
      </Tab>
    </Tabs>
  </Step>

  <Step title="Execution Timeout (Default)">
    The default **timeout is set to 1 hour**. This can be customized if your Terraform operations require more time.
  </Step>

  <Step title="Cloud Credentials (Default Behavior)">
    By default, Terraform uses **cluster credentials** when provisioning resources on the same cloud provider as your cluster.

    If you need to use **custom credentials** (e.g., a different AWS account, GCP project, or Azure subscription), you will configure them in the Environment Variables step.
  </Step>

  <Step title="Compute Resources (Default)">
    Terraform execution uses the following default compute resources:

    * **CPU**: 500,000 millicores (500 mCPU or 0.5 vCPU)
    * **Memory**: 512 MB
    * **Storage**: 1 GB

    These resources can be updated later in the **Service Settings** if your Terraform operations require more capacity.
  </Step>

  <Step title="Configure Terraform Variables">
    Qovery provides comprehensive variable management for Terraform, automatically detecting variables from your code and allowing flexible configuration.

    **Automatic Variable Detection**

    Qovery automatically loads variables from:

    * `main.tf`
    * `variables.tf`

    Variables detected by Qovery will appear prefixed with `tf_var_`.

    **Example:**

    If your `variables.tf` contains:

    ```hcl theme={null}
    variable "bucket_name" {
      type = string
    }

    variable "environment" {
      type = string
      default = "production"
    }
    ```

    Qovery will create:

    * `tf_var_bucket_name`
    * `tf_var_environment`

    **Importing TFVAR Files**

    You can import `.tfvars` files to configure multiple variables at once:

    1. Click **Import TFVAR**
    2. Select the TFVAR file(s) to import
    3. Choose which TFVAR files to apply
    4. Reorder the TFVAR files as needed

    <Info>
      **The last TFVAR file applied wins.** If multiple TFVAR files define the same variable, the value from the last file in the order takes precedence.
    </Info>

    **Manual Variable Override**

    You can manually override any variable value in two ways:

    1. **Direct Value Entry** - Enter a value directly:
       ```text theme={null}
       tf_var_bucket_name = "my-custom-bucket"
       ```
    2. **Reference Environment Variable** - Reference another environment variable:
       ```text theme={null}
       tf_var_bucket_name = ${MY_BUCKET_NAME}
       ```

    **Variables Not in main.tf or variables.tf**

    If you have variables defined in other `.tf` files that are not in `main.tf` or `variables.tf`, Qovery will not automatically detect them. You can create these variables manually using the `tf_var_` prefix:

    ```text theme={null}
    tf_var_my_custom_variable = "value"
    ```

    <Frame>
      <img src="https://mintcdn.com/qovery/ziWdn5St6rf4bcBc/images/configuration/terraform/terraform-variables-configuration.png?fit=max&auto=format&n=ziWdn5St6rf4bcBc&q=85&s=f065ca14fdd7ce7dacbea987ea80817a" alt="Configure Terraform Variables" width="3164" height="2070" data-path="images/configuration/terraform/terraform-variables-configuration.png" />
    </Frame>
  </Step>

  <Step title="Configure Environment Variables">
    Add standard environment variables that will be available during Terraform execution. This is where you configure custom cloud provider credentials, provider-specific settings, and other configuration.

    **Common use cases:**

    **Custom Cloud Credentials**

    Override the default cluster credentials with your own:

    **AWS:**

    ```text theme={null}
    AWS_ACCESS_KEY_ID=AKIA...
    AWS_SECRET_ACCESS_KEY=...
    AWS_DEFAULT_REGION=us-east-1
    ```

    **GCP:**

    ```text theme={null}
    GOOGLE_CREDENTIALS={"type":"service_account",...}
    GOOGLE_PROJECT=my-project-id
    GOOGLE_REGION=us-central1
    ```

    **Azure:**

    ```text theme={null}
    ARM_CLIENT_ID=...
    ARM_CLIENT_SECRET=...
    ARM_SUBSCRIPTION_ID=...
    ARM_TENANT_ID=...
    ```

    **Provider Configuration**

    Add provider-specific environment variables:

    **Cloudflare:**

    ```text theme={null}
    CLOUDFLARE_API_TOKEN=...
    CLOUDFLARE_ZONE_ID=...
    ```

    **Datadog:**

    ```text theme={null}
    DATADOG_API_KEY=...
    DATADOG_APP_KEY=...
    ```

    <Tip>
      Use Qovery **Secrets** for sensitive credentials to ensure they are encrypted and never exposed in logs.
    </Tip>

    **Terraform Environment Variables**

    You can also set Terraform-specific environment variables:

    ```text theme={null}
    TF_LOG=DEBUG
    TF_WORKSPACE=production
    ```
  </Step>

  <Step title="Review and Create">
    Review your configuration and click **Create** to provision the Terraform service.

    Optionally, you can select **Create & Run Plan** to execute `terraform plan` immediately and preview the execution plan before applying changes.
  </Step>
</Steps>

***

## Running Terraform

After creating your Terraform service, you can execute Terraform commands using the **Action Toolbar**.

<Frame>
  <img src="https://mintcdn.com/qovery/ziWdn5St6rf4bcBc/images/configuration/terraform/terraform-action-menu.png?fit=max&auto=format&n=ziWdn5St6rf4bcBc&q=85&s=6dd78ef769b3f60cf3e30bc36ea4eb5d" alt="Terraform Action Menu" width="3164" height="2070" data-path="images/configuration/terraform/terraform-action-menu.png" />
</Frame>

### Available Actions

The following Terraform operations are available from the service's Action Toolbar:

<AccordionGroup>
  <Accordion title="Plan">
    Executes `terraform plan` to preview infrastructure changes without applying them.

    Use this to:

    * Review what Terraform will create, modify, or destroy
    * Validate your Terraform configuration
    * Check for drift between your code and actual infrastructure
  </Accordion>

  <Accordion title="Plan & Apply">
    Executes `terraform plan` followed by `terraform apply` to provision or update infrastructure.

    This is the standard deployment action that:

    * Generates an execution plan
    * Applies changes to your infrastructure
    * Updates the Terraform state
  </Accordion>

  <Accordion title="Destroy">
    Executes `terraform destroy` to remove all resources managed by this Terraform service.

    <Warning>
      This action is **irreversible** and will delete all infrastructure resources defined in your Terraform code.
    </Warning>
  </Accordion>

  <Accordion title="Force Unlock">
    Releases a stuck Terraform state lock.

    Use this if:

    * A previous Terraform operation was interrupted
    * State is locked and preventing new operations
    * You see "state is locked" errors

    <Warning>
      Only use Force Unlock if you're certain no other Terraform operation is running.
    </Warning>
  </Accordion>

  <Accordion title="Migrate State">
    Migrates Terraform state between storage backends or updates state schema.

    This operation runs `terraform init -migrate-state` to:

    * Move state to a new backend
    * Upgrade state file format
    * Reconfigure state storage
  </Accordion>
</AccordionGroup>

### Create & Run Plan

During service creation, you can select **Create & Run Plan** to immediately execute `terraform plan` after the service is created.

This allows you to:

* Preview infrastructure changes before deployment
* Validate your Terraform configuration
* Review the execution plan before committing to apply

***

## Terraform Service Settings

After creating your Terraform service, you can update all configuration options in the **Service Settings**.

### General Configuration

All parameters from the creation flow can be modified:

<AccordionGroup>
  <Accordion title="Git Repository">
    * Change the Git repository source
    * Update the branch to deploy from
    * Modify the root path for Terraform code location
  </Accordion>

  <Accordion title="Engine">
    * Switch between Terraform and OpenTofu
    * This change will take effect on the next deployment
  </Accordion>

  <Accordion title="Version">
    * Update the Terraform or OpenTofu version
    * Useful for testing new versions or maintaining compatibility
  </Accordion>

  <Accordion title="State Management">
    * Modify state storage configuration
    * Update state backend settings if using custom backend
  </Accordion>

  <Accordion title="Timeout">
    * Adjust the execution timeout (default: 1 hour)
    * Increase for long-running Terraform operations
    * Decrease to fail faster for quick validations
  </Accordion>

  <Accordion title="Execution Resources">
    * Update CPU allocation (default: 500 mCPU)
    * Modify memory allocation (default: 512 MB)
    * Adjust storage allocation (default: 1 GB)

    Increase these resources if your Terraform operations:

    * Manage a large number of resources
    * Require significant memory for state processing
    * Need more CPU for provider operations
  </Accordion>

  <Accordion title="Environment Variables">
    * Add or update cloud provider credentials
    * Configure Terraform input variables (`TF_VAR_*`)
    * Set provider-specific environment variables
    * Reference secrets for sensitive values
  </Accordion>
</AccordionGroup>

### Terraform Arguments

The **Terraform Arguments** section allows you to specify additional CLI arguments for each Terraform command. These arguments override default behaviors and enable advanced customization.

<Frame>
  <img src="https://mintcdn.com/qovery/ziWdn5St6rf4bcBc/images/configuration/terraform/terraform-arguments.png?fit=max&auto=format&n=ziWdn5St6rf4bcBc&q=85&s=48ec0c2ca46b8f421a5b616f3df22066" alt="Terraform Arguments Configuration" width="3164" height="2070" data-path="images/configuration/terraform/terraform-arguments.png" />
</Frame>

<Info>
  Terraform Arguments provide fine-grained control over Terraform execution. Use these to customize init, validate, plan, apply, and destroy operations.
</Info>

<AccordionGroup>
  <Accordion title="Init Arguments">
    Customize `terraform init` behavior.

    **Common arguments:**

    * `-upgrade` - Upgrade modules and providers to latest versions
    * `-reconfigure` - Reconfigure backend ignoring existing configuration
    * `-backend-config=...` - Override backend configuration

    **Example:**

    ```text theme={null}
    -upgrade -backend-config="bucket=my-state-bucket"
    ```
  </Accordion>

  <Accordion title="Validate Arguments">
    Customize `terraform validate` behavior.

    **Common arguments:**

    * `-json` - Output validation results in JSON format
    * `-no-color` - Disable colored output

    **Example:**

    ```text theme={null}
    -json -no-color
    ```
  </Accordion>

  <Accordion title="Plan Arguments">
    Customize `terraform plan` behavior.

    **Common arguments:**

    * `-target=resource.name` - Plan changes for specific resource only
    * `-var="key=value"` - Set a variable value
    * `-var-file=filename` - Load variables from file
    * `-out=filename` - Save plan to a file
    * `-refresh=false` - Skip state refresh
    * `-parallelism=n` - Limit concurrent operations

    **Example:**

    ```text theme={null}
    -target=aws_s3_bucket.main -parallelism=5
    ```
  </Accordion>

  <Accordion title="Apply Arguments">
    Customize `terraform apply` behavior.

    **Common arguments:**

    * `-target=resource.name` - Apply changes to specific resource only
    * `-var="key=value"` - Set a variable value
    * `-var-file=filename` - Load variables from file
    * `-parallelism=n` - Limit concurrent operations
    * `-refresh=false` - Skip state refresh

    **Example:**

    ```text theme={null}
    -parallelism=10 -refresh=false
    ```
  </Accordion>

  <Accordion title="Destroy Arguments">
    Customize `terraform destroy` behavior.

    **Common arguments:**

    * `-target=resource.name` - Destroy specific resource only
    * `-var="key=value"` - Set a variable value
    * `-parallelism=n` - Limit concurrent operations
    * `-refresh=false` - Skip state refresh

    <Warning>
      Use destroy arguments carefully. Targeting specific resources can lead to incomplete cleanup or resource dependencies issues.
    </Warning>

    **Example:**

    ```text theme={null}
    -target=aws_s3_bucket.temp -parallelism=2
    ```
  </Accordion>
</AccordionGroup>

**Argument Format:**

* Separate multiple arguments with spaces
* Quote values containing spaces: `-var="name=my value"`
* Use multiple `-var` flags for multiple variables: `-var="a=1" -var="b=2"`

**Example Full Configuration:**

```text theme={null}
Init: -upgrade
Plan: -parallelism=10 -var="environment=production"
Apply: -parallelism=10 -var="environment=production"
Destroy: -parallelism=5 -refresh=false
```

***

## Custom Build Image

By default, Terraform services run using a base Docker image (Debian-based) containing Terraform (or OpenTofu), `dumb-init`, `rsync`, `bash`, and `ca-certificates`. If your Terraform code requires additional binaries or tools (e.g., AWS CLI, `kubectl`, `jq`, custom scripts), you can customize the build image using a **Dockerfile fragment**.

### Configuring a Dockerfile Fragment

Qovery provides two ways to inject custom Dockerfile commands during the build:

1. **File-based**: Reference a Dockerfile fragment file stored in your Git repository
2. **Inline**: Provide Dockerfile commands directly in the service configuration

<Tabs>
  <Tab title="File-based Fragment">
    Reference a Dockerfile fragment file from your repository.

    <Steps>
      <Step title="Create Fragment File">
        In your Git repository, create a Dockerfile fragment file in your Terraform code directory or a custom location.

        **Example directory structure:**

        ```text theme={null}
        my-repo/
        ├── terraform/
        │   ├── main.tf
        │   ├── variables.tf
        │   └── custom-build.dockerfile   # Your fragment file
        └── README.md
        ```
      </Step>

      <Step title="Add Dockerfile Instructions">
        Add valid Dockerfile instructions to install the tools you need. The fragment is injected into the build **after** your Terraform files are copied and **before** the final user switch.

        See the **Fragment Examples** section below for common use cases.
      </Step>

      <Step title="Configure Service">
        In the Terraform service settings, configure the Dockerfile fragment:

        * Navigate to **Service Settings** → **Dockerfile Fragment**
        * Select **Custom file path**
        * Enter the absolute path to your fragment file (e.g., `/terraform/custom-build.dockerfile`)

        <Info>
          The path must be absolute (starting with `/`) and located within your service's `root_path`.
        </Info>
      </Step>

      <Step title="Deploy">
        Save your changes and deploy. Qovery will inject the fragment contents during the Docker build.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Inline Fragment">
    Define Dockerfile commands directly in the service configuration without creating a file in your repository.

    <Steps>
      <Step title="Configure Service">
        In the Terraform service settings:

        * Navigate to **Service Settings** → **Dockerfile Fragment**
        * Select **Inline content**
        * Enter your Dockerfile commands in the text area

        **Example:**

        ```dockerfile theme={null}
        # Install AWS CLI and common utilities
        RUN apt-get update && \
            apt-get install -y --no-install-recommends awscli jq curl && \
            rm -rf /var/lib/apt/lists/*
        ```
      </Step>

      <Step title="Deploy">
        Save your changes and deploy. Qovery will inject the inline content during the Docker build.
      </Step>
    </Steps>

    <Info>
      Inline fragments are limited to 8KB of content. For larger customizations, use a file-based fragment.
    </Info>
  </Tab>
</Tabs>

### Fragment Examples

**Installing AWS CLI and common tools:**

```dockerfile theme={null}
# Install AWS CLI and common utilities
RUN apt-get update && \
    apt-get install -y --no-install-recommends awscli jq curl && \
    rm -rf /var/lib/apt/lists/*
```

**Installing kubectl:**

```dockerfile theme={null}
# Install kubectl (curl is already available in the base image)
RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && \
    chmod +x kubectl && \
    mv kubectl /usr/local/bin/
```

**Installing a custom binary from your repository:**

```dockerfile theme={null}
# Install a custom tool already present in the repository
# (repository files are already copied to /data)
RUN cp /data/bin/my-custom-tool /usr/local/bin/my-custom-tool && \
    chmod +x /usr/local/bin/my-custom-tool
```

**Downloading and extracting a remote archive:**

```dockerfile theme={null}
# Download and extract a tool from the internet
ADD https://releases.example.com/tool-v1.2.3.tar.gz /tmp/tool.tar.gz
RUN tar -xzf /tmp/tool.tar.gz -C /usr/local/bin/ && \
    rm /tmp/tool.tar.gz
```

### Supported Dockerfile Instructions

The fragment supports the following Dockerfile instructions:

* `RUN` - Execute commands during build (install packages, configure tools, etc.)
* `ADD` - Add files with URL/archive support (download and extract remote archives)

<Info>
  Repository files are already present in `/data` before the fragment executes, so you can reference them in `RUN` commands without needing `COPY`.
</Info>

<Warning>
  The fragment runs as `root` during the build. The container switches to a non-root user (`app`) after your fragment executes. Make sure any binaries you install are accessible to all users.
</Warning>

<Info>
  This feature is specific to Terraform and OpenTofu services. Applications and Jobs use different build mechanisms.
</Info>

***

## Clone Service

You can create a clone of the service via the clone feature. A new service with the same configuration will be created into the target environment.

<Steps>
  <Step title="Select Service">
    Go to the Terraform service you want to clone
  </Step>

  <Step title="Clone Service">
    Click on the **three dots button** and select **Clone**
  </Step>

  <Step title="Select Target Environment">
    Choose the target environment where you want to clone the service
  </Step>
</Steps>

<Info>
  The target environment can be the same as the current environment or a different one in a completely different project.
</Info>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Environment Variables" icon="key" href="/configuration/environment-variables">
    Learn how to manage variables and secrets
  </Card>

  <Card title="Terraform Provider" icon="code" href="/terraform-provider/overview">
    Use Qovery Terraform Provider to manage Qovery resources
  </Card>

  <Card title="Lifecycle Jobs" icon="clock" href="/configuration/lifecycle-job">
    Run scripts during deployment lifecycle
  </Card>

  <Card title="Deployment Overview" icon="rocket" href="/configuration/deployment/overview">
    Understand deployment workflows
  </Card>
</CardGroup>
