---
title: State Backend
description: Learn how Terragrunt can create and manage remote state backends.
slug: features/units/state-backend
sidebar:
  order: 3
---

import FileTree from '@components/vendored/starlight/FileTree.astro';
import { Aside } from '@astrojs/starlight/components';

OpenTofu/Terraform supports [remote state storage](https://opentofu.org/docs/language/state/remote/) via various [backends](https://opentofu.org/docs/language/settings/backends/configuration/) that you normally configure in your `.tf` files as follows:

```hcl
# main.tf
terraform {
  backend "s3" {
    bucket         = "my-tofu-state"
    key            = "frontend-app/tofu.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "my-lock-table"
  }
}
```

Unfortunately, the `backend` configuration does not currently support expressions, variables, or functions. This makes it hard to keep your code [DRY](/getting-started/terminology/#dont-repeat-yourself-dry) if you have multiple OpenTofu/Terraform modules. For example, consider the following filesystem layout, which uses different OpenTofu/Terraform modules to deploy a backend app, frontend app, MySQL database, and a VPC:

<FileTree>

- backend-app
  - main.tf
- frontend-app
  - main.tf
- mysql
  - main.tf
- vpc
  - main.tf

</FileTree>

To use remote state with each of these modules, you would have to copy/paste the identical `backend` configuration into each of the `main.tf` files. The only thing that would differ between the configurations would be the `key` parameter: e.g., the `key` for `mysql/main.tf` might be `mysql/terraform.tfstate` and the `key` for `frontend-app/main.tf` might be `frontend-app/terraform.tfstate`.

In addition, the resources used for remote state will be provisioned _somewhere else_, and that _somewhere else_ needs to be managed. Most users end up using "click-ops" to provision the S3 bucket and DynamoDB table used for AWS remote state (clicking around in the AWS console until they have what they need). This is error-prone, difficult to reproduce, and makes it hard to do the _right thing_ consistently (e.g., enabling versioning, encryption, and access logging).

Luckily, Terragrunt has built-in tooling to make it easy to manage remote state.

## Generating remote state settings with Terragrunt

To fill in the settings via Terragrunt, create a `root.hcl` file in the root folder, plus one `terragrunt.hcl` file in each of the OpenTofu/Terraform modules:

<FileTree>

- root.hcl
- backend-app
  - main.tf
  - terragrunt.hcl
- frontend-app
  - main.tf
  - terragrunt.hcl
- mysql
  - main.tf
  - terragrunt.hcl
- vpc
  - main.tf
  - terragrunt.hcl

</FileTree>

In your `root.hcl` file, you can define your entire remote state configuration just once in a `generate` block, to generate a `backend.tf` file that includes the backend configuration:

```hcl
# root.hcl
generate "backend" {
  path      = "backend.tf"
  if_exists = "overwrite_terragrunt"
  contents = <<EOF
terraform {
  backend "s3" {
    bucket         = "my-tofu-state"
    key            = "${path_relative_to_include()}/tofu.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "my-lock-table"
  }
}
EOF
}
```

<Aside type="tip" title="Windows compatibility">

To ensure compatibility with Windows systems, you may want to use `replace` to ensure that `\` characters are replaced with `/` characters in the `key` parameter.

```hcl
# root.hcl

generate "backend" {
  path      = "backend.tf"
  if_exists = "overwrite_terragrunt"
  contents = <<EOF
terraform {
  backend "s3" {
    bucket         = "my-tofu-state"
    key            = "${replace(path_relative_to_include(), "\\", "/")}/tofu.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "my-lock-table"
  }
}
EOF
}
```

</Aside>

This instructs Terragrunt to create the file `backend.tf` in the working directory (where Terragrunt calls `tofu`/`terraform`)
before it runs any OpenTofu/Terraform commands, including `init`. This allows you to inject this backend configuration
in all the units that include the root file and have `terragrunt` properly initialize the backend configuration with
interpolated values.

To inherit this configuration in each unit, such as `mysql/terragrunt.hcl`, you can
configure Terragrunt units to automatically include all the settings from the root `root.hcl` file as follows:

```hcl
# mysql/terragrunt.hcl
include "root" {
  path = find_in_parent_folders("root.hcl")
}
```

The `include` block here configures the `mysql` Terragrunt unit to merge in partial configurations from the `root.hcl` file specified via the `path` parameter. It behaves exactly as if you had copy/pasted the `generate` configuration into `mysql/terragrunt.hcl`, but this approach is much easier to maintain\!

The next time you run `terragrunt`, it will automatically configure all the settings for the backend, if they aren't configured already, by calling [tofu/terraform init](https://opentofu.org/docs/cli/commands/init/).

The `terragrunt.hcl` files above use two Terragrunt built-in functions:

- `find_in_parent_folders()`: This function returns the absolute path to the first file it finds in the parent folders above the current unit with a given file name. In the example above, the call to `find_in_parent_folders("root.hcl")` in `mysql/terragrunt.hcl` will return `/absolute/path/to/root.hcl`. This way, you don't have to hard code the `path` parameter in every unit.

- `path_relative_to_include()`: This function returns the relative path between the unit and the path specified in its `include` block. We typically use this in a root `root.hcl` file so that each unit stores its OpenTofu/Terraform state at a different `key`. For example, the `mysql` unit will have its `key` parameter resolve to `mysql/tofu.tfstate` and the `frontend-app` module will have its `key` parameter resolve to `frontend-app/tofu.tfstate`.

Read [Functions docs](/reference/hcl/functions) for more info.

## Create remote state resources automatically

The `generate` block is useful for allowing you to set up the remote state backend configuration automatically, but
this introduces a bootstrapping problem: how do you create and manage the underlying storage resources for the remote
state? For example, when using the [s3 backend](https://opentofu.org/docs/language/settings/backends/s3/), OpenTofu/Terraform
expects that the S3 bucket already exists for it to upload/download the state objects.

Ideally, you can manage the S3 bucket using OpenTofu/Terraform, but what about the state object for the module managing the S3
bucket? How do you create the S3 bucket, before you run `tofu`/`terraform`, if you need to run `tofu`/`terraform` to create the
bucket?

To handle this, Terragrunt supports a different block for managing the backend configuration: the [remote_state
block](/reference/hcl/blocks/#remote_state).

<Aside type="note" title="Remote state vs generate">

`remote_state` is an alternative way of managing the OpenTofu/Terraform backend compared to `generate`. You cannot use both
methods at the same time to manage the remote state configuration. When using `remote_state`, be sure not also to use
an equivalent `generate` block for managing the backend.

</Aside>

The following backends are currently supported by `remote_state`:

- [s3 backend](https://opentofu.org/docs/language/settings/backends/s3)
- [gcs backend](https://opentofu.org/docs/language/settings/backends/gcs)
- [azurerm backend](https://opentofu.org/docs/language/settings/backends/azurerm) (experimental; see the [`azure-backend`](/reference/experiments/active#azure-backend) experiment, which currently does not change behavior)

For all other backends, the `remote_state` block operates in the same manner as `generate`, currently. If you are not intending for Terragrunt to automatically perform
automated bootstrapping of remote state resources, you are advised to use `generate` blocks to configure the OpenTofu/Terraform backend instead.

Here is an example of using the `remote_state` block to configure the S3 backend using both an S3 bucket and a DynamoDB table:

```hcl
# root.hcl
remote_state {
  backend = "s3"
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite"
  }
  config = {
    bucket         = "my-terraform-state"
    key            = "${path_relative_to_include()}/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "my-lock-table"
  }
}
```

Like the approach with `generate` blocks, this will generate a `backend.tf` file that contains the remote state
configuration. However, in addition to that, Terragrunt will also now manage the S3 bucket and DynamoDB table for you.
This means that if the S3 bucket `my-terraform-state` and DynamoDB table `my-lock-table` does not exist in your account,
Terragrunt will automatically create these resources before calling `terraform` and configure them based on the
specified configuration parameters.

When you run `terragrunt` with a `remote_state` configuration, it will automatically create the following resources if they don't already exist:

### S3 Backend

- **S3 bucket**: If you are using the [S3 backend](https://opentofu.org/docs/language/settings/backends/s3) for remote state storage and the `bucket` you specify in `remote_state.config` doesn't already exist, Terragrunt will create it automatically, with [versioning](https://docs.aws.amazon.com/AmazonS3/latest/dev/Versioning.html), [server-side encryption](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html), and [access logging](https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerLogs.html) enabled.

  In addition, you can let Terragrunt tag the bucket with custom tags that you specify in `remote_state.config.s3_bucket_tags`.

- **DynamoDB table**: If you are using the [S3 backend](https://opentofu.org/docs/language/settings/backends/s3) for remote state storage and/or you specify a `dynamodb_table` (a [DynamoDB table used for locking](https://opentofu.org/docs/language/settings/backends/s3/#dynamodb-state-locking)) in `remote_state.config`, Terragrunt will create them automatically if they don't already exist. They will be created with [server-side encryption](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/EncryptionAtRest.html) enabled, and the DynamoDB table will use the primary key `LockID`.

  You may configure a custom endpoint for the AWS DynamoDB API using `remote_state.config.dynamodb_endpoint`.

  In addition, you can let Terragrunt tag the DynamoDB table with custom tags that you specify in `remote_state.config.dynamodb_table_tags`.

- **Access logging bucket**: If you specify an `accesslogging_bucket_name` in `remote_state.config` and that bucket doesn't already exist, Terragrunt will create it automatically. You can tag the access logging bucket with custom tags using `remote_state.config.accesslogging_bucket_tags`, and control the log object prefix using `accesslogging_target_prefix`.

<Aside type="tip" title="Using profiles">

If you specify a `profile` key in `remote_state.config`, Terragrunt will automatically use this AWS profile when creating the S3 bucket, DynamoDB table, or access logging bucket.

</Aside>

If you are using an OpenTofu/Terraform version that supports S3-based lockfiles, you can also use the following to only provision the S3 backend:

```hcl
# root.hcl

remote_state {
  backend = "s3"
  config = {
    bucket       = "my-tofu-state"
    key          = "${path_relative_to_include()}/tofu.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true
  }
}
```

Additionally, for **the S3 backend only**, Terragrunt will automatically update the S3 resource to match the
configuration specified in the `remote_state` bucket. For example, if you require versioning in the `remote_state`
block, but the underlying state bucket doesn't have versioning enabled, Terragrunt will automatically turn on versioning
on the bucket to match the configuration.

If you do not want Terragrunt to automatically apply changes, you can configure the following:

```hcl
# root.hcl
remote_state {
  # ... other args omitted for brevity ...
  config = {
    # ... other config omitted for brevity ...
    disable_bucket_update = true
  }
}
```

#### Advanced configuration

<Aside type="caution" title="DEPRECATED">
The `skip_bucket_accesslogging` option, which previously disabled access logging on the state bucket, is now deprecated. Use `accesslogging_bucket_name` instead to explicitly configure the target logging bucket.
</Aside>

For the `s3` backend, the following additional config options can be used for S3-compatible object stores, as necessary:

```hcl
# root.hcl
remote_state {
  # ...

  config = {
    skip_bucket_versioning         = true
    skip_bucket_ssencryption       = true
    skip_bucket_root_access        = true
    skip_bucket_enforced_tls       = true
    skip_credentials_validation    = true
    enable_lock_table_ssencryption = true
    accesslogging_bucket_name      = "my-logs-bucket"
    accesslogging_target_prefix    = "TFStateLogs/"
    shared_credentials_file        = "/path/to/credentials/file"
    skip_metadata_api_check        = true
    force_path_style               = true
  }
}
```

- `skip_bucket_versioning` — Skip versioning on the state bucket. Use only if the object store does not support versioning.
- `skip_bucket_ssencryption` — Skip server-side encryption on the state bucket. Use only if non-encrypted state is required or the object store does not support server-side encryption.
- `skip_bucket_root_access` — Skip granting the AWS account root user access to the state bucket.
- `skip_bucket_enforced_tls` — Skip enforcing TLS on the state bucket. Use only if you need to access the S3 bucket without TLS being enforced.
- `skip_credentials_validation` — Skip validation of AWS credentials. Useful when using S3-compatible object stores other than AWS.
- `enable_lock_table_ssencryption` — Enable server-side encryption on the DynamoDB lock table.
- `accesslogging_bucket_name` — Name of the target bucket for server access logging on the state bucket.
- `accesslogging_target_prefix` — Prefix for access log objects. Defaults to `TFStateLogs/`. Set to an empty string to disable the prefix.
- `shared_credentials_file` — Path to a shared credentials file for AWS authentication.
- `skip_metadata_api_check` — Skip the AWS metadata API check.
- `force_path_style` — Force S3 path-style URLs instead of virtual-hosted-style.

If you experience an error for any of these configurations, confirm you are using OpenTofu or Terraform v0.12.2 or greater.

The following config options are only valid for the `s3` backend and are used by Terragrunt — they are **not** passed on to OpenTofu/Terraform:

- `s3_bucket_tags`
- `dynamodb_table_tags`
- `accesslogging_bucket_tags`
- `skip_bucket_versioning`
- `skip_bucket_ssencryption`
- `skip_bucket_root_access`
- `skip_bucket_enforced_tls`
- `skip_bucket_public_access_blocking`
- `accesslogging_bucket_name`
- `accesslogging_target_prefix`
- `enable_lock_table_ssencryption`

### GCS Backend

- **GCS bucket**: If you are using the [GCS backend](https://opentofu.org/docs/language/settings/backends/gcs) for remote state storage and the `bucket` you specify in `remote_state.config` doesn't already exist, Terragrunt will create it automatically, with [versioning](https://cloud.google.com/storage/docs/object-versioning) enabled. For this to work correctly you must also specify `project` and `location` keys in `remote_state.config`, so Terragrunt knows where to create the bucket. You will also need to supply valid credentials using either `remote_state.config.credentials` or by setting the `GOOGLE_APPLICATION_CREDENTIALS` environment variable. If you want to skip creating the bucket entirely, simply set `skip_bucket_creation` to `true` and Terragrunt will assume the bucket has already been created. If you don't specify `bucket` in `remote_state` then Terragrunt will assume that you will pass `bucket` through `-backend-config` in `extra_arguments`.

  You are strongly advised to enable [Cloud Audit Logs](https://cloud.google.com/storage/docs/access-logs) to audit and track API operations performed against the state bucket.

  In addition, you can let Terragrunt label the bucket with custom labels that you specify in `remote_state.config.gcs_bucket_labels`.

#### Advanced configuration

For the `gcs` backend, the following additional config options can be used for GCS-compatible object stores, as necessary:

```hcl
# root.hcl
remote_state {
  # ...

  config = {
    skip_bucket_versioning    = true
    enable_bucket_policy_only = false
    encryption_key            = "GOOGLE_ENCRYPTION_KEY"
  }
}
```

- `skip_bucket_versioning` — Skip versioning on the state bucket. Use only if the object store does not support versioning.
- `enable_bucket_policy_only` — Enable uniform bucket-level access. See [uniform bucket-level access](https://cloud.google.com/storage/docs/uniform-bucket-level-access) for details.
- `encryption_key` — A Cloud KMS key name to use for encrypting state objects.

If you experience an error for any of these configurations, confirm you are using Terraform v0.12.0 or greater.

The following config options are only valid for the `gcs` backend and are used by Terragrunt — they are **not** passed on to OpenTofu/Terraform:

- `gcs_bucket_labels`
- `skip_bucket_versioning`
- `enable_bucket_policy_only`

### Azure Storage (azurerm) Backend

<Aside type="caution" title="Experimental">
Azure backend support is delivered through the [`azure-backend`](/reference/experiments/active#azure-backend) experiment.
Until that experiment lands functional behavior, Terragrunt does not bootstrap, delete, or migrate Azure resources;
OpenTofu/Terraform's native `azurerm` backend is used as-is.
</Aside>

Example configuration:

```hcl
# root.hcl
remote_state {
  backend = "azurerm"
  config = {
    storage_account_name = "myterragruntstate"
    container_name       = "tfstate"
    key                  = "${path_relative_to_include()}/terraform.tfstate"
    resource_group_name  = "terraform-rg"
    subscription_id      = "00000000-0000-0000-0000-000000000000"
    use_azuread_auth     = true
  }
}
```

Further configuration keys, authentication methods, and Terragrunt-side bootstrap behavior will be documented as the experiment matures.

### Disabling automatic remote state bootstrapping

You can disable automatic remote state bootstrapping by setting `remote_state.disable_init` (it's called this for legacy reasons). This will skip the automatic creation of remote state resources (S3 buckets, DynamoDB tables, GCS buckets) by Terragrunt, while still allowing OpenTofu/Terraform to initialize already provisioned backends normally. This can be handy when running commands such as `run --all validate` as part of a CI process where you do not want Terragrunt to create or modify remote state resources.

<Aside type="caution">
When `disable_init` is `true`, backend resources must already exist, and valid credentials to access the backend are still required by OpenTofu/Terraform.
</Aside>

In previous versions, `disable_init = true` passed `-backend=false` to `terraform init`, preventing OpenTofu/Terraform from initializing the backend entirely. The new behavior passes `-backend-config=KEY=VALUE` arguments instead — OpenTofu/Terraform **will** attempt to connect to the backend. Ensure backend resources exist and credentials are valid before using `disable_init = true`.

Note that `--backend-bootstrap` defaults to `false`, so Terragrunt does not create backend resources by default regardless of `disable_init`. The `disable_init` flag additionally prevents bootstrap even when `--backend-bootstrap` is explicitly set.

### Skipping backend initialization entirely

If you need to skip backend initialization entirely (the previous behavior of `disable_init`), pass `-backend=false` directly to OpenTofu/Terraform via `extra_arguments`:

```hcl
terraform {
  extra_arguments "no_backend_init" {
    commands  = ["init"]
    arguments = ["-backend=false"]
  }
}
```

Or directly on the command line: `terragrunt run -- init -backend=false`

The following example demonstrates using an environment variable to configure `disable_init`:

```hcl
# root.hcl
remote_state {
  # ...

  disable_init = tobool(get_env("TG_DISABLE_INIT", "false"))
}
```

## Further reading

- Managing your remote state like this is really valuable when you organize your units into a [stack](/features/stacks). Reading about those concepts will help you understand how to organize your infrastructure such that different units stored in isolated state can interact with each other.
- See the [`remote_state` block reference](/reference/hcl/blocks/#remote_state) for the full list of supported attributes.
- See the [`generate` block reference](/reference/hcl/blocks/#generate) for details on how code generation works.
- Check out the [terragrunt-infrastructure-catalog-example](https://github.com/gruntwork-io/terragrunt-infrastructure-catalog-example) and [terragrunt-infrastructure-live-stacks-example](https://github.com/gruntwork-io/terragrunt-infrastructure-live-stacks-example) repos for fully-working sample code that demonstrates how to use Terragrunt to manage remote state.
