---
title: "Step 7: Taking advantage of Terragrunt Stacks"
description: Taking advantage of Terragrunt Stacks
slug: docs/guides/terralith-to-terragrunt/step-7-taking-advantage-of-terragrunt-stacks
sidebar:
  order: 10
---

import FileTree from '@components/vendored/starlight/FileTree.astro';
import { Code, Aside } from '@astrojs/starlight/components';

Up until relatively recently in the history of Terragrunt, the proliferation of `terragrunt.hcl` files was the trade-off platform engineers had to accept for state segmentation. Luckily, with the advent of [Terragrunt Stacks](/docs/features/stacks/), that's not the case anymore. Collections of units, like the ones you created in the last step, can be generated on-demand using `terragrunt.stack.hcl` files. This saves you from having to duplicate `terragrunt.hcl` files across your codebase.

In this step, you will migrate to this modern pattern. You'll start by persisting your unit definitions in your `catalog`. Then, you'll replace the numerous `terragrunt.hcl` files in your `live` directory with a single `terragrunt.stack.hcl` file in each environment. To handle the slight configuration differences between `dev` and `prod`, you'll use Terragrunt's `values` attributes, which allow you to parameterize your reusable unit definitions.

## Tutorial

Let's start migrating to Terragrunt Stacks by persisting unit definitions in the `catalog`.

<Aside type="tip">
We copy over `.terraform.lock.hcl` files in addition to `terragrunt.hcl` files here. Persisting `.terraform.lock.hcl` files is a best practice to ensure reproducibility of units, and to maximize the performance of OpenTofu (as OpenTofu relies on the availability of this file when determining whether it can safely reuse content in the provider cache directory).
</Aside>

```bash
mkdir -p catalog/units/{ddb, iam, lambda, s3}
cp live/dev/ddb/{terragrunt.hcl, .terraform.lock.hcl} catalog/units/ddb/
cp live/dev/iam/{terragrunt.hcl, .terraform.lock.hcl} catalog/units/iam/
cp live/dev/lambda/{terragrunt.hcl, .terraform.lock.hcl} catalog/units/lambda/
cp live/dev/s3/{terragrunt.hcl, .terraform.lock.hcl} catalog/units/s3/
```

You might remember that the unit configurations differed very slightly between `dev` and `prod`. Luckily, Terragrunt has special tooling to handle that via the usage of `values` variables.

import catalogUnitsDdbTerragruntHcl from '../../../../fixtures/terralith-to-terragrunt/walkthrough/step-7-taking-advantage-of-terragrunt-stacks/catalog/units/ddb/terragrunt.hcl?raw';

<Code title="catalog/units/ddb/terragrunt.hcl" lang="hcl" code={catalogUnitsDdbTerragruntHcl} />

By specifying `values.name` there, we're allowing values to be used in our unit configurations from `terragrunt.stack.hcl` files. You'll see how these values are set later in this step.

import catalogUnitsIamTerragruntHcl from '../../../../fixtures/terralith-to-terragrunt/walkthrough/step-7-taking-advantage-of-terragrunt-stacks/catalog/units/iam/terragrunt.hcl?raw';

<Code title="catalog/units/iam/terragrunt.hcl" lang="hcl" code={catalogUnitsIamTerragruntHcl} />

<Aside type="note">
We're not just using the `values` variable for the inputs that differ between stacks, like the `name` and `aws_region`. We can use them for any value we want to substitute in our `terragrunt.hcl` files, including the relative paths to other units (which we might want to be dynamic if we're going to refactor or add additional dependencies, etc.)
</Aside>

import catalogUnitsLambdaTerragruntHcl from '../../../../fixtures/terralith-to-terragrunt/walkthrough/step-7-taking-advantage-of-terragrunt-stacks/catalog/units/lambda/terragrunt.hcl?raw';

<Code title="catalog/units/lambda/terragrunt.hcl" lang="hcl" code={catalogUnitsLambdaTerragruntHcl} />

import catalogUnitsS3TerragruntHcl from '../../../../fixtures/terralith-to-terragrunt/walkthrough/step-7-taking-advantage-of-terragrunt-stacks/catalog/units/s3/terragrunt.hcl?raw';

<Code title="catalog/units/s3/terragrunt.hcl" lang="hcl" code={catalogUnitsS3TerragruntHcl} />

Now we can replace the `terragrunt.hcl` files in `live` with a single `terragrunt.stack.hcl` file in each environment to generate them on-demand using `unit` blocks. By default, units generated by Terragrunt are generated into `.terragrunt-stack` directories. We opt out of that by setting `no_dot_terragrunt_stack` to `true`.

import liveDevTerragruntStackHcl from '../../../../fixtures/terralith-to-terragrunt/walkthrough/step-7-taking-advantage-of-terragrunt-stacks/live/dev/terragrunt.stack.hcl?raw';

<Code title="live/dev/terragrunt.stack.hcl" lang="hcl" code={liveDevTerragruntStackHcl} />

We'll also add this `.gitignore` file to avoid recommitting the generated files in our repository, as they'll be regenerated whenever we need them. We'll see how we can remove the need for this in a future step.

import liveDevGitignore from '../../../../fixtures/terralith-to-terragrunt/walkthrough/step-7-taking-advantage-of-terragrunt-stacks/live/dev/.gitignore?raw';

<Code title="live/dev/.gitignore" lang="hcl" code={liveDevGitignore} />

Now that we can generate these unit configurations on demand, we can remove the copies that we created manually!

<Code title="live/dev" lang="bash" frame="terminal" code={`rm -rf .terraform.lock.hcl ddb iam lambda s3
terragrunt run --all plan`} />

All that's left now is to repeat the same thing for prod.

import liveProdTerragruntStackHcl from '../../../../fixtures/terralith-to-terragrunt/walkthrough/step-7-taking-advantage-of-terragrunt-stacks/live/prod/terragrunt.stack.hcl?raw';

<Code title="live/prod/terragrunt.stack.hcl" lang="hcl" code={liveProdTerragruntStackHcl} />

import liveProdGitignore from '../../../../fixtures/terralith-to-terragrunt/walkthrough/step-7-taking-advantage-of-terragrunt-stacks/live/prod/.gitignore?raw';

<Code title="live/prod/.gitignore" lang="hcl" code={liveProdGitignore} />

<Code title="live/prod" lang="bash" frame="terminal" code={`rm -rf .terraform.lock.hcl ddb iam lambda s3
terragrunt run --all plan`} />

## Project Layout Check-in

If you clean out the `.gitignore`'ed files and take a look at the file tree, you should see that your `live` file count has shrunk down again!

<Code title="live" lang="bash" frame="terminal" code={`rm -rf ./***/ddb ./***/iam ./***/lambda ./***/s3`} />

<FileTree>
- live
  - dev
    - terragrunt.stack.hcl
  - prod
    - terragrunt.stack.hcl
  - root.hcl
</FileTree>


## Trade-offs

You've now adopted one of Terragrunt's most advanced features, **Terragrunt Stacks**, to achieve an exceptionally clean and DRY (Don't Repeat Yourself) infrastructure codebase. By generating your component configurations on the fly from a central catalog, you've eliminated the last major source of boilerplate in your Terragrunt configurations. However, this abstraction comes with its own set of trade-offs.

### Pros

- **Maximum Reusability and Deduplication**: This is the most significant benefit of using Terragrunt Stacks. Instead of having multiple `terragrunt.hcl` files scattered across each environment's subdirectories, you now have a single, reusable unit definition for each component in your `catalog`. Adding a new environment is as simple as creating a new `terragrunt.stack.hcl` and defining its unique inputs.
- **Simplified `live` Directory**: Your `live` directory is now incredibly lean and easy to navigate. Each environment is represented by a single `terragrunt.stack.hcl` file, which serves as a clear manifest of all the components that make up that environment. This is a similar layout to that which was achieved in step 5, but we've gained the ability to retain state segmentation and operate granularly.
- **Centralized Configuration Catalog**: If you need to update the configuration for a component across *all* environments (e.g., add a new dependency or change a `mock_output`), you only need to edit the corresponding file in `catalog/units`. This drastically reduces the chance of configuration drift and makes maintenance much easier.

### Cons

- **Increased Abstraction**: The biggest trade-off is the added layer of indirection. Engineers no longer have all the configuration for their Terragrunt units committed to their repository. If they want to read through their configurations, they need to generate the stack, or read the contents stored in their `catalog`.
- **Steeper Learning Curve**: The concepts of `terragrunt.stack.hcl`, `unit` blocks, and the `values` attribute are powerful but are also more advanced Terragrunt features. Onboarding new team members may require more time to explain this higher level of abstraction compared to the more explicit file-based approach from the previous step.

## Wrap Up

You've conquered the final major source of Terragrunt boilerplate! In this step, you adopted one of Terragrunt's most powerful features: **Terragrunt Stacks**.

By centralizing your generic unit configurations into the `catalog`, you were able to replace the numerous `terragrunt.hcl` files in each environment with a single, clean `terragrunt.stack.hcl` file. The core concept you mastered was using the `values` object to **parameterize** these reusable units, allowing you to define a component's configuration once and deploy it many times with environment-specific values.

However, there's one piece of technical debt left from our migration: the state paths in your S3 backend don't yet align with Terragrunt's default conventions, requiring those annoying `.gitignore` files. In the final step, you'll perform one last set of state migrations to finalize your layout, fully mastering this guide.
