<?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>DEV Community: Mary Mutua</title>
    <description>The latest articles on DEV Community by Mary Mutua (@mary_mutua_9d55b3c269f343).</description>
    <link>https://dev.to/mary_mutua_9d55b3c269f343</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%2F3835891%2F74c43ea9-1b3f-4c6f-b41c-a036e89143a0.png</url>
      <title>DEV Community: Mary Mutua</title>
      <link>https://dev.to/mary_mutua_9d55b3c269f343</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mary_mutua_9d55b3c269f343"/>
    <language>en</language>
    <item>
      <title>How to Convince Your Team to Adopt Infrastructure as Code</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Sat, 11 Apr 2026 21:28:34 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/how-to-convince-your-team-to-adopt-infrastructure-as-code-8bf</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/how-to-convince-your-team-to-adopt-infrastructure-as-code-8bf</guid>
      <description>&lt;p&gt;Convincing a team to adopt Infrastructure as Code is not a technical challenge - it is a people and process challenge.  &lt;/p&gt;

&lt;p&gt;Day 19 of my Terraform journey focused on this exact question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you actually convince a team (and leadership) to adopt IaC?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is what worked for me and what I’ve seen in real team workflows, grounded in Chapter 10 of &lt;em&gt;Terraform: Up &amp;amp; Running&lt;/em&gt; by Yevgeniy Brikman.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start With the Business Case, Not the Tool
&lt;/h2&gt;

&lt;p&gt;The biggest mistake is leading with Terraform features.&lt;/p&gt;

&lt;p&gt;What leadership really cares about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fewer incidents&lt;/li&gt;
&lt;li&gt;less downtime&lt;/li&gt;
&lt;li&gt;predictable releases&lt;/li&gt;
&lt;li&gt;auditability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the teams I’ve worked with, the real pain points are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;manual changes after PR merges&lt;/li&gt;
&lt;li&gt;environment updates applied directly on servers&lt;/li&gt;
&lt;li&gt;outages caused by exhausted workers&lt;/li&gt;
&lt;li&gt;secrets stored on servers and readable by anyone with access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the argument is not “Terraform is cool.”&lt;br&gt;&lt;br&gt;
The argument is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“We can reduce production incidents caused by manual drift.”&lt;/li&gt;
&lt;li&gt;“We can standardize autoscaling and reduce downtime.”&lt;/li&gt;
&lt;li&gt;“We can build a full audit trail for infra changes.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This aligns with Brikman’s key point: &lt;strong&gt;lead with the problem you are solving, not the tooling.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Adopt Incrementally, Not All at Once
&lt;/h2&gt;

&lt;p&gt;Big‑bang migrations almost always fail.&lt;/p&gt;

&lt;p&gt;The safer strategy is incremental:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pick one small, real problem&lt;/li&gt;
&lt;li&gt;solve it with IaC&lt;/li&gt;
&lt;li&gt;show results quickly&lt;/li&gt;
&lt;li&gt;build trust and momentum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple first win would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a new S3 bucket managed entirely in Terraform&lt;/li&gt;
&lt;li&gt;remote state stored in S3&lt;/li&gt;
&lt;li&gt;PR workflow for infra changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This reflects Brikman’s lesson: &lt;strong&gt;incremental wins build momentum and reduce risk.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Give the Team Time and Safety to Learn
&lt;/h2&gt;

&lt;p&gt;IaC fails when only one person knows how it works.&lt;/p&gt;

&lt;p&gt;In a real incident, people will choose the fastest fix.&lt;br&gt;&lt;br&gt;
If Terraform feels slow or unfamiliar, they will go manual - and drift begins.&lt;/p&gt;

&lt;p&gt;To avoid that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;training time must be deliberate&lt;/li&gt;
&lt;li&gt;workflows must be documented&lt;/li&gt;
&lt;li&gt;the “right way” must be the easiest way&lt;/li&gt;
&lt;li&gt;PR reviews and &lt;code&gt;terraform plan&lt;/code&gt; should be standard&lt;/li&gt;
&lt;li&gt;no manual console changes for Terraform‑managed resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the cultural shift Brikman emphasizes: &lt;strong&gt;code‑first operations must be taught and supported, not assumed.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Common Failure Modes to Avoid
&lt;/h2&gt;

&lt;p&gt;These are the patterns that keep showing up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trying to migrate everything at once&lt;/li&gt;
&lt;li&gt;Starting without leadership buy‑in&lt;/li&gt;
&lt;li&gt;Underestimating the learning curve&lt;/li&gt;
&lt;li&gt;One person owns all IaC knowledge&lt;/li&gt;
&lt;li&gt;Outage happens and the team goes manual&lt;/li&gt;
&lt;li&gt;Drift accumulates until Terraform is abandoned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is always the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;smaller steps&lt;/li&gt;
&lt;li&gt;clearer training&lt;/li&gt;
&lt;li&gt;consistent team practices&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A 4‑Phase Adoption Plan That Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Phase 1 - Start with something new (2–4 weeks)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Create a new S3 bucket (logs/backups) in Terraform.&lt;br&gt;&lt;br&gt;
Low risk, clear win.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2 - Import existing infrastructure&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Use &lt;code&gt;terraform import&lt;/code&gt; for one high‑change resource (e.g., security group).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 3 - Establish team practices&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Module versioning, PR reviews, &lt;code&gt;terraform plan&lt;/code&gt; in PRs, CI checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 4 - Automate deployments&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
CI/CD runs &lt;code&gt;terraform apply&lt;/code&gt; on merge to main with approval gates.&lt;/p&gt;

&lt;p&gt;Each phase delivers value even if the next phase takes longer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;If you want your team to adopt IaC, don’t start with the tool.&lt;br&gt;&lt;br&gt;
Start with the biggest pain the team is already feeling.&lt;/p&gt;

&lt;p&gt;Solve that one problem with IaC.&lt;br&gt;&lt;br&gt;
Then repeat.&lt;/p&gt;

&lt;p&gt;That is how real adoption starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub (Day 19):
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_19" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow My Journey
&lt;/h2&gt;

&lt;p&gt;This is Day 19 of my 30‑Day Terraform Challenge.&lt;br&gt;&lt;br&gt;
See you on Day 20.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>infrastructureascode</category>
      <category>devops</category>
    </item>
    <item>
      <title>Automating Terraform Testing: From Unit Tests to End-to-End Validation</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Thu, 09 Apr 2026 19:17:16 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/automating-terraform-testing-from-unit-tests-to-end-to-end-validation-2e44</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/automating-terraform-testing-from-unit-tests-to-end-to-end-validation-2e44</guid>
      <description>&lt;p&gt;Day 18 of my Terraform journey was one of the most exciting so far because it brought everything together: local testing, real AWS validation, and a CI/CD pipeline that runs automatically in GitHub Actions.&lt;/p&gt;

&lt;p&gt;In Chapter 9 of &lt;em&gt;Terraform: Up &amp;amp; Running&lt;/em&gt;, Yevgeniy Brikman explains that manual testing is necessary, but it does not scale. Once infrastructure grows beyond what one person can test by hand every time, you need automation that catches regressions early and gives the whole team confidence to move faster.&lt;/p&gt;

&lt;p&gt;That was the focus of today.&lt;/p&gt;

&lt;p&gt;I implemented all three layers of Terraform testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unit tests with &lt;code&gt;terraform test&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;integration tests with Terratest&lt;/li&gt;
&lt;li&gt;end-to-end tests with Terratest&lt;/li&gt;
&lt;li&gt;a GitHub Actions workflow to run them automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_18" rel="noopener noreferrer"&gt;GitHub Day 18 Link&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why One Test Type Is Not Enough
&lt;/h2&gt;

&lt;p&gt;One of the clearest lessons from today is that no single test layer is enough on its own.&lt;/p&gt;

&lt;p&gt;If you only use unit tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;they are fast and cheap&lt;/li&gt;
&lt;li&gt;but they do not prove real AWS behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only use integration tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;they prove module composition&lt;/li&gt;
&lt;li&gt;but they still do not test the full stack from the outside&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only use end-to-end tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;they give the strongest confidence&lt;/li&gt;
&lt;li&gt;but they are slower, more expensive, and harder to debug&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right strategy is to use all three.&lt;/p&gt;
&lt;h2&gt;
  
  
  Layer 1: Unit Tests with &lt;code&gt;terraform test&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Terraform 1.6+ includes native testing through &lt;code&gt;.tftest.hcl&lt;/code&gt; files, which makes lightweight unit-style testing much easier.&lt;/p&gt;

&lt;p&gt;For Day 18, I used &lt;code&gt;terraform test&lt;/code&gt; to validate module behavior without creating any real infrastructure.&lt;/p&gt;

&lt;p&gt;These tests checked things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ASG naming logic&lt;/li&gt;
&lt;li&gt;instance type propagation&lt;/li&gt;
&lt;li&gt;security group port configuration&lt;/li&gt;
&lt;li&gt;variable validation for invalid environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform &lt;span class="nt"&gt;-chdir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;day_18/modules/services/webserver-cluster &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I liked about this layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;very fast&lt;/li&gt;
&lt;li&gt;no AWS cost&lt;/li&gt;
&lt;li&gt;no external dependencies&lt;/li&gt;
&lt;li&gt;great for checking Terraform logic early&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tradeoff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it does not prove that AWS will accept and run the infrastructure correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So unit tests are excellent for fast feedback, but they only cover part of the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: Integration Tests with Terratest
&lt;/h2&gt;

&lt;p&gt;The next layer was Terratest integration testing.&lt;/p&gt;

&lt;p&gt;Here, the goal was to deploy real infrastructure and verify that the reusable modules worked correctly together in AWS.&lt;/p&gt;

&lt;p&gt;For my integration test, I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Day 18 webserver cluster module&lt;/li&gt;
&lt;li&gt;a lightweight example root module&lt;/li&gt;
&lt;li&gt;the default VPC as the test harness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;applied the infrastructure&lt;/li&gt;
&lt;li&gt;read outputs like &lt;code&gt;alb_dns_name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;sent HTTP requests to the deployed ALB&lt;/li&gt;
&lt;li&gt;verified the expected response&lt;/li&gt;
&lt;li&gt;destroyed the resources afterward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gave me confidence that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform could really apply the stack&lt;/li&gt;
&lt;li&gt;the ALB, ASG, target group, and alarms worked together&lt;/li&gt;
&lt;li&gt;the app was actually reachable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tradeoff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slower than unit tests&lt;/li&gt;
&lt;li&gt;creates real AWS resources&lt;/li&gt;
&lt;li&gt;costs money&lt;/li&gt;
&lt;li&gt;needs cleanup discipline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still, this is the layer where Terraform starts moving from “logic looks right” to “real infrastructure works.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: End-to-End Tests with Terratest
&lt;/h2&gt;

&lt;p&gt;The most complete layer was the end-to-end test.&lt;/p&gt;

&lt;p&gt;Instead of using existing networking, this test deployed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a fresh VPC&lt;/li&gt;
&lt;li&gt;subnets&lt;/li&gt;
&lt;li&gt;the webserver stack on top of that VPC&lt;/li&gt;
&lt;li&gt;the full public application path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it verified that the final app response was correct from the outside.&lt;/p&gt;

&lt;p&gt;This was the strongest test because it checked the complete system, not just a subset of it.&lt;/p&gt;

&lt;p&gt;It also exposed a real issue:&lt;br&gt;
my first end-to-end run failed because the subnet CIDRs did not fit inside the VPC CIDR range. AWS rejected them with an &lt;code&gt;InvalidSubnet.Range&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;That was a very useful reminder that end-to-end tests often catch the kinds of real cloud issues that smaller tests miss.&lt;/p&gt;

&lt;p&gt;After fixing the VPC CIDR input, the end-to-end test passed and cleaned up correctly.&lt;/p&gt;

&lt;p&gt;Tradeoff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slowest test layer&lt;/li&gt;
&lt;li&gt;most expensive&lt;/li&gt;
&lt;li&gt;most realistic&lt;/li&gt;
&lt;li&gt;best confidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why end-to-end tests are powerful, but you do not want to rely on them alone for every kind of feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CI/CD Pipeline That Ties It Together
&lt;/h2&gt;

&lt;p&gt;After getting the local tests working, I built a GitHub Actions workflow to automate the testing pipeline.&lt;/p&gt;

&lt;p&gt;The workflow runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unit tests on pull requests&lt;/li&gt;
&lt;li&gt;unit, integration, and end-to-end tests on pushes to &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;manual workflow runs when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That setup gave me a nice balance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PRs stay faster and cheaper&lt;/li&gt;
&lt;li&gt;merges to &lt;code&gt;main&lt;/code&gt; get full confidence checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the best parts of today was seeing the pipeline work the way it should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;local tests passed&lt;/li&gt;
&lt;li&gt;the first GitHub Actions run failed&lt;/li&gt;
&lt;li&gt;I fixed the workflow&lt;/li&gt;
&lt;li&gt;opened a pull request from a feature branch&lt;/li&gt;
&lt;li&gt;PR checks ran&lt;/li&gt;
&lt;li&gt;merged into &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Actions ran all three layers successfully&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That was a great end-to-end learning moment for both Terraform testing and CI/CD.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Each Layer Contributed
&lt;/h2&gt;

&lt;p&gt;Here is how I now think about the testing stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit tests
&lt;/h3&gt;

&lt;p&gt;Tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform test&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validation logic&lt;/li&gt;
&lt;li&gt;naming&lt;/li&gt;
&lt;li&gt;output expectations&lt;/li&gt;
&lt;li&gt;fast feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Integration tests
&lt;/h3&gt;

&lt;p&gt;Tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terratest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;real AWS deployment checks&lt;/li&gt;
&lt;li&gt;reusable module composition&lt;/li&gt;
&lt;li&gt;output and connectivity verification&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  End-to-end tests
&lt;/h3&gt;

&lt;p&gt;Tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terratest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full-stack confidence&lt;/li&gt;
&lt;li&gt;fresh environment testing&lt;/li&gt;
&lt;li&gt;user-visible behavior&lt;/li&gt;
&lt;li&gt;catching real infrastructure wiring problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That layered approach is what makes the whole system strong.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Main Takeaway
&lt;/h2&gt;

&lt;p&gt;The biggest lesson from Day 18 was this:&lt;/p&gt;

&lt;p&gt;good Terraform testing is not about choosing one “best” test type.&lt;br&gt;&lt;br&gt;
It is about using the right mix of test layers for the right level of confidence.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unit tests are fast and free, but test less&lt;/li&gt;
&lt;li&gt;integration tests are more realistic, but slower and costlier&lt;/li&gt;
&lt;li&gt;end-to-end tests are the most thorough, but also the heaviest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right strategy uses all three.&lt;/p&gt;

&lt;p&gt;And once those tests are connected to CI/CD, infrastructure changes stop being something you only hope will work and start becoming something your pipeline can prove.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code
&lt;/h2&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_18" rel="noopener noreferrer"&gt;GitHub Day 18 Link&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow My Journey
&lt;/h2&gt;

&lt;p&gt;This is Day 18 of my 30-Day Terraform Challenge.&lt;br&gt;&lt;br&gt;
See you on Day 19.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Importance of Manual Testing in Terraform</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Thu, 09 Apr 2026 12:50:50 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/the-importance-of-manual-testing-in-terraform-44ol</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/the-importance-of-manual-testing-in-terraform-44ol</guid>
      <description>&lt;p&gt;Day 17 of my Terraform journey focused on something that sounds simple, but is actually a big part of real infrastructure work: manual testing.&lt;/p&gt;

&lt;p&gt;In Chapter 9 of &lt;em&gt;Terraform: Up &amp;amp; Running&lt;/em&gt;, Yevgeniy Brikman makes a strong case that manual testing is not something you outgrow once automated testing enters the picture. Instead, it is often the step that teaches you what should be automated, what success really looks like, and what can go wrong in the real world.&lt;/p&gt;

&lt;p&gt;The biggest lesson from today was this:&lt;/p&gt;

&lt;p&gt;manual testing is not the opposite of automated testing.&lt;br&gt;&lt;br&gt;
It is often the step that makes good automated testing possible.&lt;/p&gt;

&lt;p&gt;Before you can automate a test well, you need to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what you are testing&lt;/li&gt;
&lt;li&gt;what success looks like&lt;/li&gt;
&lt;li&gt;what failure looks like&lt;/li&gt;
&lt;li&gt;how the infrastructure behaves in real life&lt;/li&gt;
&lt;li&gt;how to clean it up safely afterward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Day 17, I built a structured manual testing process for my Terraform webserver cluster, ran it against both dev and production environments, documented the results, and treated cleanup as part of the test itself.&lt;/p&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_17" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Manual Testing Still Matters
&lt;/h2&gt;

&lt;p&gt;As Yevgeniy Brikman explains in Chapter 9, automated tests are powerful, but they do not replace the need to first understand the behavior of your infrastructure. Manual testing helps you build that understanding.&lt;/p&gt;

&lt;p&gt;Automated tests are excellent for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repeatability&lt;/li&gt;
&lt;li&gt;regression detection&lt;/li&gt;
&lt;li&gt;CI/CD confidence&lt;/li&gt;
&lt;li&gt;faster feedback on future changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But manual testing is still valuable because it helps you discover things automated tests do not always reveal immediately, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;confusing outputs&lt;/li&gt;
&lt;li&gt;weak defaults&lt;/li&gt;
&lt;li&gt;cloud-provider quirks&lt;/li&gt;
&lt;li&gt;slow readiness times&lt;/li&gt;
&lt;li&gt;cleanup surprises&lt;/li&gt;
&lt;li&gt;differences between environments&lt;/li&gt;
&lt;li&gt;gaps in documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Manual testing teaches you what actually matters to verify.&lt;/p&gt;

&lt;p&gt;That is why I now see it as the foundation for better automation, not as something temporary or optional.&lt;/p&gt;
&lt;h2&gt;
  
  
  What a Structured Manual Test Should Cover
&lt;/h2&gt;

&lt;p&gt;A manual test should be more than just “run &lt;code&gt;terraform apply&lt;/code&gt; and click around.”&lt;/p&gt;

&lt;p&gt;For Day 17, I organized my checklist into these categories.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Provisioning Verification
&lt;/h3&gt;

&lt;p&gt;This is the Terraform layer.&lt;/p&gt;

&lt;p&gt;Questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did &lt;code&gt;terraform init&lt;/code&gt; complete without errors?&lt;/li&gt;
&lt;li&gt;Did &lt;code&gt;terraform validate&lt;/code&gt; pass cleanly?&lt;/li&gt;
&lt;li&gt;Did &lt;code&gt;terraform plan&lt;/code&gt; show the expected resources?&lt;/li&gt;
&lt;li&gt;Did &lt;code&gt;terraform apply&lt;/code&gt; complete successfully?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This confirms that the configuration is valid and deployable.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Resource Correctness
&lt;/h3&gt;

&lt;p&gt;This is the “did Terraform create what I expected?” layer.&lt;/p&gt;

&lt;p&gt;Questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are the expected AWS resources present?&lt;/li&gt;
&lt;li&gt;Do names, tags, and regions match the variables?&lt;/li&gt;
&lt;li&gt;Are security group rules exactly what I intended?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is important because infrastructure can be “working” while still being incorrectly shaped.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Functional Verification
&lt;/h3&gt;

&lt;p&gt;This is the behavior layer.&lt;/p&gt;

&lt;p&gt;Questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the ALB DNS name resolve?&lt;/li&gt;
&lt;li&gt;Does &lt;code&gt;curl&lt;/code&gt; return the expected response?&lt;/li&gt;
&lt;li&gt;Are instances healthy behind the load balancer?&lt;/li&gt;
&lt;li&gt;Does the environment behave as the service is supposed to?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the part that tells you whether the deployed system is actually usable.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. State Consistency
&lt;/h3&gt;

&lt;p&gt;This checks whether Terraform and reality still match.&lt;/p&gt;

&lt;p&gt;Questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does &lt;code&gt;terraform plan&lt;/code&gt; return “No changes” after apply?&lt;/li&gt;
&lt;li&gt;Does the state reflect what exists in AWS?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is important because drift or hidden mismatches are easy to miss otherwise.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Regression Check
&lt;/h3&gt;

&lt;p&gt;This tests whether a small change behaves predictably.&lt;/p&gt;

&lt;p&gt;Questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If I change one small thing, does Terraform show only that change?&lt;/li&gt;
&lt;li&gt;After applying it, does &lt;code&gt;terraform plan&lt;/code&gt; return clean again?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps prove the configuration is stable and understandable.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Cleanup
&lt;/h3&gt;

&lt;p&gt;This is the most overlooked part of manual testing.&lt;/p&gt;

&lt;p&gt;Questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did &lt;code&gt;terraform plan -destroy&lt;/code&gt; look correct?&lt;/li&gt;
&lt;li&gt;Did &lt;code&gt;terraform destroy&lt;/code&gt; succeed?&lt;/li&gt;
&lt;li&gt;Were any active resources left behind afterward?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cleanup is not just cost control.&lt;br&gt;&lt;br&gt;
It is part of whether the test process itself is trustworthy.&lt;/p&gt;
&lt;h2&gt;
  
  
  Provisioning Verification vs Functional Verification
&lt;/h2&gt;

&lt;p&gt;This difference became much clearer to me today.&lt;/p&gt;
&lt;h3&gt;
  
  
  Provisioning verification asks:
&lt;/h3&gt;

&lt;p&gt;“Did Terraform successfully create the infrastructure?”&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform validate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Functional verification asks:
&lt;/h3&gt;

&lt;p&gt;“Does the infrastructure actually do what it is supposed to do?”&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;opening the ALB URL&lt;/li&gt;
&lt;li&gt;running &lt;code&gt;curl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;checking the expected app response&lt;/li&gt;
&lt;li&gt;checking whether health checks pass&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need both.&lt;/p&gt;

&lt;p&gt;A Terraform apply can succeed while the application is still broken.&lt;br&gt;&lt;br&gt;
And an app can look reachable while the Terraform state or resource shape is still wrong.&lt;/p&gt;

&lt;p&gt;That is why a complete manual test must cover both layers.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Day 17 Test Environments
&lt;/h2&gt;

&lt;p&gt;To make the tests more realistic, I ran the manual test process against two environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;day_17/environments/dev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;day_17/environments/production&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both used the Day 17 reusable modules, but with different environment settings.&lt;/p&gt;

&lt;p&gt;That was useful because it let me compare whether behavior changed between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dev&lt;/li&gt;
&lt;li&gt;production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That kind of comparison matters, because many real issues only show up when environments differ in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;instance count&lt;/li&gt;
&lt;li&gt;naming&lt;/li&gt;
&lt;li&gt;defaults&lt;/li&gt;
&lt;li&gt;scaling assumptions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  My Actual Day 17 Results
&lt;/h2&gt;

&lt;p&gt;Here is what I found.&lt;/p&gt;
&lt;h3&gt;
  
  
  Dev environment
&lt;/h3&gt;

&lt;p&gt;Passes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform validate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform output&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;browser check showed &lt;code&gt;Hello from Day 17 Dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform plan&lt;/code&gt; returned clean after apply&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform plan -destroy&lt;/code&gt; showed the expected destroy plan&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform destroy&lt;/code&gt; completed successfully&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One useful cleanup nuance:&lt;br&gt;
after destroy, a broad EC2 query still showed an instance ID briefly. At first glance, that looked like a failed cleanup.&lt;/p&gt;

&lt;p&gt;But the real issue was the query, not Terraform.&lt;/p&gt;

&lt;p&gt;A better command filtered to active instance states only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ec2 describe-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="s2"&gt;"Name=tag:ManagedBy,Values=terraform"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="s2"&gt;"Name=instance-state-name,Values=pending,running,stopping,stopped"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"Reservations[*].Instances[*].InstanceId"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So destroy had actually worked.&lt;/p&gt;

&lt;p&gt;That was a great reminder that verifying cleanup also needs the right query logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production environment
&lt;/h3&gt;

&lt;p&gt;Passes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform validate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform output&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;browser and &lt;code&gt;curl&lt;/code&gt; checks returned &lt;code&gt;Hello from Day 17 Production&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform plan&lt;/code&gt; returned clean after apply&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform plan -destroy&lt;/code&gt; looked correct&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform destroy&lt;/code&gt; completed successfully&lt;/li&gt;
&lt;li&gt;post-destroy verification returned clean results for active resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That told me the same module structure behaved correctly in both environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cleanup Discipline Matters So Much
&lt;/h2&gt;

&lt;p&gt;This was one of the biggest takeaways of the day.&lt;/p&gt;

&lt;p&gt;As Brikman emphasizes in this chapter, a test is not really complete if you do not know how to tear the infrastructure down and verify that cleanup worked.&lt;/p&gt;

&lt;p&gt;If you test infrastructure in AWS without strong cleanup habits, you can end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;running EC2 instances&lt;/li&gt;
&lt;li&gt;load balancers&lt;/li&gt;
&lt;li&gt;security groups&lt;/li&gt;
&lt;li&gt;alarms&lt;/li&gt;
&lt;li&gt;orphaned resources&lt;/li&gt;
&lt;li&gt;unexpected costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So a manual test is not complete when the app loads once in the browser.&lt;/p&gt;

&lt;p&gt;A manual test is complete when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the infrastructure worked&lt;/li&gt;
&lt;li&gt;the results were recorded&lt;/li&gt;
&lt;li&gt;the cleanup was run&lt;/li&gt;
&lt;li&gt;the cleanup was verified&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the standard I want to keep carrying forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Manual Testing Gives You That Automation Alone Cannot
&lt;/h2&gt;

&lt;p&gt;Day 17 helped me see that manual testing gives a different kind of value than automation.&lt;/p&gt;

&lt;p&gt;Manual testing gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;context&lt;/li&gt;
&lt;li&gt;observation&lt;/li&gt;
&lt;li&gt;understanding&lt;/li&gt;
&lt;li&gt;discovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automation gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repetition&lt;/li&gt;
&lt;li&gt;speed&lt;/li&gt;
&lt;li&gt;consistency&lt;/li&gt;
&lt;li&gt;regression protection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means manual testing helps answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what should I automate next?&lt;/li&gt;
&lt;li&gt;what actually matters to check?&lt;/li&gt;
&lt;li&gt;what can go wrong in the real cloud environment?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why manual testing is still important, even when Terratest and other automation exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Main Takeaway
&lt;/h2&gt;

&lt;p&gt;The biggest lesson from Day 17 was this:&lt;/p&gt;

&lt;p&gt;good infrastructure testing is not just about proving that Terraform runs.&lt;br&gt;&lt;br&gt;
It is about proving that the system behaves correctly, stays consistent, and can be cleaned up safely.&lt;/p&gt;

&lt;p&gt;Manual testing helped me define that process clearly.&lt;/p&gt;

&lt;p&gt;And once that process is clear, automated testing becomes much more meaningful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code
&lt;/h2&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_17" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow My Journey
&lt;/h2&gt;

&lt;p&gt;This is Day 17 of my 30-Day Terraform Challenge.&lt;br&gt;&lt;br&gt;
See you on Day 18 🚀&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>devops</category>
      <category>infrastructureascode</category>
    </item>
    <item>
      <title>Creating Production-Grade Infrastructure with Terraform</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Tue, 07 Apr 2026 18:59:33 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/creating-production-grade-infrastructure-with-terraform-255</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/creating-production-grade-infrastructure-with-terraform-255</guid>
      <description>&lt;p&gt;Day 16 of my Terraform journey was less about creating new resources and more about improving how infrastructure is designed.&lt;/p&gt;

&lt;p&gt;The big lesson was this:&lt;/p&gt;

&lt;p&gt;Terraform code that works is not automatically production-grade.&lt;/p&gt;

&lt;p&gt;Production-grade infrastructure should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modular&lt;/li&gt;
&lt;li&gt;reliable&lt;/li&gt;
&lt;li&gt;secure&lt;/li&gt;
&lt;li&gt;observable&lt;/li&gt;
&lt;li&gt;maintainable&lt;/li&gt;
&lt;li&gt;testable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Day 16, I audited my earlier infrastructure against that checklist, refactored it into smaller modules, added better validation and observability, and tested it both manually and with Terratest.&lt;/p&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_16" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Production-Grade Checklist
&lt;/h2&gt;

&lt;p&gt;Here is the practical version of what I used.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Structure
&lt;/h3&gt;

&lt;p&gt;In practice, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no giant &lt;code&gt;main.tf&lt;/code&gt; doing everything&lt;/li&gt;
&lt;li&gt;small modules with one responsibility&lt;/li&gt;
&lt;li&gt;clear variables and outputs&lt;/li&gt;
&lt;li&gt;repeated logic centralized with &lt;code&gt;locals&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. Reliability
&lt;/h3&gt;

&lt;p&gt;In practice, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;safer replacements with &lt;code&gt;create_before_destroy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;proper health checks&lt;/li&gt;
&lt;li&gt;names that won’t collide&lt;/li&gt;
&lt;li&gt;designs that support rolling changes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3. Security
&lt;/h3&gt;

&lt;p&gt;In practice, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no secrets in Terraform code&lt;/li&gt;
&lt;li&gt;tighter security group rules&lt;/li&gt;
&lt;li&gt;safer state handling&lt;/li&gt;
&lt;li&gt;least-privilege thinking&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4. Observability
&lt;/h3&gt;

&lt;p&gt;In practice, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;consistent tagging&lt;/li&gt;
&lt;li&gt;alerts for important metrics&lt;/li&gt;
&lt;li&gt;basic operational visibility&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  5. Maintainability
&lt;/h3&gt;

&lt;p&gt;In practice, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;README files for modules&lt;/li&gt;
&lt;li&gt;pinned provider versions&lt;/li&gt;
&lt;li&gt;reusable module boundaries&lt;/li&gt;
&lt;li&gt;runnable examples&lt;/li&gt;
&lt;li&gt;tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That checklist turned Day 16 into an architecture refactor instead of just another provisioning exercise.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Refactored
&lt;/h2&gt;

&lt;p&gt;I split the infrastructure into smaller modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;modules/networking/alb&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;modules/cluster/asg-rolling-deploy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;modules/services/hello-world-app&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;examples/hello-world-app&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That made the design much easier to understand and test.&lt;/p&gt;
&lt;h2&gt;
  
  
  Refactor 1: Centralized Tagging
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-instance"&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;h3&gt;
  
  
  After
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;common_tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
      &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&lt;/span&gt;
      &lt;span class="nx"&gt;Project&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;
      &lt;span class="nx"&gt;Owner&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;team_name&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_tags&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;common_tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.cluster_name}-tg"&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;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;less repeated code&lt;/li&gt;
&lt;li&gt;consistent tagging&lt;/li&gt;
&lt;li&gt;easier filtering, billing, and operations&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Refactor 2: Variable Validation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  After
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Deployment environment"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"staging"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;var&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="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Environment must be dev, staging, or production."&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;And for instance type:&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"instance_type"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EC2 instance type for the app cluster"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^t[23]&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_type&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Instance type must be a t2 or t3 family type."&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;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bad values fail early&lt;/li&gt;
&lt;li&gt;module expectations are clearer&lt;/li&gt;
&lt;li&gt;future users make fewer mistakes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Refactor 3: Safer Replacements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_launch_template"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_launch_template"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;create_before_destroy&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;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;safer rolling replacements&lt;/li&gt;
&lt;li&gt;less disruption during changes&lt;/li&gt;
&lt;li&gt;better production behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Refactor 4: Tighter Security Rules
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"app_from_alb"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;referenced_security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb_security_group_id&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server_port&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server_port&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;instances are not exposed directly to the internet&lt;/li&gt;
&lt;li&gt;only the ALB can reach the app port&lt;/li&gt;
&lt;li&gt;networking intent is much safer and clearer&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Refactor 5: Basic Observability
&lt;/h2&gt;

&lt;p&gt;I added an SNS topic and CloudWatch CPU alarm:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_sns_topic"&lt;/span&gt; &lt;span class="s2"&gt;"alerts"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.cluster_name}-alerts"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudwatch_metric_alarm"&lt;/span&gt; &lt;span class="s2"&gt;"high_cpu"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;alarm_name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.cluster_name}-high-cpu"&lt;/span&gt;
  &lt;span class="nx"&gt;comparison_operator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GreaterThanThreshold"&lt;/span&gt;
  &lt;span class="nx"&gt;evaluation_periods&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;metric_name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CPUUtilization"&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS/EC2"&lt;/span&gt;
  &lt;span class="nx"&gt;period&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;
  &lt;span class="nx"&gt;statistic&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Average"&lt;/span&gt;
  &lt;span class="nx"&gt;threshold&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
  &lt;span class="nx"&gt;alarm_actions&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_sns_topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;dimensions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;AutoScalingGroupName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_autoscaling_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;infrastructure should not just run&lt;/li&gt;
&lt;li&gt;it should also tell you when it is unhealthy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I Tested It
&lt;/h2&gt;

&lt;p&gt;I tested Day 16 in two ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manual test
&lt;/h3&gt;

&lt;p&gt;I ran the example root module in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;day_16/examples/hello-world-app&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;applied the stack&lt;/li&gt;
&lt;li&gt;got the ALB DNS output&lt;/li&gt;
&lt;li&gt;opened it in the browser&lt;/li&gt;
&lt;li&gt;confirmed it returned:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Hello from Day 16&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then I destroyed everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automated test with Terratest
&lt;/h3&gt;

&lt;p&gt;I also added a Go test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestHelloWorldApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;terraformOptions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithDefaultRetryableErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TerraformDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"../examples/hello-world-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Vars&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
            &lt;span class="s"&gt;"cluster_name"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"test-cluster"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"instance_type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"t3.micro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"min_size"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"max_size"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"desired_capacity"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"environment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"server_text"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;"Hello from Day 16"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Destroy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitAndApply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;albDnsName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"alb_dns_name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"http://"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;albDnsName&lt;/span&gt;

    &lt;span class="n"&gt;http_helper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpGetWithRetryWithCustomValidation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statusCode&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello from Day 16"&lt;/span&gt;&lt;span class="p"&gt;)&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;h2&gt;
  
  
  Why Automated Testing Matters
&lt;/h2&gt;

&lt;p&gt;Manual testing is useful, but automated infrastructure testing gives you things manual testing cannot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repeatability&lt;/li&gt;
&lt;li&gt;faster regression checks&lt;/li&gt;
&lt;li&gt;confidence after refactors&lt;/li&gt;
&lt;li&gt;executable proof that the infrastructure behaves as expected&lt;/li&gt;
&lt;li&gt;easier team validation in CI/CD later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Manual testing told me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“it works right now”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Terratest moves closer to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“it keeps working when I change the code”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a big difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Main Takeaway
&lt;/h2&gt;

&lt;p&gt;Day 16 changed how I think about Terraform quality.&lt;/p&gt;

&lt;p&gt;The goal is not just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Can I provision this?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Can another engineer understand, trust, reuse, test, and operate this safely?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is what production-grade infrastructure really means.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code
&lt;/h2&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_16" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow My Journey
&lt;/h2&gt;

&lt;p&gt;This is Day 16 of my 30-Day Terraform Challenge.&lt;br&gt;
See you on Day 17 🚀&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>devops</category>
      <category>infrastructureascode</category>
    </item>
    <item>
      <title>Deploying Multi-Cloud Infrastructure with Terraform Modules</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Mon, 06 Apr 2026 20:52:18 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/deploying-multi-cloud-infrastructure-with-terraform-modules-3a1h</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/deploying-multi-cloud-infrastructure-with-terraform-modules-3a1h</guid>
      <description>&lt;p&gt;Day 15 of my Terraform journey was about moving from basic provider usage into more advanced provider patterns.&lt;/p&gt;

&lt;p&gt;This was the day where Terraform started to feel much more like a real infrastructure orchestration tool rather than just a way to create isolated cloud resources.&lt;/p&gt;

&lt;p&gt;The main focus was learning how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;build modules that accept provider configurations from their callers&lt;/li&gt;
&lt;li&gt;use multiple providers in one Terraform project&lt;/li&gt;
&lt;li&gt;manage Docker locally with Terraform&lt;/li&gt;
&lt;li&gt;prepare an AWS EKS + Kubernetes deployment using Terraform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_15" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Topic Matters
&lt;/h2&gt;

&lt;p&gt;In real infrastructure, one provider configuration is often not enough.&lt;/p&gt;

&lt;p&gt;You may need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one AWS region for primary infrastructure&lt;/li&gt;
&lt;li&gt;another AWS region for replicas&lt;/li&gt;
&lt;li&gt;a separate AWS account for production&lt;/li&gt;
&lt;li&gt;Kubernetes to deploy workloads after AWS creates the cluster&lt;/li&gt;
&lt;li&gt;Docker locally for quick testing before cloud deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the real question is no longer just:&lt;/p&gt;

&lt;p&gt;“How do I use a provider?”&lt;/p&gt;

&lt;p&gt;It becomes:&lt;/p&gt;

&lt;p&gt;“How do I pass the right provider configuration into the right part of my Terraform code?”&lt;/p&gt;

&lt;p&gt;That is what Day 15 was really about.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Core Rule: Modules Should Not Define Their Own Providers
&lt;/h2&gt;

&lt;p&gt;This was the biggest lesson of the day.&lt;/p&gt;

&lt;p&gt;A reusable module should &lt;strong&gt;not&lt;/strong&gt; hardcode provider blocks inside itself.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because a reusable module should not decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which region to deploy into&lt;/li&gt;
&lt;li&gt;which account to authenticate to&lt;/li&gt;
&lt;li&gt;which alias to use&lt;/li&gt;
&lt;li&gt;which credentials or access path the caller should depend on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those decisions belong to the &lt;strong&gt;root module&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the child module defines its own providers, it becomes harder to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reuse it across regions&lt;/li&gt;
&lt;li&gt;reuse it across accounts&lt;/li&gt;
&lt;li&gt;test it cleanly&lt;/li&gt;
&lt;li&gt;compose it into larger infrastructure setups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the better pattern is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the child module declares which providers it expects&lt;/li&gt;
&lt;li&gt;the root module creates the real provider configurations&lt;/li&gt;
&lt;li&gt;the root module passes them in explicitly&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The &lt;code&gt;configuration_aliases&lt;/code&gt; Pattern
&lt;/h2&gt;

&lt;p&gt;Inside a reusable module, Terraform needs to know which aliased provider configurations the module expects.&lt;/p&gt;

&lt;p&gt;That is where &lt;code&gt;configuration_aliases&lt;/code&gt; comes in.&lt;/p&gt;

&lt;p&gt;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="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;
      &lt;span class="nx"&gt;configuration_aliases&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replica&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&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;This tells Terraform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this module expects two aliased AWS provider configurations&lt;/li&gt;
&lt;li&gt;one named &lt;code&gt;aws.primary&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;one named &lt;code&gt;aws.replica&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is important because the child module is not defining the providers itself.&lt;br&gt;
It is declaring the provider interfaces it expects the caller to supply.&lt;/p&gt;

&lt;p&gt;That was the cleanest way for me to understand it:&lt;br&gt;
the module is declaring its provider dependencies, not its provider setup.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wiring Providers into a Module with the &lt;code&gt;providers&lt;/code&gt; Map
&lt;/h2&gt;

&lt;p&gt;Once the child module declares what it expects, the root module passes the actual provider configurations using the &lt;code&gt;providers&lt;/code&gt; map.&lt;/p&gt;

&lt;p&gt;Example root module:&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;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;alias&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"primary"&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-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;alias&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"replica"&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="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"multi_region_app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../../modules/multi-region-app"&lt;/span&gt;
  &lt;span class="nx"&gt;app_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-app"&lt;/span&gt;

  &lt;span class="nx"&gt;providers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replica&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replica&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;This part is the key wiring step.&lt;/p&gt;

&lt;p&gt;The root module is saying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when the child module asks for &lt;code&gt;aws.primary&lt;/code&gt;, give it this provider&lt;/li&gt;
&lt;li&gt;when the child module asks for &lt;code&gt;aws.replica&lt;/code&gt;, give it this other provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That keeps the responsibilities clean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;child module = reusable logic&lt;/li&gt;
&lt;li&gt;root module = environment-specific provider wiring&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Multi-Region Module Lab
&lt;/h2&gt;

&lt;p&gt;To make the concept practical, I built a reusable module that deploys S3 buckets in two regions.&lt;/p&gt;

&lt;p&gt;Inside the child module:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one bucket uses &lt;code&gt;provider = aws.primary&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the other uses &lt;code&gt;provider = aws.replica&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the root module:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the primary AWS provider points to one region&lt;/li&gt;
&lt;li&gt;the replica AWS provider points to another region&lt;/li&gt;
&lt;li&gt;both are passed into the module with the &lt;code&gt;providers&lt;/code&gt; map&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gave me a very concrete understanding of how Terraform decides where each resource should go.&lt;/p&gt;

&lt;p&gt;The important distinction here is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;provider&lt;/code&gt; is used on individual resources and data sources&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;providers&lt;/code&gt; is used when calling a module&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is one of the most important details from Day 15.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker as a Quick Multi-Provider Win
&lt;/h2&gt;

&lt;p&gt;Before getting into EKS, I used the Docker provider for a simpler live example.&lt;/p&gt;

&lt;p&gt;This was a great bridge between theory and the more expensive AWS/Kubernetes setup.&lt;/p&gt;

&lt;p&gt;The Terraform configuration used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Docker provider&lt;/li&gt;
&lt;li&gt;a Docker image resource&lt;/li&gt;
&lt;li&gt;a Docker container resource&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example pattern:&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;provider&lt;/span&gt; &lt;span class="s2"&gt;"docker"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"docker_image"&lt;/span&gt; &lt;span class="s2"&gt;"nginx"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nginx:latest"&lt;/span&gt;
  &lt;span class="nx"&gt;keep_locally&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"docker_container"&lt;/span&gt; &lt;span class="s2"&gt;"nginx"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;docker_image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nginx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image_id&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-nginx"&lt;/span&gt;

  &lt;span class="nx"&gt;ports&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;internal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;external&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&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;After applying it, I confirmed nginx was serving locally on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http://localhost:8080&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was a very useful reminder that Terraform is not just for cloud resources.&lt;br&gt;
It can also manage local platform resources through providers.&lt;/p&gt;

&lt;p&gt;In this case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform managed Docker resources&lt;/li&gt;
&lt;li&gt;Docker handled the actual container runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  EKS + Kubernetes: The Real Multi-Provider Pattern
&lt;/h2&gt;

&lt;p&gt;The most advanced part of Day 15 was preparing an EKS + Kubernetes deployment.&lt;/p&gt;

&lt;p&gt;This is where Terraform starts using multiple provider types together in a realistic way.&lt;/p&gt;

&lt;p&gt;The basic pattern is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS provider creates the cloud infrastructure&lt;/li&gt;
&lt;li&gt;Kubernetes provider connects to the cluster and deploys workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the EKS lab, Terraform was prepared to create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a VPC&lt;/li&gt;
&lt;li&gt;public subnets&lt;/li&gt;
&lt;li&gt;an EKS cluster&lt;/li&gt;
&lt;li&gt;a managed node group&lt;/li&gt;
&lt;li&gt;a Kubernetes namespace&lt;/li&gt;
&lt;li&gt;a Kubernetes Deployment&lt;/li&gt;
&lt;li&gt;a Kubernetes Service of type &lt;code&gt;LoadBalancer&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the real Day 15 milestone:&lt;br&gt;
one Terraform project managing both infrastructure and the application platform on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Stopped at &lt;code&gt;terraform plan&lt;/code&gt; for EKS
&lt;/h2&gt;

&lt;p&gt;I prepared the full EKS configuration and validated that Terraform could generate a real plan.&lt;/p&gt;

&lt;p&gt;The plan showed that Terraform was ready to create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS networking&lt;/li&gt;
&lt;li&gt;EKS resources&lt;/li&gt;
&lt;li&gt;node group resources&lt;/li&gt;
&lt;li&gt;Kubernetes workload resources after the cluster became available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, I intentionally stopped before &lt;code&gt;terraform apply&lt;/code&gt; for the EKS part.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because EKS is the first part of the challenge that can use credits more noticeably due to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EKS control plane charges&lt;/li&gt;
&lt;li&gt;worker nodes&lt;/li&gt;
&lt;li&gt;load balancer resources&lt;/li&gt;
&lt;li&gt;supporting AWS infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I made a practical engineering decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;complete the Docker lab live&lt;/li&gt;
&lt;li&gt;complete the EKS configuration and plan&lt;/li&gt;
&lt;li&gt;avoid unnecessary credit consumption by not applying the cluster today&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think that still reflects an important real-world skill:&lt;br&gt;
sometimes the right infrastructure decision is not just “can I deploy it?”&lt;br&gt;
but also “should I deploy it right now?”&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Day 15 made a few things much clearer for me:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Root modules should own provider configuration
&lt;/h3&gt;

&lt;p&gt;Reusable modules should stay flexible and let the caller decide region, account, and authentication strategy.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;configuration_aliases&lt;/code&gt; is the missing link
&lt;/h3&gt;

&lt;p&gt;It tells Terraform which aliased providers a module expects.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The &lt;code&gt;providers&lt;/code&gt; map is how modules get wired
&lt;/h3&gt;

&lt;p&gt;This is what connects root-module providers to child-module expectations.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Terraform can orchestrate multiple platforms
&lt;/h3&gt;

&lt;p&gt;A single Terraform project can manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes Terraform much more powerful than a simple cloud provisioning tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Docker is a great stepping stone before Kubernetes
&lt;/h3&gt;

&lt;p&gt;It gives a quick, low-cost way to understand provider-based container management before moving into a much larger EKS deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Main Takeaway
&lt;/h2&gt;

&lt;p&gt;The biggest shift for me today was understanding that multi-provider Terraform is really about separation of responsibilities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;child modules define reusable infrastructure logic&lt;/li&gt;
&lt;li&gt;root modules define the real provider wiring&lt;/li&gt;
&lt;li&gt;each provider handles a specific platform&lt;/li&gt;
&lt;li&gt;Terraform coordinates the whole thing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once that clicked, the Day 15 material became much easier to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code
&lt;/h2&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_15" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow My Journey
&lt;/h2&gt;

&lt;p&gt;This is Day 15 of my 30-Day Terraform Challenge.&lt;br&gt;
See you on Day 16 🚀&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>docker</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Getting Started with Multiple Providers in Terraform</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Sat, 04 Apr 2026 19:55:49 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/getting-started-with-multiple-providers-in-terraform-3g39</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/getting-started-with-multiple-providers-in-terraform-3g39</guid>
      <description>&lt;p&gt;Day 14 of my Terraform journey was about understanding one of the most important Terraform building blocks: providers.&lt;/p&gt;

&lt;p&gt;Until now, most of my Terraform configurations had only used one provider configuration at a time. That was enough for simple examples, but real infrastructure often spans:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple AWS regions&lt;/li&gt;
&lt;li&gt;multiple AWS accounts&lt;/li&gt;
&lt;li&gt;and sometimes multiple cloud platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today I learned how Terraform providers actually work under the hood, how Terraform installs and pins them, and how provider aliases make multi-region and multi-account setups possible.&lt;/p&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_14" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What Is a Provider in Terraform?
&lt;/h2&gt;

&lt;p&gt;A provider is a plugin that Terraform uses to communicate with an external platform such as AWS, Azure, or Google Cloud.&lt;/p&gt;

&lt;p&gt;A simple way to think about it is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform Core handles:

&lt;ul&gt;
&lt;li&gt;reading your &lt;code&gt;.tf&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;building the plan&lt;/li&gt;
&lt;li&gt;understanding dependencies&lt;/li&gt;
&lt;li&gt;managing state&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;the provider handles:

&lt;ul&gt;
&lt;li&gt;the real API calls to AWS or another platform&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when I declare something like:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"example"&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-example-bucket"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform Core understands the structure of the resource, but the AWS provider is the part that actually knows how to call AWS and create that bucket.&lt;/p&gt;

&lt;p&gt;That was one of the biggest mindset shifts for me today: providers are not just configuration details. They are what connect Terraform to the outside world.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Terraform Installs Providers
&lt;/h2&gt;

&lt;p&gt;Terraform installs providers when you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During initialization, Terraform reads the &lt;code&gt;required_providers&lt;/code&gt; block and downloads the provider binaries from the Terraform Registry.&lt;/p&gt;

&lt;p&gt;A production-friendly pattern looks like this:&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;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0.0"&lt;/span&gt;

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&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;This tells Terraform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use the AWS provider&lt;/li&gt;
&lt;li&gt;download it from &lt;code&gt;hashicorp/aws&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;allow versions in the &lt;code&gt;5.x&lt;/code&gt; range, but not &lt;code&gt;6.0&lt;/code&gt; or above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The version constraint:&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;version&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use any version &lt;code&gt;&amp;gt;= 5.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;but &lt;code&gt;&amp;lt; 6.0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That helps avoid unexpected breaking changes from a future major version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Provider Version Pinning Matters
&lt;/h2&gt;

&lt;p&gt;This was one of the clearest best-practice lessons from today.&lt;/p&gt;

&lt;p&gt;If provider versions are not controlled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform init&lt;/code&gt; on a different machine may install a different provider version&lt;/li&gt;
&lt;li&gt;that can cause unexpected plan changes&lt;/li&gt;
&lt;li&gt;or even break a working configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pinning provider versions makes the setup more stable and repeatable.&lt;/p&gt;

&lt;p&gt;In other words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no version pinning = more surprises&lt;/li&gt;
&lt;li&gt;version pinning = more predictability&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;.terraform.lock.hcl&lt;/code&gt; Does
&lt;/h2&gt;

&lt;p&gt;After running &lt;code&gt;terraform init&lt;/code&gt;, Terraform creates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.terraform.lock.hcl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file records the exact provider versions Terraform selected.&lt;/p&gt;

&lt;p&gt;For example, in my Day 14 lab it recorded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the provider source&lt;/li&gt;
&lt;li&gt;the exact provider version&lt;/li&gt;
&lt;li&gt;the version constraints&lt;/li&gt;
&lt;li&gt;package hashes used to verify the provider binaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because it keeps provider installation consistent across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;my machine&lt;/li&gt;
&lt;li&gt;teammates’ machines&lt;/li&gt;
&lt;li&gt;CI/CD systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why &lt;code&gt;.terraform.lock.hcl&lt;/code&gt; should be committed to version control.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; store secrets.&lt;br&gt;
It stores dependency lock information.&lt;/p&gt;

&lt;p&gt;That was an important clarification for me.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using a Single Provider
&lt;/h2&gt;

&lt;p&gt;A simple provider configuration looks like this:&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;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&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-east-1"&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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS is the provider&lt;/li&gt;
&lt;li&gt;resources without any explicit override will use &lt;code&gt;us-east-1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That default provider configuration applies automatically to all AWS resources in the configuration unless told otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with Multiple Copies of the Same Provider
&lt;/h2&gt;

&lt;p&gt;The most practical concept I learned today was provider aliases.&lt;/p&gt;

&lt;p&gt;If I want Terraform to deploy some resources in one region and other resources in another region, I need multiple configurations of the same provider.&lt;/p&gt;

&lt;p&gt;Here is the pattern:&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;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&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-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;alias&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us_west"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a default AWS provider in &lt;code&gt;us-east-1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;an aliased AWS provider named &lt;code&gt;us_west&lt;/code&gt; in &lt;code&gt;us-west-2&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any resource that should use the secondary region must reference that aliased provider explicitly.&lt;/p&gt;

&lt;p&gt;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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"primary"&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-app-primary-bucket"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"replica"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;us_west&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-app-replica-bucket"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;primary&lt;/code&gt; goes to the default region&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replica&lt;/code&gt; goes to the aliased provider region&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Multi-Region Day 14 Lab
&lt;/h2&gt;

&lt;p&gt;For the hands-on part, I built a multi-region AWS example using provider aliases.&lt;/p&gt;

&lt;p&gt;The lab included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a primary S3 bucket in &lt;code&gt;us-east-1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a replica S3 bucket in &lt;code&gt;us-west-2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;IAM resources for replication&lt;/li&gt;
&lt;li&gt;an S3 replication configuration between the two buckets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gave me a practical example of how multiple provider configurations can work together in one Terraform project.&lt;/p&gt;

&lt;p&gt;This was much more useful than just reading about aliases in theory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Account Pattern with &lt;code&gt;assume_role&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I also reviewed the multi-account pattern.&lt;/p&gt;

&lt;p&gt;For multiple AWS accounts, Terraform can use aliased providers with &lt;code&gt;assume_role&lt;/code&gt;:&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;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&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-east-1"&lt;/span&gt;
  &lt;span class="nx"&gt;alias&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;

  &lt;span class="nx"&gt;assume_role&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;role_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::111111111111:role/TerraformDeployRole"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&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-east-1"&lt;/span&gt;
  &lt;span class="nx"&gt;alias&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"staging"&lt;/span&gt;

  &lt;span class="nx"&gt;assume_role&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;role_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::222222222222:role/TerraformDeployRole"&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;I did not apply this part live because I do not yet have multiple AWS account roles set up, but I documented the configuration pattern as part of the lesson.&lt;/p&gt;

&lt;p&gt;The important idea is that aliases are not only for regions.&lt;br&gt;
They are also for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple accounts&lt;/li&gt;
&lt;li&gt;different access contexts&lt;/li&gt;
&lt;li&gt;more advanced module usage later on&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Main Takeaway
&lt;/h2&gt;

&lt;p&gt;Day 14 made Terraform providers feel much less magical.&lt;/p&gt;

&lt;p&gt;Before today, I mostly saw providers as something you add near the top of a file and then move on.&lt;/p&gt;

&lt;p&gt;Now I understand that providers control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which platform Terraform talks to&lt;/li&gt;
&lt;li&gt;where infrastructure gets deployed&lt;/li&gt;
&lt;li&gt;which account or region is used&lt;/li&gt;
&lt;li&gt;which provider version is installed&lt;/li&gt;
&lt;li&gt;how multi-region and multi-account architectures become possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest lessons for me were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;always pin provider versions&lt;/li&gt;
&lt;li&gt;always understand what &lt;code&gt;terraform init&lt;/code&gt; is doing&lt;/li&gt;
&lt;li&gt;commit &lt;code&gt;.terraform.lock.hcl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;use aliases carefully when working across regions or accounts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the foundation for more advanced provider and module work coming next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code
&lt;/h2&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_14" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow My Journey
&lt;/h2&gt;

&lt;p&gt;This is Day 14 of my 30-Day Terraform Challenge.&lt;/p&gt;

&lt;p&gt;See you on Day 15 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>beginners</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>How to Handle Sensitive Data Securely in Terraform</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Sat, 04 Apr 2026 02:32:18 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/how-to-handle-sensitive-data-securely-in-terraform-5fgn</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/how-to-handle-sensitive-data-securely-in-terraform-5fgn</guid>
      <description>&lt;p&gt;Day 13 of my Terraform journey focused on one of the most important topics in real infrastructure work: secrets.&lt;/p&gt;

&lt;p&gt;Every serious deployment eventually needs sensitive values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;database passwords&lt;/li&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;tokens&lt;/li&gt;
&lt;li&gt;TLS material&lt;/li&gt;
&lt;li&gt;provider credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The challenge is not just using those secrets. The challenge is making sure they do not leak into places they should never be.&lt;/p&gt;

&lt;p&gt;Terraform makes infrastructure easy to define, but if you are careless with secrets, they can leak through your code, your terminal output, your Git history, and even your state file.&lt;/p&gt;

&lt;p&gt;This post is the guide I wish I had before learning this lesson.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Secrets Leak in Terraform
&lt;/h2&gt;

&lt;p&gt;There are three major ways secrets leak in Terraform.&lt;/p&gt;

&lt;p&gt;If you understand these clearly, you will avoid most beginner and intermediate Terraform security mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leak Path 1: Hardcoded in &lt;code&gt;.tf&lt;/code&gt; Files
&lt;/h2&gt;

&lt;p&gt;This is the most obvious mistake.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"super-secret-password"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this is bad:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the password is now in source control&lt;/li&gt;
&lt;li&gt;if you run &lt;code&gt;git add&lt;/code&gt;, the secret is part of Git history&lt;/li&gt;
&lt;li&gt;even if you delete it later, the old commit still contains it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once a secret is committed, you must treat it as compromised.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better
&lt;/h3&gt;

&lt;p&gt;Do not write the secret directly in Terraform code.&lt;/p&gt;

&lt;p&gt;Instead, fetch it from a secret store such as AWS Secrets Manager.&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;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_secretsmanager_secret"&lt;/span&gt; &lt;span class="s2"&gt;"db_credentials"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod/db/credentials"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_secretsmanager_secret_version"&lt;/span&gt; &lt;span class="s2"&gt;"db_credentials"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;secret_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_secretsmanager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db_credentials&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsondecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_secretsmanager_secret_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret_string&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt;
  &lt;span class="nx"&gt;engine_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"8.0"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t3.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;db_name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"appdb"&lt;/span&gt;

  &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;allocated_storage&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="nx"&gt;skip_final_snapshot&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the secret out of your &lt;code&gt;.tf&lt;/code&gt; files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leak Path 2: Secret Stored as a Variable Default
&lt;/h2&gt;

&lt;p&gt;This one looks cleaner, but it is still wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"db_password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"super-secret-password"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this is bad:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the secret is still in source control&lt;/li&gt;
&lt;li&gt;it still ends up in the file&lt;/li&gt;
&lt;li&gt;it is just hidden behind a variable name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A variable default is not a secure secret store.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better
&lt;/h3&gt;

&lt;p&gt;If a variable may carry a secret:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;do not give it a default&lt;/li&gt;
&lt;li&gt;mark it sensitive
&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"db_password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Database administrator password"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then pass the value from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;your CI/CD secret store&lt;/li&gt;
&lt;li&gt;a secure runtime mechanism&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps reduce accidental exposure in code and CLI output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leak Path 3: Plaintext in Terraform State
&lt;/h2&gt;

&lt;p&gt;This is the most important and most overlooked problem.&lt;/p&gt;

&lt;p&gt;Even if you do everything else correctly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no hardcoded secrets&lt;/li&gt;
&lt;li&gt;no secret defaults&lt;/li&gt;
&lt;li&gt;values pulled from Secrets Manager&lt;/li&gt;
&lt;li&gt;outputs marked as sensitive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Terraform can still store secret values in &lt;code&gt;terraform.tfstate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;anyone with access to the state file may be able to read secrets&lt;/li&gt;
&lt;li&gt;protecting state is just as important as protecting code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what separates security-aware Terraform usage from surface-level secret handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Secrets Manager Integration Pattern
&lt;/h2&gt;

&lt;p&gt;For Day 13, I used AWS Secrets Manager as the secret source.&lt;/p&gt;

&lt;p&gt;First, create the secret manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager create-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"prod/db/credentials"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-string&lt;/span&gt; &lt;span class="s1"&gt;'{"username":"dbadmin","password":"your-secure-password-here"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then read it in 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;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_secretsmanager_secret"&lt;/span&gt; &lt;span class="s2"&gt;"db_credentials"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod/db/credentials"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_secretsmanager_secret_version"&lt;/span&gt; &lt;span class="s2"&gt;"db_credentials"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;secret_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_secretsmanager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db_credentials&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsondecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_secretsmanager_secret_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret_string&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;Then use it inside your resource:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt;
  &lt;span class="nx"&gt;engine_version&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"8.0"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t3.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;allocated_storage&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="nx"&gt;db_name&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"appdb"&lt;/span&gt;
  &lt;span class="nx"&gt;username&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;skip_final_snapshot&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this is better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the secret is not hardcoded in Terraform&lt;/li&gt;
&lt;li&gt;the secret stays centralized in Secrets Manager&lt;/li&gt;
&lt;li&gt;secret updates can be managed outside the codebase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this still does &lt;strong&gt;not&lt;/strong&gt; eliminate the state-file risk&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  HashiCorp Vault Integration Pattern
&lt;/h2&gt;

&lt;p&gt;For teams already using Vault, the pattern is similar.&lt;/p&gt;

&lt;p&gt;Instead of pulling from AWS Secrets Manager, Terraform can read from Vault.&lt;/p&gt;

&lt;p&gt;Example pattern:&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;data&lt;/span&gt; &lt;span class="s2"&gt;"vault_generic_secret"&lt;/span&gt; &lt;span class="s2"&gt;"db_credentials"&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;"secret/data/prod/db"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vault_generic_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;db_password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vault_generic_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"password"&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;Then use those values in resources just like any other data source.&lt;/p&gt;

&lt;p&gt;Why Vault is useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;strong secret lifecycle management&lt;/li&gt;
&lt;li&gt;centralized policy control&lt;/li&gt;
&lt;li&gt;dynamic secrets in more advanced setups&lt;/li&gt;
&lt;li&gt;useful when teams already standardize on Vault across environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same warning still applies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if Terraform consumes the value in a resource, the secret can still end up in state&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;sensitive = true&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Terraform provides a &lt;code&gt;sensitive = true&lt;/code&gt; flag for variables and outputs.&lt;/p&gt;

&lt;p&gt;Example variable:&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"db_password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Database administrator password"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&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;output&lt;/span&gt; &lt;span class="s2"&gt;"db_connection_string"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql://${aws_db_instance.example.username}@${aws_db_instance.example.endpoint}"&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prevents Terraform from printing the raw value in plan/apply output&lt;/li&gt;
&lt;li&gt;helps keep logs and terminal output cleaner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it does &lt;strong&gt;not&lt;/strong&gt; do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it does not encrypt the secret&lt;/li&gt;
&lt;li&gt;it does not remove it from state&lt;/li&gt;
&lt;li&gt;it does not make a bad secret-handling design safe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;sensitive = true&lt;/code&gt; is helpful, but it is not enough on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provider Credentials: Use Environment Variables
&lt;/h2&gt;

&lt;p&gt;Never put provider credentials directly in Terraform code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&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-east-1"&lt;/span&gt;
  &lt;span class="nx"&gt;access_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AKIA..."&lt;/span&gt;
  &lt;span class="nx"&gt;secret_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Better
&lt;/h3&gt;

&lt;p&gt;Use environment variables:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-access-key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-secret-key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much safer because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;credentials stay out of &lt;code&gt;.tf&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;CI/CD systems can inject them securely&lt;/li&gt;
&lt;li&gt;they are easier to rotate than hardcoded values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In real environments, even better options are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IAM roles&lt;/li&gt;
&lt;li&gt;OIDC&lt;/li&gt;
&lt;li&gt;short-lived credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  State File Security Checklist
&lt;/h2&gt;

&lt;p&gt;Because secrets can land in state, protecting the state file is mandatory.&lt;/p&gt;

&lt;p&gt;Use this checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;store state remotely, not just on a laptop&lt;/li&gt;
&lt;li&gt;enable S3 encryption&lt;/li&gt;
&lt;li&gt;enable bucket versioning&lt;/li&gt;
&lt;li&gt;block all public access&lt;/li&gt;
&lt;li&gt;restrict bucket access with least-privilege IAM&lt;/li&gt;
&lt;li&gt;enable DynamoDB locking&lt;/li&gt;
&lt;li&gt;keep state files out of Git&lt;/li&gt;
&lt;li&gt;treat anyone with state access as having potential secret access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good backend pattern looks like this:&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;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;"your-terraform-state-bucket"&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;"production/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-east-1"&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-state-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;h2&gt;
  
  
  &lt;code&gt;.gitignore&lt;/code&gt; Template for Terraform Projects
&lt;/h2&gt;

&lt;p&gt;Every Terraform project should ignore these files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Terraform
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.backup
*.tfvars
override.tf
override.tf.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduces the chance of accidentally committing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local cache files&lt;/li&gt;
&lt;li&gt;state files&lt;/li&gt;
&lt;li&gt;variable files with secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Least-Privilege IAM for the State Bucket
&lt;/h2&gt;

&lt;p&gt;Your state bucket should not be wide open.&lt;/p&gt;

&lt;p&gt;At minimum, access should be limited to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the IAM user/role that runs Terraform&lt;/li&gt;
&lt;li&gt;only the necessary S3 actions&lt;/li&gt;
&lt;li&gt;only the specific state bucket and key paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple principle is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allow read/write to the Terraform state bucket&lt;/li&gt;
&lt;li&gt;allow lock-table access to the DynamoDB table&lt;/li&gt;
&lt;li&gt;deny everyone else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact policy will vary by environment, but the mindset is the important part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;state is sensitive&lt;/li&gt;
&lt;li&gt;access to state should be tightly controlled&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Main Takeaway
&lt;/h2&gt;

&lt;p&gt;Day 13 changed how I think about Terraform security.&lt;/p&gt;

&lt;p&gt;The big lesson was not just:&lt;br&gt;
“don’t hardcode passwords.”&lt;/p&gt;

&lt;p&gt;The real lesson was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;secrets can leak in code&lt;/li&gt;
&lt;li&gt;secrets can leak through defaults&lt;/li&gt;
&lt;li&gt;secrets can still leak through state even when the code looks clean&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means secure Terraform work requires both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better secret input patterns&lt;/li&gt;
&lt;li&gt;better state protection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using AWS Secrets Manager and &lt;code&gt;sensitive = true&lt;/code&gt; is a strong start.&lt;/p&gt;

&lt;p&gt;But the deeper lesson is this:&lt;br&gt;
if you protect the code but ignore the state file, you have not really solved the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code
&lt;/h2&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_13" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow My Journey
&lt;/h2&gt;

&lt;p&gt;This is Day 13 of my 30-Day Terraform Challenge.&lt;/p&gt;

&lt;p&gt;See you on Day 14 🚀&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>terraform</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Mastering Zero-Downtime Deployments with Terraform</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Sat, 04 Apr 2026 00:50:09 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/mastering-zero-downtime-deployments-with-terraform-1bi5</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/mastering-zero-downtime-deployments-with-terraform-1bi5</guid>
      <description>&lt;p&gt;Day 12 of my Terraform journey focused on one of the most practical infrastructure problems: how to deploy changes without taking an application offline.&lt;/p&gt;

&lt;p&gt;This is the kind of Terraform topic that matters immediately in real systems. It is one thing to provision infrastructure. It is another thing entirely to update that infrastructure safely while users are still depending on it.&lt;/p&gt;

&lt;p&gt;Today I worked through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;why Terraform’s default replacement behavior can cause downtime&lt;/li&gt;
&lt;li&gt;how &lt;code&gt;create_before_destroy&lt;/code&gt; changes the order of operations&lt;/li&gt;
&lt;li&gt;why Auto Scaling Group naming becomes a problem&lt;/li&gt;
&lt;li&gt;how blue/green deployment works with a load balancer&lt;/li&gt;
&lt;li&gt;how to test the difference between a rolling replacement and an atomic traffic switch&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Default Terraform Can Cause Downtime
&lt;/h2&gt;

&lt;p&gt;By default, if Terraform needs to replace a resource that cannot be updated in place, it often destroys the old resource before creating the new one.&lt;/p&gt;

&lt;p&gt;For a live service, that is risky.&lt;/p&gt;

&lt;p&gt;In a setup using an Auto Scaling Group and a load balancer, the default replacement flow can look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;old ASG is destroyed&lt;/li&gt;
&lt;li&gt;instances are terminated&lt;/li&gt;
&lt;li&gt;traffic has nowhere healthy to go&lt;/li&gt;
&lt;li&gt;new ASG is created&lt;/li&gt;
&lt;li&gt;new instances boot and pass health checks&lt;/li&gt;
&lt;li&gt;the app finally comes back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gap between old capacity disappearing and new capacity becoming healthy is the downtime window.&lt;/p&gt;

&lt;p&gt;For production workloads, that is not acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: &lt;code&gt;create_before_destroy&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Terraform provides a lifecycle rule for this exact problem:&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;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;create_before_destroy&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This changes the replacement order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create the new resource first&lt;/li&gt;
&lt;li&gt;let the replacement become ready&lt;/li&gt;
&lt;li&gt;destroy the old resource only after that&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my Day 12 lab, I used this on both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;launch templates&lt;/li&gt;
&lt;li&gt;Auto Scaling Groups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That let Terraform attempt a safer rolling replacement instead of immediately dropping the old infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ASG Naming Problem
&lt;/h2&gt;

&lt;p&gt;There is an important catch.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;create_before_destroy = true&lt;/code&gt; is enabled, Terraform needs the old and new versions of the resource to exist at the same time for a short period.&lt;/p&gt;

&lt;p&gt;That becomes a problem with Auto Scaling Groups because AWS does not allow two ASGs with the same name to exist at once.&lt;/p&gt;

&lt;p&gt;If the name is hardcoded, the deployment fails even though the lifecycle rule is correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solving It with &lt;code&gt;random_id&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The solution is to give the replacement ASG a unique name.&lt;/p&gt;

&lt;p&gt;I used a &lt;code&gt;random_id&lt;/code&gt; resource tied to the application version:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_id"&lt;/span&gt; &lt;span class="s2"&gt;"rolling"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;keepers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_version&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;byte_length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I used that value in the rolling launch template and ASG naming.&lt;/p&gt;

&lt;p&gt;That solved a real deployment problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the old ASG could stay alive&lt;/li&gt;
&lt;li&gt;the new ASG could be created beside it&lt;/li&gt;
&lt;li&gt;Terraform could shift traffic without a name collision&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was one of the most important practical lessons of the day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rolling Zero-Downtime Deployment
&lt;/h2&gt;

&lt;p&gt;To test the rolling replacement flow, I deployed a versioned response behind an Application Load Balancer.&lt;/p&gt;

&lt;p&gt;The app initially returned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Hello World v1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I changed the Terraform input to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Hello World v2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and ran &lt;code&gt;terraform apply&lt;/code&gt; again while continuously hitting the ALB.&lt;/p&gt;

&lt;p&gt;The goal was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;traffic should continue working throughout the apply&lt;/li&gt;
&lt;li&gt;at some point the response should switch from &lt;code&gt;v1&lt;/code&gt; to &lt;code&gt;v2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;there should be no outage during the transition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the essence of a zero-downtime rolling update.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Observed During Testing
&lt;/h2&gt;

&lt;p&gt;One important real-world lesson came up during testing: zero-downtime often requires temporary extra capacity.&lt;/p&gt;

&lt;p&gt;Because the old and new ASGs briefly coexist, AWS may need to run both old and new instances at the same time. On a small AWS quota, that can hit vCPU limits.&lt;/p&gt;

&lt;p&gt;That happened in my lab, so I had to test the rolling and blue/green parts separately instead of running everything at once.&lt;/p&gt;

&lt;p&gt;That is actually a useful lesson in itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;zero-downtime is safer&lt;/li&gt;
&lt;li&gt;but it can temporarily cost more capacity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Blue/Green Deployment
&lt;/h2&gt;

&lt;p&gt;I also implemented a blue/green deployment pattern.&lt;/p&gt;

&lt;p&gt;Instead of gradually replacing one environment, blue/green keeps two separate environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;blue = currently live&lt;/li&gt;
&lt;li&gt;green = next version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traffic is controlled by the load balancer listener rule:&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;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"forward"&lt;/span&gt;
  &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_environment&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;green&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means traffic switching is not done by rebuilding the environment live. It is done by changing a single routing decision.&lt;/p&gt;

&lt;p&gt;In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;blue served traffic first&lt;/li&gt;
&lt;li&gt;I changed &lt;code&gt;active_environment&lt;/code&gt; to &lt;code&gt;green&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ran &lt;code&gt;terraform apply&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;traffic switched to green&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This made the cutover feel much cleaner and more atomic than a normal replacement.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Testing Detail That Matters
&lt;/h2&gt;

&lt;p&gt;During the blue/green test, I initially thought the switch had not worked because the browser kept showing the old environment.&lt;/p&gt;

&lt;p&gt;The real issue was browser caching.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;curl&lt;/code&gt; or a hard refresh made it clear that Terraform had updated the listener rule correctly and the green environment was serving traffic.&lt;/p&gt;

&lt;p&gt;That was a small but very real operational lesson:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;always verify deployment changes with tools that are less likely to hide the truth behind cached responses&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rolling vs Blue/Green
&lt;/h2&gt;

&lt;p&gt;The two patterns solve similar problems, but in different ways.&lt;/p&gt;

&lt;p&gt;Rolling zero-downtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;replaces the existing infrastructure safely&lt;/li&gt;
&lt;li&gt;uses &lt;code&gt;create_before_destroy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;depends on overlap and health checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Blue/green:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keeps two environments ready&lt;/li&gt;
&lt;li&gt;switches traffic at the load balancer&lt;/li&gt;
&lt;li&gt;makes the cutover more explicit and atomic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are valuable. Blue/green feels cleaner, but it also requires more duplicate infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Main Takeaway
&lt;/h2&gt;

&lt;p&gt;Day 12 made one thing very clear: zero-downtime is not just about writing Terraform syntax correctly.&lt;/p&gt;

&lt;p&gt;It depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;resource lifecycle order&lt;/li&gt;
&lt;li&gt;naming strategy&lt;/li&gt;
&lt;li&gt;healthy load balancer targets&lt;/li&gt;
&lt;li&gt;enough temporary capacity&lt;/li&gt;
&lt;li&gt;good testing discipline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Terraform gives you the tools, but understanding how those tools behave during replacement is what makes a production deployment safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code
&lt;/h2&gt;

&lt;p&gt;GitHub reference:&lt;br&gt;
👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_12" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Terminal Transition
&lt;/h2&gt;

&lt;p&gt;Example transition to include from your terminal testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello World v1
Hello World v1
Hello World v1
Hello World v2
Hello World v2
Hello World v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for blue/green:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blue environment v1
Blue environment v1
Green environment v2
Green environment v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Follow My Journey
&lt;/h2&gt;

&lt;p&gt;This is Day 12 of my 30-Day Terraform Challenge.&lt;/p&gt;

&lt;p&gt;See you on Day 13 🚀&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>devops</category>
      <category>terraform</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How Conditionals Make Terraform Infrastructure Dynamic and Efficient</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Wed, 01 Apr 2026 18:40:00 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/how-conditionals-make-terraform-infrastructure-dynamic-and-efficient-il3</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/how-conditionals-make-terraform-infrastructure-dynamic-and-efficient-il3</guid>
      <description>&lt;p&gt;Day 11 of my Terraform journey was all about going deeper on conditionals.&lt;/p&gt;

&lt;p&gt;I had already used conditionals briefly before, but today I focused on how they make a single Terraform configuration behave differently across environments without duplicating code.&lt;/p&gt;

&lt;p&gt;This is what makes Terraform feel much smarter in real projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dev and production can use the same module&lt;/li&gt;
&lt;li&gt;optional resources can be turned on or off cleanly&lt;/li&gt;
&lt;li&gt;invalid input can be caught before deployment&lt;/li&gt;
&lt;li&gt;one codebase can support different use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Conditionals Matter
&lt;/h2&gt;

&lt;p&gt;Without conditionals, infrastructure code becomes repetitive very quickly.&lt;/p&gt;

&lt;p&gt;You end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;separate dev and production files that mostly repeat the same logic&lt;/li&gt;
&lt;li&gt;optional resources that require manual commenting in and out&lt;/li&gt;
&lt;li&gt;brittle outputs that fail when a resource is disabled&lt;/li&gt;
&lt;li&gt;confusing module behavior when bad input slips through&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conditionals solve that by making Terraform react to input values in a controlled way.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Ternary Expression
&lt;/h2&gt;

&lt;p&gt;The basic Terraform conditional is the ternary expression:&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;condition&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;true_value&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;false_value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A common mistake is scattering that logic directly inside many resource arguments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&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;"production"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"t3.small"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but if you repeat the same logic in many places, the configuration becomes hard to read.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Pattern: Use &lt;code&gt;locals&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;is_production&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&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;"production"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_production&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"t3.small"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;min_size&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_production&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;max_size&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_production&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_type&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solves a real problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the decision logic stays in one place&lt;/li&gt;
&lt;li&gt;resources stay easier to read&lt;/li&gt;
&lt;li&gt;testing environment differences becomes simpler&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Optional Resources with &lt;code&gt;count = condition ? 1 : 0&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is one of the most useful Terraform patterns.&lt;/p&gt;

&lt;p&gt;It lets you create a resource only when a condition is true.&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudwatch_metric_alarm"&lt;/span&gt; &lt;span class="s2"&gt;"high_cpu"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable_detailed_monitoring&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="nx"&gt;alarm_name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.cluster_name}-high-cpu"&lt;/span&gt;
  &lt;span class="nx"&gt;comparison_operator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GreaterThanThreshold"&lt;/span&gt;
  &lt;span class="nx"&gt;evaluation_periods&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;metric_name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CPUUtilization"&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS/EC2"&lt;/span&gt;
  &lt;span class="nx"&gt;period&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;
  &lt;span class="nx"&gt;statistic&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Average"&lt;/span&gt;
  &lt;span class="nx"&gt;threshold&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solves the problem of optional infrastructure.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicating code&lt;/li&gt;
&lt;li&gt;maintaining separate files&lt;/li&gt;
&lt;li&gt;commenting blocks in and out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you can simply say:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create it when enabled&lt;/li&gt;
&lt;li&gt;skip it when disabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used the same idea for optional DNS creation too.&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_record"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create_dns_record&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_route53_zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_name&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
  &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_ip&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;h2&gt;
  
  
  3. Referencing Conditionally Created Resources Safely
&lt;/h2&gt;

&lt;p&gt;This is where many people get tripped up.&lt;/p&gt;

&lt;p&gt;If a resource uses &lt;code&gt;count&lt;/code&gt;, Terraform no longer sees it as a single object. It becomes a list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"alarm_arn"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cloudwatch_metric_alarm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;high_cpu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This breaks when &lt;code&gt;count = 0&lt;/code&gt; because the resource does not exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Correct
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"alarm_arn"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable_detailed_monitoring&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;aws_cloudwatch_metric_alarm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;high_cpu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solves a very real problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;outputs remain safe&lt;/li&gt;
&lt;li&gt;Terraform does not crash when the optional resource is missing&lt;/li&gt;
&lt;li&gt;the module can behave differently without breaking downstream consumers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my Day 11 module, I used the same pattern for both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;alarm_arn&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dns_record_fqdn&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Input Validation Blocks
&lt;/h2&gt;

&lt;p&gt;Validation is one of the most practical Terraform features for shared modules.&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Deployment environment: dev, staging, or production"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"staging"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;var&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="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Environment must be dev, staging, or production."&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;This solves the problem of bad input reaching plan or apply.&lt;/p&gt;

&lt;p&gt;Without validation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;someone could pass &lt;code&gt;prodution&lt;/code&gt; by mistake&lt;/li&gt;
&lt;li&gt;the module might behave unexpectedly&lt;/li&gt;
&lt;li&gt;debugging becomes harder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With validation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform fails early&lt;/li&gt;
&lt;li&gt;the error is clear&lt;/li&gt;
&lt;li&gt;invalid values are caught before deployment starts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is especially useful when modules are shared across teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Environment-Aware Module Pattern
&lt;/h2&gt;

&lt;p&gt;This was the biggest takeaway of the day.&lt;/p&gt;

&lt;p&gt;Instead of maintaining separate modules for dev and production, I built one module that changes behavior based on a single environment variable.&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;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;is_production&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&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;"production"&lt;/span&gt;

  &lt;span class="nx"&gt;instance_type&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_production&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"t3.small"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;min_cluster_size&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_production&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;max_cluster_size&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_production&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="nx"&gt;detailed_monitoring_enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_production&lt;/span&gt;
  &lt;span class="nx"&gt;dns_record_enabled&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_production&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the root configurations become very small.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dev
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"webserver_cluster"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../../modules/services/webserver-cluster"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"day11-web-dev"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Production
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"webserver_cluster"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../../modules/services/webserver-cluster"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"day11-web-production"&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;"production"&lt;/span&gt;
  &lt;span class="nx"&gt;create_dns_record&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;enable_detailed_monitoring&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solves a major real-world problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one reusable module&lt;/li&gt;
&lt;li&gt;environment-specific behavior&lt;/li&gt;
&lt;li&gt;less duplication&lt;/li&gt;
&lt;li&gt;fewer chances for config drift between environments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Real Problem I Hit
&lt;/h2&gt;

&lt;p&gt;One useful lesson from today came from Route53.&lt;/p&gt;

&lt;p&gt;Because production defaulted to enabling DNS, Terraform tried to look up a hosted zone during planning. Since that hosted zone did not exist in my AWS account, &lt;code&gt;terraform plan&lt;/code&gt; failed.&lt;/p&gt;

&lt;p&gt;That showed why conditionals matter so much:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;optional behavior must be guarded carefully&lt;/li&gt;
&lt;li&gt;data lookups should only happen when the related feature is actually enabled&lt;/li&gt;
&lt;li&gt;environment-aware defaults sometimes need explicit overrides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is exactly why I made DNS override-friendly in the production caller.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Main Takeaway
&lt;/h2&gt;

&lt;p&gt;Day 11 showed me that conditionals are not just small syntax tricks.&lt;/p&gt;

&lt;p&gt;They are what make Terraform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reusable&lt;/li&gt;
&lt;li&gt;environment-aware&lt;/li&gt;
&lt;li&gt;safer&lt;/li&gt;
&lt;li&gt;easier to maintain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest lessons for me were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep conditionals in &lt;code&gt;locals&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;count = condition ? 1 : 0&lt;/code&gt; for optional resources&lt;/li&gt;
&lt;li&gt;reference count-based resources safely with &lt;code&gt;[0]&lt;/code&gt; and &lt;code&gt;null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add validation blocks to fail early&lt;/li&gt;
&lt;li&gt;let one module adapt to multiple environments instead of duplicating code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is what makes Terraform infrastructure dynamic and efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_11" rel="noopener noreferrer"&gt;https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_11&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow My Journey
&lt;/h2&gt;

&lt;p&gt;This is Day 11 of my 30-Day Terraform Challenge.&lt;/p&gt;

&lt;p&gt;See you on Day 12 🚀&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>devops</category>
      <category>terraform</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Mastering Loops and Conditionals in Terraform</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Wed, 01 Apr 2026 17:51:15 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/mastering-loops-and-conditionals-in-terraform-844</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/mastering-loops-and-conditionals-in-terraform-844</guid>
      <description>&lt;p&gt;Day 10 of my Terraform journey was all about writing less repetitive infrastructure code.&lt;/p&gt;

&lt;p&gt;Up to this point, most resources had been declared one by one. That works for small labs, but it becomes painful fast when you need multiple IAM users, repeated rules, or environment-specific behavior.&lt;/p&gt;

&lt;p&gt;Today I learned the four Terraform tools that make configurations dynamic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;count&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;for_each&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;for&lt;/code&gt; expressions&lt;/li&gt;
&lt;li&gt;ternary conditionals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was one of the most practical Terraform topics so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Terraform is declarative, but these features make it feel much closer to a programming language.&lt;/p&gt;

&lt;p&gt;They help you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reduce repetition&lt;/li&gt;
&lt;li&gt;create multiple resources safely&lt;/li&gt;
&lt;li&gt;transform data cleanly&lt;/li&gt;
&lt;li&gt;make infrastructure behavior change by environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are also heavily tested in the Terraform Associate exam.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Loops with &lt;code&gt;count&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;count&lt;/code&gt; is the simplest loop in Terraform.&lt;/p&gt;

&lt;p&gt;Use it when you want Terraform to create a fixed number of similar resources.&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"user-${count.index}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;user-0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user-1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user-2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is &lt;code&gt;count.index&lt;/code&gt;, which gives each resource its position.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;count&lt;/code&gt; Index Problem
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;count&lt;/code&gt; becomes risky when it is tied to a list that can change.&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"user_names"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"charlie"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&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;At first, Terraform maps the list like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;index &lt;code&gt;0&lt;/code&gt; → &lt;code&gt;alice&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;index &lt;code&gt;1&lt;/code&gt; → &lt;code&gt;bob&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;index &lt;code&gt;2&lt;/code&gt; → &lt;code&gt;charlie&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now imagine I remove &lt;code&gt;alice&lt;/code&gt; from the list:&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;default&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"charlie"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform now sees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;index &lt;code&gt;0&lt;/code&gt; → &lt;code&gt;bob&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;index &lt;code&gt;1&lt;/code&gt; → &lt;code&gt;charlie&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The configuration still runs, but the identities shift. Terraform tracks these resources by position, so when the list changes, later resources can be updated or recreated unexpectedly.&lt;/p&gt;

&lt;p&gt;That is why &lt;code&gt;count&lt;/code&gt; can be destructive when list order changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Loops with &lt;code&gt;for_each&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;for_each&lt;/code&gt; solves that problem by using keys or values as identity instead of numeric positions.&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"user_names"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"charlie"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_names&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Terraform tracks users by value, not by index.&lt;/p&gt;

&lt;p&gt;If I remove &lt;code&gt;alice&lt;/code&gt;, only &lt;code&gt;alice&lt;/code&gt; is affected.&lt;br&gt;&lt;br&gt;
&lt;code&gt;bob&lt;/code&gt; and &lt;code&gt;charlie&lt;/code&gt; keep their identities.&lt;/p&gt;

&lt;p&gt;That makes &lt;code&gt;for_each&lt;/code&gt; much safer for collections that may change over time.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;for_each&lt;/code&gt; with a Map
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;for_each&lt;/code&gt; becomes even more useful when paired with a map.&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;department&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;admin&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bool&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;alice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;department&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"engineering"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;admin&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;bob&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;department&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"marketing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;admin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Department&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;department&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;This lets each item carry extra configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;code&gt;for&lt;/code&gt; Expressions
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;for&lt;/code&gt; expressions do not create resources. They transform data.&lt;/p&gt;

&lt;p&gt;For example, this outputs uppercase names:&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;output&lt;/span&gt; &lt;span class="s2"&gt;"upper_names"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_names&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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;And this creates a map of usernames to ARNs:&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;output&lt;/span&gt; &lt;span class="s2"&gt;"user_arns"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&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;A good way to think about it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;count&lt;/code&gt; and &lt;code&gt;for_each&lt;/code&gt; create resources&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;for&lt;/code&gt; expressions reshape data&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Ternary Conditionals
&lt;/h2&gt;

&lt;p&gt;Terraform conditionals use this form:&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;condition&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;true_value&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;false_value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They are useful when you want infrastructure behavior to change based on input.&lt;/p&gt;

&lt;p&gt;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="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&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;"production"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"t2.medium"&lt;/span&gt; &lt;span class="err"&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;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if environment is &lt;code&gt;production&lt;/code&gt;, use &lt;code&gt;t2.medium&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;otherwise use &lt;code&gt;t2.micro&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then you can use it like this:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_type&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conditionals with &lt;code&gt;count&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Conditionals also work well with &lt;code&gt;count&lt;/code&gt; when you want a resource to be optional.&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"enable_autoscaling"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Enable autoscaling for the cluster"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bool&lt;/span&gt;
  &lt;span class="nx"&gt;default&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_autoscaling_policy"&lt;/span&gt; &lt;span class="s2"&gt;"scale_out"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable_autoscaling&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if &lt;code&gt;enable_autoscaling = true&lt;/code&gt;, create the policy&lt;/li&gt;
&lt;li&gt;if &lt;code&gt;enable_autoscaling = false&lt;/code&gt;, skip it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to Use &lt;code&gt;count&lt;/code&gt; vs &lt;code&gt;for_each&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This was the biggest lesson of the day.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;count&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you need a fixed number of nearly identical resources&lt;/li&gt;
&lt;li&gt;order does not matter&lt;/li&gt;
&lt;li&gt;the collection is unlikely to change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;code&gt;for_each&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;items have stable names or keys&lt;/li&gt;
&lt;li&gt;the collection may change over time&lt;/li&gt;
&lt;li&gt;each item may have different configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, &lt;code&gt;for_each&lt;/code&gt; is often safer.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Main Takeaway
&lt;/h2&gt;

&lt;p&gt;Day 10 made Terraform feel much more dynamic.&lt;/p&gt;

&lt;p&gt;The most important lesson for me was this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;count&lt;/code&gt; is simple&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;for_each&lt;/code&gt; is safer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;for&lt;/code&gt; expressions clean up data&lt;/li&gt;
&lt;li&gt;conditionals make infrastructure flexible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these four tools make Terraform much more powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_10" rel="noopener noreferrer"&gt;Github LInk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow My Journey&lt;br&gt;
This is Day 10 of my 30-Day Terraform Challenge.&lt;/p&gt;

&lt;p&gt;See you on Day 11 🚀&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>devops</category>
      <category>terraform</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Advanced Terraform Module Usage: Versioning, Gotchas, and Reuse Across Environments</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Mon, 30 Mar 2026 20:01:00 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/advanced-terraform-module-usage-versioning-gotchas-and-reuse-across-environments-4c18</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/advanced-terraform-module-usage-versioning-gotchas-and-reuse-across-environments-4c18</guid>
      <description>&lt;p&gt;Day 9 of my Terraform journey focused on the part of modules that feels more real-world: not just creating them, but using them safely across environments.&lt;/p&gt;

&lt;p&gt;After building my first reusable module on Day 8, today I went deeper into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;module gotchas&lt;/li&gt;
&lt;li&gt;version pinning&lt;/li&gt;
&lt;li&gt;using different module versions in different environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where Terraform modules start to feel production-ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;A module is useful when it is reusable.&lt;/p&gt;

&lt;p&gt;A module becomes powerful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is versioned&lt;/li&gt;
&lt;li&gt;environments can adopt changes at different times&lt;/li&gt;
&lt;li&gt;teams can avoid accidental breakage from untested module updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the core lesson of Day 9.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 1: File Paths Inside Modules
&lt;/h2&gt;

&lt;p&gt;One easy mistake is referencing files inside a module using a plain relative path like this:&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;user_data&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;templatefile&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"./user-data.sh"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server_port&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is that Terraform may resolve that path relative to where Terraform is run, not relative to the module itself.&lt;/p&gt;

&lt;p&gt;The safer pattern is:&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;user_data&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;templatefile&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/user-data.sh"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server_port&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;path.module&lt;/code&gt; always points to the module’s own folder&lt;/li&gt;
&lt;li&gt;file lookups stay predictable&lt;/li&gt;
&lt;li&gt;the module works correctly when called from different root configurations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Gotcha 2: Inline Blocks vs Separate Resources
&lt;/h2&gt;

&lt;p&gt;Some Terraform resources can be configured in two ways.&lt;/p&gt;

&lt;p&gt;For example, security groups can use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inline &lt;code&gt;ingress&lt;/code&gt; and &lt;code&gt;egress&lt;/code&gt; blocks&lt;/li&gt;
&lt;li&gt;separate &lt;code&gt;aws_security_group_rule&lt;/code&gt; resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mixing both patterns can cause conflicts and make the module harder to extend.&lt;/p&gt;

&lt;p&gt;For reusable modules, separate resources are usually the better choice because they are more flexible and easier for callers to extend without editing the module itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 3: Broad Module Dependencies
&lt;/h2&gt;

&lt;p&gt;Another subtle issue is depending on an entire module with &lt;code&gt;depends_on&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If a root configuration depends on a whole module, Terraform can treat the entire module as the dependency instead of just the specific resource or output that was actually needed.&lt;/p&gt;

&lt;p&gt;That can create unnecessary dependency chains and make plans more confusing than they need to be.&lt;/p&gt;

&lt;p&gt;The better pattern is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expose more specific outputs&lt;/li&gt;
&lt;li&gt;depend on the exact value you need&lt;/li&gt;
&lt;li&gt;avoid broad module-level dependencies unless they are truly necessary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Versioning the Module
&lt;/h2&gt;

&lt;p&gt;This was the most practical part of the day.&lt;/p&gt;

&lt;p&gt;Instead of calling the module only from a local path, I versioned it with Git tags and pinned environments to specific versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local Source
&lt;/h3&gt;

&lt;p&gt;For quick development, a local source is useful:&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;source&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../../../../modules/services/webserver-cluster"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is great for fast iteration, but not ideal for shared environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git Source
&lt;/h3&gt;

&lt;p&gt;For versioned usage, I used a Git source with &lt;code&gt;ref=&lt;/code&gt;:&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;source&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/mary20205090/30-day-Terraform-Challenge//day_8/modules/services/webserver-cluster?ref=v0.0.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pull the module from GitHub&lt;/li&gt;
&lt;li&gt;use the module subdirectory&lt;/li&gt;
&lt;li&gt;pin it to the exact version tag &lt;code&gt;v0.0.1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Registry Source
&lt;/h3&gt;

&lt;p&gt;A registry source is cleaner when modules are published more formally:&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;source&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"namespace/webserver-cluster/aws"&lt;/span&gt;
&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is easier to read, but the Git source pattern was enough for my lab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Environment Versioning Pattern
&lt;/h2&gt;

&lt;p&gt;I then used different module versions intentionally across environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dev&lt;/code&gt; used &lt;code&gt;v0.0.2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;production&lt;/code&gt; stayed on &lt;code&gt;v0.0.1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the real pattern teams use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dev&lt;/code&gt; tests the newer module version&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;production&lt;/code&gt; stays pinned to the older stable version until the new one is validated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes module rollouts safer and reduces the risk of untested changes reaching production too early.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Observed
&lt;/h2&gt;

&lt;p&gt;In my Day 9 setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dev&lt;/code&gt; picked up the new module version and exposed the additional output I added&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;production&lt;/code&gt; remained on the previous version and continued using the older stable module behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That confirmed the main idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same reusable module&lt;/li&gt;
&lt;li&gt;different pinned versions&lt;/li&gt;
&lt;li&gt;different environments can move at different speeds safely&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Day 9 made one thing very clear to me: creating a reusable module is only the beginning.&lt;/p&gt;

&lt;p&gt;The real strength comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;understanding the gotchas&lt;/li&gt;
&lt;li&gt;versioning modules properly&lt;/li&gt;
&lt;li&gt;testing newer versions in lower-risk environments&lt;/li&gt;
&lt;li&gt;promoting those versions only when they are ready&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is what makes Terraform modules practical for teams at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_9" rel="noopener noreferrer"&gt;Github LInk&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow My Journey
&lt;/h2&gt;

&lt;p&gt;This is Day 9 of my 30-Day Terraform Challenge.&lt;/p&gt;

&lt;p&gt;See you on Day 10 🚀&lt;/p&gt;

</description>
      <category>automation</category>
      <category>devops</category>
      <category>learning</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Building Reusable Infrastructure with Terraform Modules</title>
      <dc:creator>Mary Mutua</dc:creator>
      <pubDate>Sun, 29 Mar 2026 17:52:52 +0000</pubDate>
      <link>https://dev.to/mary_mutua_9d55b3c269f343/building-reusable-infrastructure-with-terraform-modules-71d</link>
      <guid>https://dev.to/mary_mutua_9d55b3c269f343/building-reusable-infrastructure-with-terraform-modules-71d</guid>
      <description>&lt;p&gt;Day 8 of my Terraform journey focused on one of Terraform’s most important ideas: &lt;strong&gt;modules&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After a week of building infrastructure, I started noticing the same patterns repeating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;security groups&lt;/li&gt;
&lt;li&gt;launch templates&lt;/li&gt;
&lt;li&gt;Auto Scaling Groups&lt;/li&gt;
&lt;li&gt;Application Load Balancers&lt;/li&gt;
&lt;li&gt;listeners&lt;/li&gt;
&lt;li&gt;target groups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So today I stopped copying that logic and turned it into a reusable Terraform module.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Modules Matter
&lt;/h2&gt;

&lt;p&gt;A module is a reusable package of Terraform code.&lt;/p&gt;

&lt;p&gt;Instead of rewriting the same infrastructure for every environment, you write it once and call it from different root configurations with different inputs.&lt;/p&gt;

&lt;p&gt;That gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;less duplication&lt;/li&gt;
&lt;li&gt;cleaner code&lt;/li&gt;
&lt;li&gt;easier maintenance&lt;/li&gt;
&lt;li&gt;more consistency across environments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Module Structure
&lt;/h2&gt;

&lt;p&gt;I organized my module 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;modules/
└── services/
    └── webserver-cluster/
        ├── main.tf
        ├── variables.tf
        ├── outputs.tf
        └── README.md

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This module packages a reusable web server cluster made of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an ALB&lt;/li&gt;
&lt;li&gt;a target group&lt;/li&gt;
&lt;li&gt;a listener&lt;/li&gt;
&lt;li&gt;security groups&lt;/li&gt;
&lt;li&gt;a launch template&lt;/li&gt;
&lt;li&gt;an Auto Scaling Group&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Inputs and Outputs
&lt;/h2&gt;

&lt;p&gt;To make the module reusable, I moved environment-specific values into inputs such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cluster_name&lt;/li&gt;
&lt;li&gt;environment&lt;/li&gt;
&lt;li&gt;instance_type&lt;/li&gt;
&lt;li&gt;min_size&lt;/li&gt;
&lt;li&gt;max_size&lt;/li&gt;
&lt;li&gt;desired_capacity&lt;/li&gt;
&lt;li&gt;server_port&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also exposed outputs that a caller would need, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;alb_dns_name&lt;/li&gt;
&lt;li&gt;asg_name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the module hides the repeated infrastructure logic, while still letting the caller configure what matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling the Module
&lt;/h2&gt;

&lt;p&gt;I then created root configurations that reused the same module with different values.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dev used smaller settings&lt;/li&gt;
&lt;li&gt;production used larger settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the pattern became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same module&lt;/li&gt;
&lt;li&gt;different inputs&lt;/li&gt;
&lt;li&gt;zero code duplication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the biggest lesson of the day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes a Good Module
&lt;/h2&gt;

&lt;p&gt;After building one, I think a good Terraform module should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;focused on one job&lt;/li&gt;
&lt;li&gt;easy to understand&lt;/li&gt;
&lt;li&gt;configurable where needed&lt;/li&gt;
&lt;li&gt;not overloaded with too many inputs&lt;/li&gt;
&lt;li&gt;documented clearly in a README&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A painful module is one that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hardcodes too much&lt;/li&gt;
&lt;li&gt;exposes too many confusing variables&lt;/li&gt;
&lt;li&gt;has weak outputs&lt;/li&gt;
&lt;li&gt;has no usage guidance&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Takeaway
&lt;/h2&gt;

&lt;p&gt;Earlier days taught me how to build the infrastructure itself.&lt;/p&gt;

&lt;p&gt;Day 8 taught me how to package that infrastructure into something reusable.&lt;/p&gt;

&lt;p&gt;That shift feels important. Modules are what make Terraform code easier to scale across environments, projects, and teams.&lt;/p&gt;

&lt;p&gt;I kept the full module code and root usage examples in GitHub here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_8" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow My Journey
&lt;/h2&gt;

&lt;p&gt;This is Day 8 of my 30-Day Terraform Challenge.&lt;/p&gt;

&lt;p&gt;See you on Day 9 🚀&lt;/p&gt;

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