If you've run Terragrunt across more than a handful of modules for a while, you've hit this: you want to reorganize your repo — rename a folder, group projects differently, fix a naming mistake from six months ago — and Terragrunt's default behavior punishes you for it.
The problem
By default, Terragrunt derives your remote state key from the file path of the module. Something like:
# root.hcl
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "your-terraform-state-bucket"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "eu-west-1"
}
}
path_relative_to_include() is convenient until the day you move projects/old-name/ to projects/new-name/. The key changes, Terraform can no longer find the state for those resources, and you're one terragrunt apply away from either a pile of "already exists" errors or, worse, Terraform trying to recreate live infrastructure because it thinks it's gone.
The usual fixes are painful: terraform state mv per resource, or a full terraform import pass. Both work, both are slow, both are one typo away from downtime on something that's just supposed to be a folder rename.
The pattern: decouple the state key from the file path
Give every project a project.hcl with a legacy_prefix — the state key it should keep, independent of wherever the folder happens to live today:
# projects/new-name/project.hcl
locals {
legacy_prefix = "old-name" # the prefix this project's state has always used
aws_region = "eu-west-1"
}
Then have root.hcl build the state key from that prefix instead of the live path:
# root.hcl
locals {
project = read_terragrunt_config(find_in_parent_folders("project.hcl"))
}
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "your-terraform-state-bucket"
key = "${local.project.locals.legacy_prefix}/terraform.tfstate"
region = "eu-west-1"
}
}
Now the folder path is free to change. Rename it, move it, restructure the whole repo — the state key is pinned to legacy_prefix, not to path_relative_to_include(). A new project just sets legacy_prefix to whatever it wants (usually matching its own path, since it has no history to preserve); an existing project keeps its old prefix forever, regardless of where the folder lives.
The proof, not just the theory
Don't take "it should work" on faith. Before you consider a reorganization safe, run:
terragrunt plan
on every module you touched, and the only acceptable output is:
Plan: 0 to add, 0 to change, 0 to destroy.
Anything else means the key computation didn't land on the same value as before, and you stop right there. This one-line proof requirement is what makes the whole pattern trustworthy enough to use on live infrastructure: you're not hoping the refactor is safe, you're verifying it before it touches anything.
When this is worth it
This pattern earns its keep the moment you have more than a few Terragrunt modules and expect to reorganize them more than once — a new team convention, a naming scheme that didn't survive contact with reality, projects that outgrew their original folder structure. If you have three modules that will never move, plain path_relative_to_include() is simpler and you don't need this.
The core idea generalizes beyond Terragrunt: whenever a tool derives a stable identifier (a state key, a resource ID, a cache key) from something that isn't actually stable (a file path, a display name), that's worth decoupling explicitly — before the first painful migration forces you to do it under pressure.
Top comments (0)