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

# Remote Backends - CDK Terrain

> Configure a remote backend where Terraform can store infrastructure state files remotely.

Terraform (or OpenTofu) stores [state](https://developer.hashicorp.com/terraform/language/state) about managed infrastructure to map real-world resources to the configuration, keep track of metadata, and improve performance. Terraform stores this state in a local file by default, but you can also use a Terraform [remote backend](https://developer.hashicorp.com/terraform/language/settings/backends/remote) to store state remotely.

By default, `cdktn init` will configure a HCP Terraform workspace and a corresponding remote backend to store state for the new project. If you run `cdktn init --local` to configure your new project to use a local backend to store state, you can still [migrate the state](#migrate-local-state-storage-to-remote) to a remote backend later.

You can configure your CDK Terrain (CDKTN) remote backend to be [HCP Terraform](https://cloud.hashicorp.com/products/terraform), another Terraform [supported backend](#supported-backends), or a custom location.

## When to Use Remote Backends

Consider using a remote backend when multiple individuals or teams need access to your infrastructure state data.

[Remote state](https://developer.hashicorp.com/terraform/language/state/remote) makes it easier for teams to work together because all members have access to the latest state data in the remote store. It also allows you to share output values with other configurations, allowing groups to share infrastructure resources. For example, a core infrastructure team can handle building the core machines and then expose some information that other teams can use for their own infrastructure.

## Define Remote Backends

You can define a [JSON configuration for a remote backend](https://developer.hashicorp.com/terraform/language/syntax/json#terraform-blocks)
with a `TerraformBackend` subclass or a JSON configuration file.

The following example uses the `TerraformBackend` subclass `CloudBackend`.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { Construct } from "constructs";
  import {
    CloudBackend,
    TerraformStack,
    TerraformOutput,
    NamedCloudWorkspace,
    App,
  } from "cdktn";

  export class CloudBackendStack extends TerraformStack {
    constructor(scope: Construct, id: string) {
      super(scope, id);

      new CloudBackend(this, {
        hostname: "app.terraform.io",
        organization: "company",
        workspaces: new NamedCloudWorkspace("my-app-prod"),
      });

      new TerraformOutput(this, "dns-server", {
        value: "hello-world",
      });
    }
  }
  ```

  ```java Java theme={null}
  import software.constructs.Construct;
  import io.cdktn.cdktn.TerraformStack;
  import io.cdktn.cdktn.App;
  import io.cdktn.cdktn.CloudBackend;
  import io.cdktn.cdktn.CloudBackendConfig;
  import io.cdktn.cdktn.NamedCloudWorkspace;
  import io.cdktn.cdktn.TerraformOutput;
  import io.cdktn.cdktn.TerraformOutputConfig;

  public class MainRemoteBackendDefine extends TerraformStack {

      public MainRemoteBackendDefine(Construct scope, String id) {
          super(scope, id);

          new CloudBackend(this, CloudBackendConfig.builder()
                  .hostname("app.terraform.io")
                  .organization("company")
                  .workspaces(new NamedCloudWorkspace("my-app-prod"))
                  .build()
          );

          new TerraformOutput(this, "dns-server", TerraformOutputConfig.builder()
                  .value("hello-world")
                  .build()
          );
      }
  }
  ```

  ```python Python theme={null}
  from constructs import Construct
  from cdktn import App, CloudBackend, NamedCloudWorkspace, TerraformStack, TerraformOutput
  class RemoteBackendStack(TerraformStack):
      def __init__(self, scope: Construct, id: str):
          super().__init__(scope, id)

          CloudBackend(self,
              hostname = "app.terraform.io",
              organization = "company",
              workspaces = NamedCloudWorkspace("my-app-prod")
          )

          TerraformOutput(self, "dns-server",
              value = "hello-world"
          )

  app = App()
  RemoteBackendStack(app, "hello-terraform")
  app.synth()
  ```

  ```csharp C# theme={null}
  using System;
  using System.IO;
  using System.Collections.Generic;
  using System.Linq;
  using Constructs;
  using Io.Cdktn;

  namespace Examples
  {
      class CloudBackendStack : TerraformStack
      {
          public CloudBackendStack(Construct scope, string name) : base(scope, name)
          {
              new CloudBackend(this, new CloudBackendConfig
              {
                  Hostname = "app.terraform.io",
                  Organization = "company",
                  Workspaces = new NamedCloudWorkspace("my-app-prod")
              });

              new TerraformOutput(this, "dns-server", new TerraformOutputConfig
              {
                  Value = "hello-world"
              });
          }
      }
  }
  ```

  ```go Go theme={null}
  import (
  	"github.com/aws/constructs-go/constructs/v10"
  	"github.com/aws/jsii-runtime-go"
  	"github.com/open-constructs/cdk-terrain-go/cdktn"
  )

  func NewCloudBackendStack(scope constructs.Construct, name string) cdktn.TerraformStack {
  	stack := cdktn.NewTerraformStack(scope, &name)

  	cdktn.NewCloudBackend(stack, &cdktn.CloudBackendConfig{
  		Hostname:     jsii.String("app.terraform.io"),
  		Organization: jsii.String("company"),
  		Workspaces:   cdktn.NewNamedCloudWorkspace(jsii.String("my-app-prod"), nil),
  	})

  	cdktn.NewTerraformOutput(stack, jsii.String("dns-server"), &cdktn.TerraformOutputConfig{
  		Value: "hello-world",
  	})

  	return stack
  }

  ```
</CodeGroup>

When you call `cdktn synth`, CDKTN stores the backend metadata like the organization name and workspace name in the `cdk.tf.json` file within the `cdktf.out` stack sub-directory containing the synthesized CDKTN code. For example, CDKTN creates the output for a stack called `hello-terraform` in `cdktf.out/stacks/hello-terraform`.

The following example shows the stack output directory.

```bash theme={null}
tree .
.
└── cdk.tf.json
```

The following example shows a relevant snippet of the generated `cdk.tf.json` file.

```json theme={null}
{
  "//": {
    "metadata": {
      "backend": "cloud",
      "cloud": "tfc",
      "stackName": "hello-terraform",
      "version": "0.20.11"
    }
  },
  "terraform": {
    "cloud": {
      "hostname": "app.terraform.io",
      "organization": "company",
      "workspaces": {
        "name": "hello-terraform"
      }
    }
  }
}
```

## Initialize Remote Backends

All `cdktn` operations perform an automatic `terraform init`, but you can also initialize manually.

To manually initialize a remote backend, go to the corresponding stack output directory in the `cdktf.out` folder and run `terraform init`.

```shell theme={null}
$ cd cdkf.out/stacks/hello-terraform
$ terraform init
```

## Migrate Local State Storage to Remote

After you define your remote backend, you can migrate existing local state files to the designated remote location. This requires moving Terraform state files to the CDKTN output directory.

Consider an example project called `hello-terraform` that is using local storage to store the Terraform state. To migrate the local stage files to the remote backend:

1. Navigate into the main project directory.

2. Use `CloudBackend` to add a new remote backend.

<CodeGroup>
  ```ts TypeScript theme={null}
  class LocalBackendStack extends TerraformStack {
    constructor(scope: Construct, id: string) {
      super(scope, id);

      new TerraformOutput(this, "dns-server", {
        value: "local",
      });
    }
  }

  const app = new App();
  const stack = new LocalBackendStack(app, "local-to-cloud-backend");
  new CloudBackend(stack, {
    hostname: "app.terraform.io",
    organization: "company",
    workspaces: new NamedCloudWorkspace("my-app-prod"),
  });
  ```

  ```java Java theme={null}
  public class MainRemoteBackend extends TerraformStack {

      public MainRemoteBackend(Construct scope, String id) {
          super(scope, id);

          new TerraformOutput(this, "dns-server", TerraformOutputConfig.builder()
                  .value("local")
                  .build()
          );
      }

      public static void main(String[] args) {
          final App app = new App();
          MainRemoteBackend stack = new MainRemoteBackend(app, "local-to-cloud-backend");
          new CloudBackend(stack, CloudBackendConfig.builder()
                  .hostname("app.terraform.io")
                  .organization("company")
                  .workspaces(new NamedCloudWorkspace("my-app-prod"))
                  .build()
          );
          app.synth();
      }
  }
  ```

  ```python Python theme={null}
  stack = Stack(App(), "hi-terraform")
          CloudBackend(self,
              hostname = "app.terraform.io",
              organization = "company",
              workspaces = NamedCloudWorkspace("my-app-prod")
          )
  ```

  ```csharp C# theme={null}
  using System;
  using System.IO;
  using System.Collections.Generic;
  using System.Linq;
  using Constructs;
  using Io.Cdktn;

  namespace Examples
  {
      class LocalBackendStack : TerraformStack
      {
          public LocalBackendStack(Construct scope, string name) : base(scope, name)
          {
              new TerraformOutput(this, "dns-server", new TerraformOutputConfig
              {
                  Value = "local"
              });
          }
      }
  }

  App app = new App();
  LocalBackendStack stack = new LocalBackendStack(app, "local-to-cloud-backend");
  new CloudBackend(stack, new CloudBackendConfig {
      Hostname = "app.terraform.io",
      Organization = "company",
      Workspaces = new NamedCloudWorkspace("my-app-prod")
  });

  app.Synth();
  ```

  ```go Go theme={null}

  func NewLocalBackendStack(scope constructs.Construct, name string) cdktn.TerraformStack {
  	stack := cdktn.NewTerraformStack(scope, &name)

  	cdktn.NewTerraformOutput(stack, jsii.String("dns-server"), &cdktn.TerraformOutputConfig{
  		Value: "local",
  	})

  	return stack
  }

  func main() {
  	app := cdktn.NewApp(nil)

  	stack := NewLocalBackendStack(app, "hello-terraform")
  	cdktn.NewCloudBackend(stack, &cdktn.CloudBackendConfig{
  		Hostname:     jsii.String("app.terraform.io"),
  		Organization: jsii.String("company"),
  		Workspaces:   cdktn.NewNamedCloudWorkspace(jsii.String("my-app-prod"), nil),
  	})

  	app.Synth()
  }

  ```
</CodeGroup>

3. Run `cdktn diff <stack name> --migrate-state` to migrate the state into HCP Terraform or Terraform Enterprise.

   ```bash theme={null}
   Initializing Terraform Cloud...
   Migrating from backend "local" to Terraform Cloud.
   Do you wish to proceed?
                 As part of migrating to Terraform Cloud, Terraform can optionally copy your
                 current workspace state to the configured Terraform Cloud workspace.

                 Answer "yes" to copy the latest state snapshot to the configured
                 Terraform Cloud workspace.

                 Answer "no" to ignore the existing state and just activate the configured
                 Terraform Cloud workspace with its existing state, if any.

                 Should Terraform migrate your existing state?

                 Enter a value:
   yes
   Initializing provider plugins...
               - Reusing previous version of hashicorp/random from the dependency lock file
   - Using previously-installed hashicorp/random v3.4.3
   Terraform Cloud has been successfully initialized!
   ```

# Supported Backends

In addition to HCP Terraform, Terraform and CDKTN support the following backends.

* [local](https://developer.hashicorp.com/terraform/language/settings/backends/local)
  ```typescript theme={null}
  new LocalBackend(stack, {...});
  ```
* [azurerm](https://developer.hashicorp.com/terraform/language/settings/backends/azurerm)
  ```typescript theme={null}
  new AzurermBackend(stack, {...});
  ```
* [consul](https://developer.hashicorp.com/terraform/language/settings/backends/consul)
  ```typescript theme={null}
  new ConsulBackend(stack, {...});
  ```
* [cos](https://developer.hashicorp.com/terraform/language/settings/backends/cos)
  ```typescript theme={null}
  new CosBackend(stack, {...});
  ```
* [gcs](https://developer.hashicorp.com/terraform/language/settings/backends/gcs)
  ```typescript theme={null}
  new GcsBackend(stack, {...});
  ```
* [http](https://developer.hashicorp.com/terraform/language/settings/backends/http)
  ```typescript theme={null}
  new HttpBackend(stack, {...});
  ```
* [oss](https://developer.hashicorp.com/terraform/language/settings/backends/oss)
  ```typescript theme={null}
  new OssBackend(stack, {...});
  ```
* [pg](https://developer.hashicorp.com/terraform/language/settings/backends/pg)
  ```typescript theme={null}
  new PgBackend(stack, {...});
  ```
* [s3](https://developer.hashicorp.com/terraform/language/settings/backends/s3)
  ```typescript theme={null}
  new S3Backend(stack, {...});
  ```

<Note>CDK Terrain v0.14 deprecated the artifactory, etcd, etcdv3, manta, and swift backends, and removed them in v0.20. Terraform removed these backends in v1.3. For migration paths from these removed backends, refer to [Upgrading to Terraform v1.3](https://developer.hashicorp.com/terraform/language/v1.3.x/upgrade-guides).</Note>

## State Locking with the S3 Backend

When several people or automated pipelines run against the same state, Terraform (or OpenTofu) uses [state locking](https://developer.hashicorp.com/terraform/language/state/locking) to prevent concurrent runs from corrupting the state file.

The S3 backend supports two locking mechanisms:

* **S3-native locking** (recommended) via the `useLockfile` property. Terraform writes a lock file alongside your state object in the same bucket — at `<key>.tflock` — and removes it when the operation completes. No additional infrastructure is required. S3-native locking is available in Terraform 1.10 and later and OpenTofu 1.10 and later.
* **DynamoDB locking** via the `dynamodbTable` property, which points the backend at a DynamoDB table whose partition key is `LockID`. This is the legacy mechanism and is now deprecated in favor of `useLockfile`.

If you set neither property, state locking is disabled.

### Enable S3-Native Locking

Set `useLockfile` to `true` on the `S3Backend` configuration.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { Construct } from "constructs";
  import { S3Backend, TerraformStack } from "cdktn";

  export class S3BackendStack extends TerraformStack {
    constructor(scope: Construct, id: string) {
      super(scope, id);

      new S3Backend(this, {
        bucket: "my-terraform-state",
        key: "my-app/terraform.tfstate",
        region: "us-east-1",
        encrypt: true,
        useLockfile: true,
      });
    }
  }
  ```

  ```java Java theme={null}
  import software.constructs.Construct;
  import io.cdktn.cdktn.TerraformStack;
  import io.cdktn.cdktn.S3Backend;
  import io.cdktn.cdktn.S3BackendConfig;

  public class MainS3Backend extends TerraformStack {

      public MainS3Backend(Construct scope, String id) {
          super(scope, id);

          new S3Backend(this, S3BackendConfig.builder()
                  .bucket("my-terraform-state")
                  .key("my-app/terraform.tfstate")
                  .region("us-east-1")
                  .encrypt(true)
                  .useLockfile(true)
                  .build()
          );
      }
  }
  ```

  ```python Python theme={null}
  from constructs import Construct
  from cdktn import S3Backend, TerraformStack

  class S3BackendStack(TerraformStack):
      def __init__(self, scope: Construct, id: str):
          super().__init__(scope, id)

          S3Backend(self,
              bucket = "my-terraform-state",
              key = "my-app/terraform.tfstate",
              region = "us-east-1",
              encrypt = True,
              use_lockfile = True
          )
  ```

  ```csharp C# theme={null}
  using Constructs;
  using Io.Cdktn;

  namespace Examples
  {
      class S3BackendStack : TerraformStack
      {
          public S3BackendStack(Construct scope, string name) : base(scope, name)
          {
              new S3Backend(this, new S3BackendConfig
              {
                  Bucket = "my-terraform-state",
                  Key = "my-app/terraform.tfstate",
                  Region = "us-east-1",
                  Encrypt = true,
                  UseLockfile = true
              });
          }
      }
  }
  ```

  ```go Go theme={null}
  import (
  	"github.com/aws/constructs-go/constructs/v10"
  	"github.com/aws/jsii-runtime-go"
  	"github.com/open-constructs/cdk-terrain-go/cdktn"
  )

  func NewS3BackendStack(scope constructs.Construct, name string) cdktn.TerraformStack {
  	stack := cdktn.NewTerraformStack(scope, &name)

  	cdktn.NewS3Backend(stack, &cdktn.S3BackendConfig{
  		Bucket:      jsii.String("my-terraform-state"),
  		Key:         jsii.String("my-app/terraform.tfstate"),
  		Region:      jsii.String("us-east-1"),
  		Encrypt:     jsii.Bool(true),
  		UseLockfile: jsii.Bool(true),
  	})

  	return stack
  }
  ```
</CodeGroup>

The identity that runs `cdktn` operations needs `s3:GetObject`, `s3:PutObject`, and `s3:DeleteObject` permissions on the lock file path so Terraform can create and release the lock.

<Note>
  S3-native locking requires Terraform 1.10 or OpenTofu 1.10 (or later).
  Declare the runtime versions your project supports in the
  [`targetVersions`](/create-and-deploy/configuration-file#declare-target-runtimes)
  field of `cdktf.json` so that synthesis validates native locking against the
  versions you target — the default targets (`terraform >=1.5.7`,
  `opentofu >=1.6.0`) predate it. This is the same declarative,
  binary-free check CDK Terrain applies to
  [function usage at synth time](/concepts/functions#validating-function-usage-at-synth-time).

  ```json theme={null}
  {
    "targetVersions": {
      "terraform": ">=1.10.0",
      "opentofu": ">=1.10.0"
    }
  }
  ```
</Note>

### Migrate from DynamoDB to S3-Native Locking

If you already lock state with a DynamoDB table, you can move to S3-native locking without a window where state is unprotected by enabling both mechanisms for a single run, then removing the DynamoDB table.

1. Keep `dynamodbTable` and add `useLockfile: true` to your `S3Backend` configuration. With both set, Terraform acquires both locks.

   ```ts theme={null}
   new S3Backend(this, {
     bucket: "my-terraform-state",
     key: "my-app/terraform.tfstate",
     region: "us-east-1",
     dynamodbTable: "my-lock-table",
     useLockfile: true,
   });
   ```

2. Run `cdktn deploy` (or `cdktn diff`) once so the change takes effect and the backend is reconfigured.

3. Remove `dynamodbTable`, leaving only `useLockfile: true`. After your next run no longer references the table, you can delete the DynamoDB table.

<Note>`dynamodbTable` remains available for backwards compatibility, but it is deprecated. Terraform deprecated the `dynamodb_table` argument in the S3 backend starting with v1.11 and may remove DynamoDB-based locking in a future release. Prefer `useLockfile` for new projects.</Note>

## Escape Hatches

Escape hatches can add to or override existing resources, and you can use them for backends or backend constructs that CDKTN does not natively support. Escape hatch methods have an `Override` suffix (e.g., `addOverride`).

The following example uses an escape hatch to add an unsupported remote backend on a `Stack` object.

<CodeGroup>
  ```ts TypeScript theme={null}
  stack.addOverride("terraform.backend", {
    atlas: {
      name: "example_corp/networking-prod",
      address: "https://app.terraform.io",
    },
  });
  ```

  ```java Java theme={null}
  stack.addOverride("terraform.backend", new HashMap<String, HashMap>() {
      {
          put("atlas", new HashMap<String, String>() {
              {
                  put("name", "example_corp/networking-prod");
                  put("address", "https://app.terraform.io");
              }
          });
      }
  });
  ```

  ```python Python theme={null}
  stack.add_override("terraform.backend",{
      "atlas": {
          "name": "example_corp/networking-prod",
          "address": "https://app.terraform.io"
      }
  })
  ```

  ```csharp C# theme={null}
  stack.AddOverride("terraform.backend", new Dictionary<String, Object> {
    { "atlas", new Dictionary<String, Object> {
      { "name", "example_corp/networking-prod" },
      { "address", "https://app.terraform.io" }
    }
  });
  ```

  ```go Go theme={null}
  stack.AddOverride(jsii.String("terraform.backend"), &map[string]map[string]string{
  	"atlas": {
  		"name":    "example_corp/networking-prod",
  		"address": "https://app.terraform.io",
  	},
  })
  ```
</CodeGroup>
