Skip to content

State Backend

OpenTofu/Terraform supports remote state storage via various backends that you normally configure in your .tf files as follows:

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 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:

  • Directorybackend-app
    • main.tf
  • Directoryfrontend-app
    • main.tf
  • Directorymysql
    • main.tf
  • Directoryvpc
    • main.tf

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

Section titled “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:

  • root.hcl
  • Directorybackend-app
    • main.tf
    • terragrunt.hcl
  • Directoryfrontend-app
    • main.tf
    • terragrunt.hcl
  • Directorymysql
    • main.tf
    • terragrunt.hcl
  • Directoryvpc
    • main.tf
    • terragrunt.hcl

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:

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
}

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:

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.

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 for more info.

Create remote state resources automatically

Section titled “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, 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.

The following backends are currently supported by remote_state:

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:

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 bucket: If you are using the S3 backend for remote state storage and the bucket you specify in remote_state.config doesn’t already exist, Terragrunt will create it automatically, with versioning, server-side encryption, and access logging 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 for remote state storage and/or you specify a dynamodb_table (a DynamoDB table used for locking) in remote_state.config, Terragrunt will create them automatically if they don’t already exist. They will be created with server-side encryption 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.

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:

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:

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

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

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 bucket: If you are using the GCS backend for remote state storage and the bucket you specify in remote_state.config doesn’t already exist, Terragrunt will create it automatically, with 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 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.

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

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 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

Disabling automatic remote state bootstrapping

Section titled “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.

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.

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

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:

root.hcl
remote_state {
# ...
disable_init = tobool(get_env("TG_DISABLE_INIT", "false"))
}