Give every Terragrunt unit a source URL with a ref that names a tag or commit, and change it per environment on purpose. Terragrunt uses the same source syntax as Terraform and OpenTofu, including ref for pinning a tag, commit or branch. A unit with no ref follows the module repository default.
How units fetch code
Terragrunt documentation describes a layout with two repositories. A modules repository holds ordinary OpenTofu or Terraform code. A live repository holds one terragrunt.hcl file per unit, and each unit says where its module comes from and which inputs to pass.
When Terragrunt finds a source parameter in a unit, it downloads the configuration into a download directory, by default a folder named .terragrunt-cache in the working directory. It then copies the working directory's files in and runs your command there. The docs recommend adding that cache folder to .gitignore. It downloads all the code before the double slash, so relative paths between modules still work.
Why per-environment refs help
The documentation's own example sets the staging unit to one version tag and the production unit to an older one. That is the promotion model: new module versions are exercised in staging first, and production moves only when someone edits its ref.
This is also a security control. A malicious or accidental change in the module repository only reaches production when a reviewer approves a ref change in the live repository.
What to pin and how
| Source style | Moves when | Suggested use |
|---|---|---|
| No ref | The default branch changes | Avoid |
| Branch ref | That branch changes | Short lived experiments |
| Tag ref | The tag is moved or recreated | Staging and production, with tag protection |
| Commit ref | Never | Highest assurance |
For private repositories, prefer SSH or token based access that your CI already manages, and keep credentials out of the URL.
Checks to add
- A CI rule that fails when any unit source lacks a ref.
- A second rule that fails when a ref looks like a branch name, such as main.
- A report listing every source and ref across the live repository, so you can see which environments run which module version.
- A pull request template for ref changes that links to the module changelog and its diff.
- The same lock file practices as plain Terraform, since the underlying tool still pins provider versions. See what does the Terraform lock file protect.
Remember that the cache folder is a copy of what was downloaded. If you cache it in CI, key the cache on the resolved ref so a changed pin forces a fresh download.
What a ref cannot do
A ref pins the module code. It does not pin the providers that module needs, nor tell you whether the module itself is sound. Review the module's own provider requirements and run a plan review before apply. Also remember that a tag in a repository you do not control can be moved, so either mirror the repository or use commit hashes for critical modules.
Where a scanner fits
A live repository with dozens of units is an inventory problem. Safeguard's asset discovery and SBOM generation help you list what you run, and policy gates on pull requests can enforce rules like requiring a ref on every unit. Confirm that your scanner reads terragrunt.hcl files before relying on it. Free scan: gold.safeguard.sh/scan.
Quick check
Where does Terragrunt keep downloaded module code?
By default in a .terragrunt-cache folder in the working directory. The documentation recommends adding it to .gitignore.
Does Terragrunt use a different source syntax from Terraform?
No. The documentation says the source parameter supports the same syntax as the OpenTofu and Terraform module source, including local paths, Git URLs and ref parameters.
Sources
Last reviewed: 19 October 2026.