{"id":20657757,"url":"https://github.com/boltops-tools/terragrunt-vs-terraform-reusable-module","last_synced_at":"2025-09-11T13:41:20.124Z","repository":{"id":91684893,"uuid":"341301628","full_name":"boltops-tools/terragrunt-vs-terraform-reusable-module","owner":"boltops-tools","description":"example terragrunt and terraspace projects to explain difference between reusable modules ","archived":false,"fork":false,"pushed_at":"2021-07-27T01:22:54.000Z","size":23,"stargazers_count":10,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-17T11:32:34.549Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HCL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/boltops-tools.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-22T18:44:02.000Z","updated_at":"2023-06-30T08:16:01.000Z","dependencies_parsed_at":null,"dependency_job_id":"cc521299-42d8-41bd-9176-b37b71a3d588","html_url":"https://github.com/boltops-tools/terragrunt-vs-terraform-reusable-module","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boltops-tools%2Fterragrunt-vs-terraform-reusable-module","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boltops-tools%2Fterragrunt-vs-terraform-reusable-module/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boltops-tools%2Fterragrunt-vs-terraform-reusable-module/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boltops-tools%2Fterragrunt-vs-terraform-reusable-module/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/boltops-tools","download_url":"https://codeload.github.com/boltops-tools/terragrunt-vs-terraform-reusable-module/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242768074,"owners_count":20182098,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-16T18:23:00.709Z","updated_at":"2025-03-09T23:46:41.240Z","avatar_url":"https://github.com/boltops-tools.png","language":"HCL","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Overview\n\nThese example projects explain how terragrunt and terraform/terraspace handle creating reusable modules differently. It provides the necessary context to answer this community post:\n\n* [Migrating from terragrunt - Root modules?](https://community.boltops.com/t/migrating-from-terragrunt-root-modules/627)\n\nThis may also help folks migrating from terragrunt to terraspace and using the same original state files.\n\nThough a greenfield terraspace project is a lot cleaner, it's not always possible. You might also want to consider migrating by copy each module's state file one at a time. This allows you to get rid of legacy artifacts. Again, this approach is sometimes not possible.\n\n## Files Summary\n\nHere's a summary of the files in this repo. Please read the **entire** README and come back to this to be most useful.\n\nName | Description | Terraform Resource Name\n---|---|---\n[terragrunt-project/dev/demo](terragrunt-project/dev/demo) | The \"dev demo\" terragrunt project that creates a random pet. Terragrunt creates \"flattened\" resource names. The key file in this folder is [terragrunt.hcl](terragrunt-project/dev/demo/terragrunt.hcl). | random_pet.pet1\n[terraspace-project/app/stacks/demo](terraspace-project/app/stacks/demo) | The \"demo\" terraspace stack that creates a random pet using the `module` keyword. Since this project uses the terraform `module` keyword it'll create a hierarchical resource name. The key file in this folder is [main.tf](terraspace-project/app/stacks/demo/main.tf). | module.pet.random_pet.pet1\n[terraspace-project-flat/app/stacks/demo](terraspace-project-flat/app/stacks/demo) | The \"demo\" terraspace stack creates a random pet directly. This creates the same \"flattened\" terragrunt resource name structure. And is the key to allowing you to use the **same statefiles**.  The key file in this folder is [main.tf](terraspace-project-flat/app/stacks/demo/main.tf).  | random_pet.pet1\n\nNotes:\n\n* Both terraspace project examples make use of Terrafile to source in the modules.\n* You can use the `module source` field if you're using `terraform module` keyword to reuse modules.\n* But for the flattened structure that creates a \"flattened\" resource name like Terragrunt, you have to use Terrafile to reuse module code.\n\n## Reusing Modules with Terragrunt vs Terraform Looks the Same\n\nTo reuse terraform modules, Terragrunt uses a custom HCL syntax with the `terraform` keyword:\n\ndev/demo/terragrunt.hcl\n\n```terraform\nterraform {\n  source = \"git::https://github.com/tongueroo/pet.git\"\n}\n```\n\nTerraform/terraspace uses the `module` keyword that looks very similar:\n\n```terraform\nmodule \"example\" {\n  source = \"github.com/hashicorp/example\"\n}\n```\n\n## But Reusing Modules with Terragrunt vs Terraform Creates Very Different Resource Name Structures\n\nThough the terragrunt custom `terraform` keyword and terraform native `module` keyword code look very similar, they behave quite differently.\n\n* The terragrunt `terraform` keyword sources the module in a flattened manner.\n* Whereas the terraform `module` keyword adds another hierarchical namespace to the created resource.\n\n## Resource Name Structure: Terragrunt Flattened\n\nThe terragrunt `terraform` keyword creates a flattened resource name structure. For example, given these files:\n\ndev/demo/terragrunt.hcl\n\n```terraform\nterraform {\n  source = \"git::https://github.com/tongueroo/pet.git\"\n}\n```\n\ndev/demo/main.tf\n\n```terraform\nresource \"random_pet\" \"pet1\" {\n  length = 2\n}\n```\n\nRunning:\n\n    $ terragrunt apply\n    ...\n    # random_pet.pet1 will be created\n    ...\n\nThe key is to notice that the resource name is:\n\n    random_pet.pet1\n\n\u003cdetails\u003e\n \u003csummary\u003eClick to see full output\u003c/summary\u003e\n\n    $ terragrunt apply\n\n    An execution plan has been generated and is shown below.\n    Resource actions are indicated with the following symbols:\n      + create\n\n    Terraform will perform the following actions:\n\n      # random_pet.pet1 will be created\n      + resource \"random_pet\" \"pet1\" {\n          + id        = (known after apply)\n          + length    = 2\n          + separator = \"-\"\n        }\n\n    Plan: 1 to add, 0 to change, 0 to destroy.\n\n    Do you want to perform these actions?\n      Terraform will perform the actions described above.\n      Only 'yes' will be accepted to approve.\n\n      Enter a value: yes\n\n    random_pet.pet1: Creating...\n    random_pet.pet1: Creation complete after 0s [id=exciting-kit]\n\n    Apply complete! Resources: 1 added, 0 changed, 0 destroyed.\n    $\n\u003c/details\u003e\n\nSo terragrunt downloads the reusable module code from `https://github.com/tongueroo/pet.git` as if you had written the source code in the same `dev/demo/main.tf` file. It's \"flattened\".\n\n## Resource Name Structure: Terraform Hierarchical\n\nThe terraform/terraspace module keyword creates a hierarchical resource name structure. For example, given these files:\n\napp/modules/demo/main.tf\n\n```terraform\nmodule \"pet1\" {\n  source     = \"../../modules/pet\"\n}\n```\n\nvendor/modules/pet/main.tf\n\n```terraform\nresource \"random_pet\" \"pet\" {\n  length = 2\n}\n```\n\nTerrafile\n\n```ruby\nmod \"pet\", source: \"git@github.com:tongueroo/pet\"\n```\n\nRunning\n\n    $ terraspace bundle   # to build vendor/modules/pet\n    $ terraspace up demo\n    ...\n    # module.pet1.random_pet.pet will be created\n    ...\n\nThe key is to notice that the resource name is:\n\n    module.pet1.random_pet.pet will be created\n\n\u003cdetails\u003e\n \u003csummary\u003eClick to see full output\u003c/summary\u003e\n\n    $ terraspace up demo\n    Building .terraspace-cache/us-west-2/dev/stacks/demo\n    Built in .terraspace-cache/us-west-2/dev/stacks/demo\n    Current directory: .terraspace-cache/us-west-2/dev/stacks/demo\n    =\u003e terraform apply -input=false\n\n    An execution plan has been generated and is shown below.\n    Resource actions are indicated with the following symbols:\n      + create\n\n    Terraform will perform the following actions:\n\n      # module.pet1.random_pet.pet will be created\n      + resource \"random_pet\" \"pet\" {\n          + id        = (known after apply)\n          + length    = 2\n          + separator = \"-\"\n        }\n\n    Plan: 1 to add, 0 to change, 0 to destroy.\n\n    Do you want to perform these actions?\n      Terraform will perform the actions described above.\n      Only 'yes' will be accepted to approve.\n      Enter a value: yes\n\n    module.pet1.random_pet.pet: Creating...\n    module.pet1.random_pet.pet: Creation complete after 0s [id=useful-stingray]\n\n    Apply complete! Resources: 1 added, 0 changed, 0 destroyed.\n    Time took: 1m 4s\n    $\n\u003c/details\u003e\n\nProduces this resource name: `module.pet1.random_pet.pet`\n\n## Resource Name Structure: Terraform \"Flattened\"\n\nIt is key to understand that `terraform module` keyword will create a hierarchical resource name no matter what. So if you want to produce the same \"flattened\" resource name structure like terragrunt, do not use the module keyword. Examples:\n\napp/stacks/demo/main.tf\n\n```terraform\nresource \"random_pet\" \"pet\" {\n  length = 2\n}\n```\n\nTerrafile\n\n```ruby\nmod \"demo\", source: \"git@github.com:tongueroo/pet\", export_to: \"app/stacks\"\n```\n\nseed/tfvars/stacks/demo/dev.tfvars\n\n    length = 1\n\nRunning\n\n    $ terraspace up demo\n    ...\n    # random_pet.pet1 will be created\n    ...\n\nThe key is to notice that the resource name is:\n\n    random_pet.pet1\n\nThis is the **same** flattened structure that the terragrunt `terraform` customized keyword produces: `random_pet.pet1`\n\n\u003cdetails\u003e\n \u003csummary\u003eClick to see full output\u003c/summary\u003e\n\n    $ terraspace up demo\n    Building .terraspace-cache/us-west-2/dev/stacks/demo\n    Built in .terraspace-cache/us-west-2/dev/stacks/demo\n    Current directory: .terraspace-cache/us-west-2/dev/stacks/demo\n    =\u003e terraform apply -input=false\n\n    An execution plan has been generated and is shown below.\n    Resource actions are indicated with the following symbols:\n      + create\n\n    Terraform will perform the following actions:\n\n      # random_pet.pet1 will be created\n      + resource \"random_pet\" \"pet1\" {\n          + id        = (known after apply)\n          + length    = 1\n          + separator = \"-\"\n        }\n\n    Plan: 1 to add, 0 to change, 0 to destroy.\n\n    Do you want to perform these actions?\n      Terraform will perform the actions described above.\n      Only 'yes' will be accepted to approve.\n      Enter a value: yes\n\n    random_pet.pet1: Creating...\n    random_pet.pet1: Creation complete after 0s [id=hermit]\n\n    Apply complete! Resources: 1 added, 0 changed, 0 destroyed.\n    Time took: 4s\n    $\n\u003c/details\u003e\n\nThis demonstrates that the mirrored tfvars structure is also possible with Terraspace. We're putting the tfvars files in the seed folder here because a mirrored tfvars structure is likely more familiar for Terragrunt users. However, think it's clearer to put the tfvars files in `app/modules/demo/tfvars`. You have options.\n\n## Concluding Thoughts: Pros and Cons\n\nThe devil is in the details, we have to go pretty deep into the weeds to see the differences here.\n\n* The advantage with Terragrunt's custom HCL syntax is that it produces a nice flattened resource name structure. This is nicer because it's \"simpler\". You don't have to \"daisy-chain\" variable inputs as much.\n* The advantage of terraform's `module` keyword is that it's native. It's already built-in. The native module syntax also creates a hierarchical resource name. This nicer because it's more \"organized\".\n\nThink that sometimes a custom syntax is worth it; it depends on the value add. DSLs are worth it when they provide enough pros. Now that the terraform `module` keyword is available though, think it's no longer worth it. The terragrunt custom HCL syntax conflates things.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboltops-tools%2Fterragrunt-vs-terraform-reusable-module","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fboltops-tools%2Fterragrunt-vs-terraform-reusable-module","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboltops-tools%2Fterragrunt-vs-terraform-reusable-module/lists"}