Terragrunt 1.1 promoted the Content Addressable Store (CAS) to general availability, and with it shipped a new attribute: update_source_with_cas. What does this mean for you?
The TL;DR: you can now author components in your infrastructure catalog with plain relative paths, and Terragrunt will rewrite them into content-addressed cas:: references when it generates your stacks.
If you maintain a catalog of infrastructure patterns (i.e. modules, units, and stacks) that live repositories consume, this attribute removes a whole category of version plumbing from your configuration. If you use Gruntwork Pipelines, it also makes your pipelines way faster. We'll get to why soon.
The problem: versioning a layered catalog
A common (and recommended) way to organize Terragrunt at scale is to split your infrastructure into two kinds of repositories:
- One or more catalog repositories that hold your reusable components: OpenTofu/Terraform modules, the Terragrunt units that wrap them, and the stacks that compose those units into deployable patterns.
- Live repositories then reference the catalog(s) from
terragrunt.stack.hclandterragrunt.hclfiles, where you runterragruntcommands to manage your live infrastructure.
Components in the catalog can be self-referential. If you want to define modules, units and stacks in the same catalog repository, a stack in the catalog points at units in the catalog, and those units point at modules in the catalog. The problem with this is that the terragrunt.stack.hcl file you author in the catalog gets generated into the live repository, so every source in it has to resolve in both places. A relative path like ../..//units/vpc is valid inside the catalog but meaningless once the stack has been generated somewhere else.
That constraint left catalog authors with two options, neither great:
- Pin every nested
sourceto a remote URL with an explicit?ref=. Now a single version bump means editing every layer of the catalog. - Plumb a version down through
values, so one version set at the top determines the ref used by every stack, unit, and module below it.
The second option became the standard workaround, and it looks something like this:
It works. One version defined at the top-level stack controls versions for the whole tree. But the interpolation is easy to get subtly wrong, and every component in the catalog now has to know about a version value whose only job is to reconstruct information Terragrunt already had when it fetched the catalog in the first place.
There's a hidden cost too. Each of those values blocks materializes as a generated terragrunt.values.hcl file on disk, and every one of those files changes when the version changes. Hold that thought; it will come up again in the Pipelines section.
What update_source_with_cas does
The CAS stores everything Terragrunt fetches, addressed by a hash of its content. Once your catalog has been fetched, there's a tree in the CAS that corresponds exactly to the catalog at the ref you asked for.
update_source_with_cas takes advantage of that. Setting it on a block opts that block into a rewrite that happens after generation: the source attribute is replaced with a cas:: reference pointing at the content-addressed tree in your local CAS.
The attribute can go in any and all of three places:
unitblocks interragrunt.stack.hclstackblocks interragrunt.stack.hclterraformblocks interragrunt.hcl
Relative paths that are only valid inside the catalog become references that are valid anywhere, built from context Terragrunt captured when it fetched the catalog repository. A //subdir suffix on the source is preserved on the rewritten reference, so a source that selects a subdirectory keeps resolving to that same subdirectory inside the content-addressed tree.
Authoring a catalog with update_source_with_cas
Let's build the same network stack with the new attribute. The catalog has three layers:
The module at my-catalog/modules/vpc/ is plain OpenTofu/Terraform. Note what's absent: there's no values block and no ?ref= interpolation. Each layer references its neighbor with an ordinary relative path.
The live repository references the catalog stack with a single version pin:
Run terragrunt stack generate in live-repo/dev and the following happens, in order:
- Terragrunt fetches
my-catalogatv1.2.3once, into the CAS. - It generates the
networkstack and seesupdate_source_with_cason thevpcunit inside it, so it follows the relative path within the fetched catalog and materializes that unit's tree into the CAS as well. The unit'sterraformblock carries the same attribute, so the module source gets the same treatment. - Each generated layer is written out with its
sourcerewritten to acas::reference.
The generated stack file in the live repository ends up looking like this:
And the generated unit:
The cas:: prefix is a forced-getter protocol that tells Terragrunt to resolve the source from your local CAS. These generated files are self-contained: run terragrunt plan against that unit later and it fetches straight from the CAS, with no network requests and no dependency on the surrounding repository layout.
The end result is the property the values pattern was buying without the plumbing. One ?ref= at the top of the live repository pins the entire tree, and bumping the catalog version is a one-line change.
Why this matters for Pipelines users
To decide which units within a stack need a plan or an apply, it diffs the generated files on the filesystem: if a generated file changed, the unit it belongs to gets a run.
Under the values-plumbing pattern, the version reference was written into terragrunt.values.hcl files across the entire generated tree. Bump the catalog from v1.2.3 to v1.2.4 and every one of those files changes, so Pipelines sees every unit as changed and plans all of them. That happens even when the bump was only necessary to update one module and the content of everything else is identical.
With update_source_with_cas, the generated source is derived from content. A unit whose content didn't change between catalog versions hashes to the same cas:: reference, so there's no filesystem diff and Pipelines leaves it alone. Only the units whose content actually changed get planned and applied.
On a large estate, that's the difference between planning hundreds of units on every version bump and planning the handful that changed.
This Terragrunt feature was prioritized directly due to customer feedback in Pipelines. Pipelines users told us the version plumbing was both an ergonomics headache and a source of wasted work in Pipelines, and that feedback is what pushed this work up the Terragrunt roadmap.
Things to know before adopting
- You'll need to be using Terragrunt 1.1 or later to use this functionality, where the CAS is on by default. On 1.0.x the
casexperiment has to be enabled explicitly as an opt-in experiment. - The
update_source_with_casattribute defaults tofalse, and catalog authors opt in per block. Consumers of a catalog don't set anything; the rewrite follows the attributes the catalog author wrote. - The CAS hardlinks fetched content into
.terragrunt-cacheas read-only files to protect the shared store. If your workflow edits fetched sources in place, setmutable = trueon theterraformblock and Terragrunt will copy files from the CAS instead. - It pairs well with stack dependencies (
autoincludeblocks), which also graduated in 1.1. The Terragrunt 1.1 release post covers both.
Wrapping up
The new workflow allows for convenient relative paths in the catalog, one version pin in the live repository for your catalog, and content-addressed generation so that you can regenerate your stacks without issue. The CAS integration docs cover the full details.
Try it on one stack in your catalog, then check how many units actually get planned on your next version bump.
Features like the CAS and update_source_with_cas come out of the work we do supporting large Terragrunt estates. If that's the problem you're solving, check out Terragrunt Scale.



- No-nonsense DevOps insights
- Expert guidance
- Latest trends on IaC, automation, and DevOps
- Real-world best practices
