S3 Backend
Example configuration:
remote_state { backend = "s3" config = { bucket = "my-tofu-state" key = "${path_relative_to_include()}/tofu.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "my-lock-table" }}When you bootstrap the backend, Terragrunt creates the following resources if they don’t already exist:
-
S3 bucket: If you are using the S3 backend for remote state storage and the
bucketyou specify inremote_state.configdoesn’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) inremote_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 keyLockID.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_nameinremote_state.configand that bucket doesn’t already exist, Terragrunt will create it automatically. You can tag the access logging bucket with custom tags usingremote_state.config.accesslogging_bucket_tags, and control the log object prefix usingaccesslogging_target_prefix.A bucket Terragrunt creates here gets a bucket policy letting S3 deliver access logs into it on behalf of buckets in the same AWS account. AWS recommends a bucket policy over the log delivery group ACL for this, and documents the policy to use. Terragrunt also creates buckets with ACLs disabled, which AWS recommends and applies by default to new buckets.
A bucket that already exists keeps the permissions it has: Terragrunt reads and writes nothing on its policy, so if access logs don’t arrive, grant
s3:PutObjecttologging.s3.amazonaws.comon that bucket yourself.
Terragrunt creates a bucket in your account regional namespace when you name it using a name that’s reserved for the account regional namespace. That namespace is a reserved subdivision of the S3 namespace that only your account can create buckets in, and its names end with your account ID, the region, and -an:
remote_state { backend = "s3" config = { bucket = "my-tofu-state-111122223333-us-east-1-an" key = "${path_relative_to_include()}/tofu.tfstate" region = "us-east-1" }}accesslogging_bucket_name follows the same rule. A name that fits the convention but names a region other than the one you are deploying to is refused before Terragrunt offers to create the bucket.
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:
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:
remote_state { # ... other args omitted for brevity ... config = { # ... other config omitted for brevity ... disable_bucket_update = true }}Root user access to the state bucket
Section titled “Root user access to the state bucket”Terragrunt used to attach a bucket policy statement with the Sid RootAccess to every state bucket it bootstrapped, granting s3:* on the bucket and its objects to arn:aws:iam::<account-id>:root. A resource policy naming that ARN grants access to the AWS account as a whole, not only to its root user. That statement has been removed. The account already owns the bucket, so the statement gave the account no reach it did not have, and state files often contain secrets that belong behind least-privilege access.
If your setup depends on that statement, set enable_bucket_root_access to add it back:
remote_state { # ... other args omitted for brevity ... config = { # ... other config omitted for brevity ... enable_bucket_root_access = true }}Terragrunt does not strip the statement from buckets that already have it. To drop it, fetch the current policy, delete the statement whose Sid is RootAccess, and put the policy back:
aws s3api get-bucket-policy --bucket my-tofu-state --query Policy --output text \ | jq '.Statement |= map(select(.Sid != "RootAccess"))' > policy.json
aws s3api put-bucket-policy --bucket my-tofu-state --policy file://policy.jsonBefore you apply this, confirm that the principals you expect can still reach the bucket through their own IAM policies.
Advanced configuration
Section titled “Advanced configuration”For the s3 backend, the following additional config options can be used for S3-compatible object stores, as necessary:
remote_state { # ...
config = { skip_bucket_versioning = true skip_bucket_ssencryption = 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_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 toTFStateLogs/. 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. -
use_path_style— The newer name forforce_path_style. Set only one of them, because OpenTofu rejects both together.
If you experience an error for any of these configurations, confirm you are using OpenTofu/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_tagsdynamodb_table_tagsaccesslogging_bucket_tagsskip_bucket_versioningskip_bucket_ssencryptionskip_bucket_root_accessenable_bucket_root_accessskip_bucket_enforced_tlsskip_bucket_public_access_blockingaccesslogging_bucket_nameaccesslogging_target_prefixenable_lock_table_ssencryption
S3-compatible object stores
Section titled “S3-compatible object stores”Terragrunt can bootstrap and use state buckets in object stores that implement the S3 API, such as RustFS, which Terragrunt’s integration tests run against. Point endpoints.s3 at the store and turn off the steps that need AWS services the store doesn’t have:
remote_state { backend = "s3" config = { bucket = "my-tofu-state" key = "${path_relative_to_include()}/tofu.tfstate" region = "us-east-1" use_lockfile = true
endpoints = { s3 = "http://localhost:9000" }
use_path_style = true skip_credentials_validation = true skip_requesting_account_id = true skip_metadata_api_check = true encrypt = false
skip_bucket_versioning = true skip_bucket_ssencryption = true skip_bucket_enforced_tls = true skip_bucket_public_access_blocking = true }}-
endpoints.s3sends both Terragrunt’s and OpenTofu/Terraform’s S3 requests to the store. The olderendpointsetting works too, but OpenTofu warns that it is deprecated. Set only one of them, because OpenTofu rejects both together. -
use_path_styleputs the bucket name in the URL path (http://localhost:9000/my-tofu-state/...) in place of the hostname, which most self-hosted stores need. The olderforce_path_styledoes the same, but OpenTofu warns that it is deprecated. Set only one of them, because OpenTofu rejects both together. -
skip_credentials_validationstops Terragrunt and OpenTofu/Terraform from checking the credentials against AWS STS.skip_requesting_account_idstops OpenTofu/Terraform from looking up an AWS account ID, andskip_metadata_api_checkstops it from querying the EC2 instance metadata service. -
use_lockfilelocks state with a.tflockobject that OpenTofu writes with a conditionalPutObject(If-None-Match), so the store must support conditional writes. It needs OpenTofu 1.10 or later, and replaces the DynamoDB table these stores don’t have. -
encrypt = falseandskip_bucket_ssencryptionturn off server-side encryption. Leave them out if the store supports it. -
skip_bucket_versioningleaves versioning off. If the store supports versioning, keep it on:backend deleteandbackend migraterefuse to run against an unversioned bucket unless you pass--force. -
skip_bucket_enforced_tlsandskip_bucket_public_access_blockingstop Terragrunt from adding the TLS-only bucket policy and the public access block when it creates the bucket.
Terragrunt sends any region other than us-east-1 as the new bucket’s location constraint, so set a region the store accepts.
Pass the store’s access key in AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. Terragrunt and OpenTofu/Terraform read them the same way they read AWS credentials. Leave AWS_PROFILE unset, or set it to a profile that exists on the machine, or the AWS SDK fails to load its configuration.