<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jeancy Joachim Mukaka</title>
    <description>The latest articles on Forem by Jeancy Joachim Mukaka (@jeancy).</description>
    <link>https://forem.com/jeancy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3822295%2F820b8506-9bc3-4eab-960d-cd36d34b1f2e.jpeg</url>
      <title>Forem: Jeancy Joachim Mukaka</title>
      <link>https://forem.com/jeancy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jeancy"/>
    <language>en</language>
    <item>
      <title>Stop Copy-Pasting Terraform State Configs: Use Terragrunt instead</title>
      <dc:creator>Jeancy Joachim Mukaka</dc:creator>
      <pubDate>Mon, 13 Apr 2026 14:31:31 +0000</pubDate>
      <link>https://forem.com/jeancy/stop-copy-pasting-terraform-state-configs-use-terragrunt-instead-ana</link>
      <guid>https://forem.com/jeancy/stop-copy-pasting-terraform-state-configs-use-terragrunt-instead-ana</guid>
      <description>&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;br&gt;
Before getting started, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic knowledge of Terraform (HCL Syntax, resources, variables, remote state), the full prerequisite code is available in the &lt;a href="https://github.com/JM01lab/aws-terraform-infrastructure" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Terraform installed on your machine (v0.12 or higher)&lt;/li&gt;
&lt;li&gt;Terragrunt installed, check the &lt;a href="https://terragrunt.gruntwork.io/docs/getting-started/install/" rel="noopener noreferrer"&gt;official installation guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An AWS account with suffficient permissions to create S3  buckets and DynamoDB tables&lt;/li&gt;
&lt;li&gt;AWS CLI configured with your credentials (aws configure)&lt;/li&gt;
&lt;li&gt;Visual Studio Codes as your code editor, with the &lt;a href="https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform" rel="noopener noreferrer"&gt;HashiCorp Terraform extension&lt;/a&gt; for syntax highlighting and autocompletion. &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you have been working with Terraform for a while, you have probably faced this situation: you have a working configuration for your dev environment, and now you need to deploy the same infrastructure to staging and prod. So you copy the folder, update a few values, including the remote state backend configuration, and repeat. It works, but something feels wrong.&lt;br&gt;
That "something" is a violation of the DRY principle, don't repeat yourself. Every time you duplicate your backend configuration, you create a new opportunity for error and a new file to maintain. &lt;br&gt;
In this article, we will explore how Terragrunt solves this problem by allowing you to define your remote state configuration once and reuse it across all your environments.&lt;br&gt;
If you are new to terraform, I recommend exploring &lt;a href="https://github.com/JM01lab/aws-terraform-infrastructure" rel="noopener noreferrer"&gt;prerequisite code on GitHub&lt;/a&gt; before diving in.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;The Problem: Repeat Remote State&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When managing multiple environments with Terraform, most developers end up with a structure like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;environments/
├── dev/
│   └── main.tf
├── staging/
│   └── main.tf
└── prod/
    └── main.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And inside each main.tf, the same backend block appears with only one line changing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# dev/main.tf&lt;/span&gt;
&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-terraform-state"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev/terraform.tfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-west-2"&lt;/span&gt;
    &lt;span class="nx"&gt;dynamodb_table&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-locks"&lt;/span&gt;
    &lt;span class="nx"&gt;encrypt&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same block is then copy-pasted into staging/main.tf and prod/main.tf, with only the key value changing (staging/terraform.tfstate, prod/terraform.tfsate). That's three files, three times the same configuration. And if you ever need to change the bucket name, the region, or encryption, you have to update every single file manually. This is exactly the kind of repetition that leads to human error and maintenance nightmares.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is Terragrunt?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Terragrunt is a thin wrapper around Terraform, developed by Gruntwork, It doesn't replace Terraform, it enhances it by providing additional tools to keep your configurations DRY, manageable, and consistent across environments.&lt;br&gt;
Think of it this way: Terraform is the engine, and Terragrunt is the intelligent framework built around it. You still write the same HCL code you know, but Terragrunt handless the repetitive parts for you.&lt;br&gt;
With Terragrunt you can: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define your remote state configuration once and reuse it across all environments&lt;/li&gt;
&lt;li&gt;Automatically create your S3 bucket and DynamoDB table if they don't exist&lt;/li&gt;
&lt;li&gt;Deploy multiple environments with a single command&lt;/li&gt;
&lt;li&gt;Keep your codebase clean, readable, and easy to maintain
The key concept we'll focus on in this article is the remote_state block — the feature that eliminates repeated backend configurations across environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;The Solution: Centralized Remote State with Terragrunt&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Instead of repeating the backend configuration in every environment, Terragrunt lets you define it once in a root terragrunt hcl file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project/
├── terragrunt.hcl        ← defined once here
├── dev/
│   └── terragrunt.hcl    ← only what changes
├── staging/
│   └── terragrunt.hcl    ← only what changes
└── prod/
    └── terragrunt.hcl    ← only what changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root terragrunt.hcl contains the full remote state configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# terragrunt.hcl (root)&lt;/span&gt;
&lt;span class="nx"&gt;remote_state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt;

  &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-terraform-state"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path_relative_to_include()}/terraform.tfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-west-2"&lt;/span&gt;
    &lt;span class="nx"&gt;dynamodb_table&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-locks"&lt;/span&gt;
    &lt;span class="nx"&gt;encrypt&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;generate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"backend.tf"&lt;/span&gt;
    &lt;span class="nx"&gt;if_exists&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"overwrite_terragrunt"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each environment file simply inherits from the root. Here is the dev example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# dev/terragrunt.hcl&lt;/span&gt;
&lt;span class="nx"&gt;include&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;find_in_parent_folders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;environment&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The staging and prod files follow the exact same structure, only the environment and instance_type values change. That's it. Three environments, three small files, each containing only what is unique to that environment. The backend configuration lives in one place and is never repeated.&lt;br&gt;
 &lt;em&gt;The full project structure with all environments is available in the GitHub repository.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Key Terragrunt Functions Explained&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Two functions make all of this possible. Understanding them is the key to mastering Terragrunt.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;find_in_parent_folders()&lt;/strong&gt;
This funtcion automatically searches parent directories for the root terragrunt.hcl file. It allows each environment file to inherit the root configuration without hardcoding the path.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;include&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;find_in_parent_folders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# finds ../../terragrunt.hcl automatically&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No matter how deeply nested your environment folder is, Terragrunt will always find the root configuration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;path_relative_to_include()&lt;/strong&gt;
This is the function that makes the state key dynamic. It returns the relative path of the current environment folder from the root.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path_relative_to_include()}/terraform.tfstate"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Concretely, this means:&lt;br&gt;
|Environment folder | Generated state key |&lt;br&gt;
| :---------------- | :------------------ |&lt;br&gt;
| &lt;code&gt;dev/&lt;/code&gt;            | &lt;code&gt;dev/terraform.tfstate&lt;/code&gt; |&lt;br&gt;
| &lt;code&gt;staging/&lt;/code&gt;        | &lt;code&gt;staging/terraform.tfstate&lt;/code&gt; |&lt;br&gt;
| &lt;code&gt;prod/&lt;/code&gt;           | &lt;code&gt;prod/terraform.tfstate&lt;/code&gt; |&lt;/p&gt;

&lt;p&gt;Each environment automatically gets its own isolated state file in S3, with zero manual configuration.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;The generate Block&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;This block is often overlooked but extremely powerful. It tells Terragrunt to automatically generate a backend.tf file in each environment folder before running Terraform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;generate&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"backend.tf"&lt;/span&gt;
  &lt;span class="nx"&gt;if_exists&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"overwrite_terragrunt"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means you never have to manually write a backend.tf file again. Terragrunt generates it for you, every time, with the correct values.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Deploying All Environments&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;once your configuration is in place, deploying all environments is as simple as running a single command from the root folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deploy all environments at once&lt;/span&gt;
terragrunt run-all apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terragrunt will automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect all terragrunt.hcl files in subdirectories&lt;/li&gt;
&lt;li&gt;Run terraform init for each environment&lt;/li&gt;
&lt;li&gt;Deploy each environment in the correct order&lt;/li&gt;
&lt;li&gt;Create the S3 bucket and DynamoDB table if they don't exist yet
you can also target a specific environment:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deploy only dev&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;dev/
terragrunt apply

&lt;span class="c"&gt;# Check outputs across all environments&lt;/span&gt;
terragrunt run-all output

&lt;span class="c"&gt;# Destroy all environments&lt;/span&gt;
terragrunt run-all destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare this to the old approach where you had to navigate into each folder manually, run terraform init, then terraform apply, and repeat for every environment. With Terragrunt, that entire workflow collapses into one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Managing Terraform remote state across multiple environments doesn't have to be painful. With Terragrunt's remotestate block, &lt;code&gt;find_in_parent_folders()&lt;/code&gt;, and &lt;code&gt;path_relative_to_include()&lt;/code&gt;, you can define your backend configuration once and let Terragrunt handle the rest.&lt;br&gt;
Let's recap what we covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem: repeated backend configuration across environments violate DRY principle&lt;/li&gt;
&lt;li&gt;The solution: a single root terragrunt.hcl that centralizes the remote state configuration&lt;/li&gt;
&lt;li&gt;The magic functions: &lt;code&gt;find_in_parent_folders()&lt;/code&gt; and &lt;code&gt;path_relative_to_include()&lt;/code&gt;that make everything dynamic&lt;/li&gt;
&lt;li&gt;The power of Terragrunt run-all apply: deploy all environments in one command.
This is just the beginning of what Terragrunt can do. In the next article, Part 2, we will go deeper and explore how to split your Terraform dependency blocks. You will learn why putting your VPC, your security groups, and your EC2 instances in the same state file is a risk and how to fix it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you found this article helpful, feel free to share it and follow me for Part 2. The code for this article is available on &lt;a href="https://github.com/JM01lab/aws-terragrunt-examples" rel="noopener noreferrer"&gt;my GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>infrastructureascode</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
