<?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: Mukami</title>
    <description>The latest articles on DEV Community by Mukami (@tink-origami).</description>
    <link>https://dev.to/tink-origami</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%2F3829683%2Fcc4c3ea0-78ad-491a-82f2-a0508368436b.png</url>
      <title>DEV Community: Mukami</title>
      <link>https://dev.to/tink-origami</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tink-origami"/>
    <language>en</language>
    <item>
      <title>Ready for Terraform Certification: My Final Exam Prep and 30-Day Reflection</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Tue, 21 Apr 2026 18:06:28 +0000</pubDate>
      <link>https://dev.to/tink-origami/ready-for-terraform-certification-my-final-exam-prep-and-30-day-reflection-o2m</link>
      <guid>https://dev.to/tink-origami/ready-for-terraform-certification-my-final-exam-prep-and-30-day-reflection-o2m</guid>
      <description>&lt;h2&gt;
  
  
  Five Practice Exams. 30 Days. 1,247 Lines of HCL. Zero Sanity Left.
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 30 of the 30-Day Terraform Challenge&lt;/strong&gt;: and I survived.&lt;/p&gt;

&lt;p&gt;Barely. My keyboard has seen things. My AWS bill has seen worse. My &lt;code&gt;terraform destroy&lt;/code&gt; reflex is now faster than my morning coffee reflex.&lt;/p&gt;

&lt;p&gt;Five practice exams. 30 days of building. Countless "why is this not working" moments. And one certification prep that started as a goal and became an obsession.&lt;/p&gt;

&lt;p&gt;Here's where I stand, what I learned, and why I'm finally ready to face the exam.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practice Exam 5: The Final Boss
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Score: 46/57 (81%)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Character Arc:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exam 1 (Day 28): 74% — nervous, sweaty palms, second-guessing everything&lt;/li&gt;
&lt;li&gt;Exam 5 (Today): 81% — confident, calm, only mildly questioning my life choices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Improvement:&lt;/strong&gt; +7%&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this tells me:&lt;/strong&gt; My brain has been successfully reprogrammed. The gaps are filled. The CLI flags no longer haunt my dreams.&lt;/p&gt;




&lt;h2&gt;
  
  
  Five-Exam Score Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exam&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;%&lt;/th&gt;
&lt;th&gt;Mood&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exam 1&lt;/td&gt;
&lt;td&gt;42/57&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;td&gt;"Wait, what does &lt;code&gt;state rm&lt;/code&gt; do again?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exam 2&lt;/td&gt;
&lt;td&gt;44/57&lt;/td&gt;
&lt;td&gt;77%&lt;/td&gt;
&lt;td&gt;"Oh, I actually know some of this"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exam 3&lt;/td&gt;
&lt;td&gt;45/57&lt;/td&gt;
&lt;td&gt;79%&lt;/td&gt;
&lt;td&gt;"I am become Terraform, destroyer of resources"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exam 4&lt;/td&gt;
&lt;td&gt;43/57&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;"Brain.exe has stopped working" (fatigue)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exam 5&lt;/td&gt;
&lt;td&gt;46/57&lt;/td&gt;
&lt;td&gt;81%&lt;/td&gt;
&lt;td&gt;"Let's do this."&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Average:&lt;/strong&gt; 44/57 (77%) — consistently above passing&lt;br&gt;
&lt;strong&gt;Peak:&lt;/strong&gt; 81% — ready to book&lt;/p&gt;




&lt;h2&gt;
  
  
  Fill-in-the-Blank Results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;My answers (from memory, no peeking):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;terraform fmt&lt;/code&gt; — because my code was uglier than my sleep schedule&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prevent_destroy&lt;/code&gt; — the "save me from myself" button&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform.workspace&lt;/code&gt; — the magic variable that knows where you are&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;encrypt&lt;/code&gt; — because secrets in plaintext are a resume-generating event&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;set&lt;/code&gt; — for_each's best friend (maps are also invited to the party)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rm&lt;/code&gt; — removes from state, not from existence (confusing? yes. important? also yes)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3.0&lt;/code&gt; — because &lt;code&gt;~&amp;gt; 2.0&lt;/code&gt; is a promise, not a suggestion&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;existing&lt;/code&gt; / &lt;code&gt;managed&lt;/code&gt; — one reads, one creates. don't mix them up.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.terraform.lock.hcl&lt;/code&gt; — the bouncer that keeps provider versions consistent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;myplan.tfplan&lt;/code&gt; — apply what you planned, not what your tired brain thinks you planned&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;All correct.&lt;/strong&gt; The knowledge is in there. Somewhere between the coffee and the broken builds.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Readiness Check (The "Can I Actually Do This?" Test)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. What does &lt;code&gt;terraform init&lt;/code&gt; do to your &lt;code&gt;.terraform&lt;/code&gt; directory?&lt;/strong&gt;&lt;br&gt;
Downloads providers and modules. It's like grocery shopping for your infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Difference between &lt;code&gt;terraform.tfstate&lt;/code&gt; and &lt;code&gt;terraform.tfstate.backup&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
One is the source of truth. The other is the "oops, I broke it" safety net.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Why never commit &lt;code&gt;terraform.tfstate&lt;/code&gt; to version control?&lt;/strong&gt;&lt;br&gt;
Secrets. Also merge conflicts. Also your teammates will never forgive you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. What does &lt;code&gt;depends_on&lt;/code&gt; do and when should you use it?&lt;/strong&gt;&lt;br&gt;
Terraform's way of saying "I know you think this doesn't matter, but trust me, it does." Use it when Terraform can't figure out dependencies on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Difference between a variable block and a locals block?&lt;/strong&gt;&lt;br&gt;
Variables are inputs from outside (like asking your friend what pizza topping they want). Locals are internal calculations (like deciding how many slices everyone gets).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. What happens if you run &lt;code&gt;terraform apply&lt;/code&gt; and the state file was modified by another team member?&lt;/strong&gt;&lt;br&gt;
Chaos. Or an error message. Depends on whether you have state locking enabled. (Enable state locking.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. What does &lt;code&gt;terraform graph&lt;/code&gt; output and what is it used for?&lt;/strong&gt;&lt;br&gt;
A beautiful mess of dependencies that looks like a conspiracy theorist's wall. Used for debugging when you have no idea why Resource A depends on Resource B.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. What is the Terraform Registry?&lt;/strong&gt;&lt;br&gt;
The App Store for infrastructure. Providers, modules, and policies — all ready to download.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Difference between Terraform Cloud and Terraform Enterprise?&lt;/strong&gt;&lt;br&gt;
One is SaaS (they run it). One is self-hosted (you run it, good luck).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. When a module uses &lt;code&gt;configuration_aliases&lt;/code&gt;, what problem does it solve?&lt;/strong&gt;&lt;br&gt;
"How do I use this module in two different regions without copying the entire thing?" — this is the answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confidence level:&lt;/strong&gt; I'm ready. The exam is not ready for me.&lt;/p&gt;




&lt;h2&gt;
  
  
  30-Day Reflection (The Therapy Session)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What changed?
&lt;/h3&gt;

&lt;p&gt;What changed is how I think.&lt;/p&gt;

&lt;p&gt;Before Day 1, infrastructure was something I set up once and prayed no one touched. After Day 30, infrastructure is code. It goes through PRs. It has tests. It has a CI/CD pipeline. It has opinions about how it should be deployed.&lt;/p&gt;

&lt;p&gt;The biggest shift: I stopped fearing &lt;code&gt;terraform destroy&lt;/code&gt;. I trust it because I know state is backed up, plans are reviewed, and rollback is one command away. That's not confidence — that's engineering.&lt;/p&gt;

&lt;h3&gt;
  
  
  What am I most proud of?
&lt;/h3&gt;

&lt;p&gt;The EKS cluster on Day 15.&lt;/p&gt;

&lt;p&gt;It took 15 minutes to provision. It failed twice. &lt;code&gt;kubectl&lt;/code&gt; authentication broke in ways I didn't know were possible. I spent hours debugging. I questioned every life choice that led me to that moment.&lt;/p&gt;

&lt;p&gt;But when &lt;code&gt;kubectl get nodes&lt;/code&gt; finally returned two Ready nodes, I felt like Neo seeing the Matrix for the first time. That cluster taught me more about debugging, patience, and AWS networking than any tutorial ever could.&lt;/p&gt;

&lt;h3&gt;
  
  
  What comes next?
&lt;/h3&gt;

&lt;p&gt;First, the certification exam. I'm booking it for next week. The practice exams say I'm ready. My gut says I'm ready. (My sleep schedule says otherwise, but that's a separate issue.)&lt;/p&gt;

&lt;p&gt;After that: contributing to open source Terraform modules. I've broken enough things. Time to help fix some.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exam Logistics
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; Not yet booked — aiming for next Friday&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plan:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Online proctored (so I can wear comfortable pants)&lt;/li&gt;
&lt;li&gt;Morning slot (my brain works before noon)&lt;/li&gt;
&lt;li&gt;Clear desk, test webcam, have ID ready&lt;/li&gt;
&lt;li&gt;One final practice exam the day before, then rest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What could go wrong:&lt;/strong&gt; Everything. But I've debugged worse.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Message to the Future Participant
&lt;/h2&gt;

&lt;p&gt;You will break things. You will get frustrated. You will stare at &lt;code&gt;terraform plan&lt;/code&gt; output wondering why it wants to destroy your entire VPC when you only changed a tag.&lt;/p&gt;

&lt;p&gt;That's not failure. That's learning.&lt;/p&gt;

&lt;p&gt;Here's what I wish someone told me on Day 1:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commit &lt;code&gt;.gitignore&lt;/code&gt; before you commit anything else&lt;/li&gt;
&lt;li&gt;Write tests earlier — they're not optional&lt;/li&gt;
&lt;li&gt;Every error message is telling you exactly what's wrong. Read it. Then read it again. Then maybe Google it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform state rm&lt;/code&gt; does NOT delete the resource. Learn this before you learn it the hard way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will finish this challenge knowing more than most engineers learn in their first year.&lt;/p&gt;

&lt;p&gt;Trust the process. Break things they won't break you. Fix them. Then break them again.&lt;/p&gt;

&lt;p&gt;And when you finally pass the exam, celebrate. You earned it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line (In Numbers)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fear of &lt;code&gt;terraform destroy&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;2/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardcoded values&lt;/td&gt;
&lt;td&gt;Embarrassing&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test coverage&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Unit + integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State location&lt;/td&gt;
&lt;td&gt;Local laptop (yikes)&lt;/td&gt;
&lt;td&gt;S3 + DynamoDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regions&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3 (multi-region flex)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confidence in passing exam&lt;/td&gt;
&lt;td&gt;"Maybe?"&lt;/td&gt;
&lt;td&gt;"Let's go."&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Day 30. Challenge complete. Exam next. Let's do this.&lt;/strong&gt;&lt;/p&gt;




</description>
      <category>aws</category>
      <category>devops</category>
      <category>learning</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Fine-tuning My Terraform Exam Prep with Practice Exams</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Tue, 21 Apr 2026 17:18:38 +0000</pubDate>
      <link>https://dev.to/tink-origami/fine-tuning-my-terraform-exam-prep-with-practice-exams-3ej</link>
      <guid>https://dev.to/tink-origami/fine-tuning-my-terraform-exam-prep-with-practice-exams-3ej</guid>
      <description>&lt;h2&gt;
  
  
  Four Exams. 228 Questions. One Clear Picture.
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 29 of the 30-Day Terraform Challenge&lt;/strong&gt; — and today I did something that felt excessive.&lt;/p&gt;

&lt;p&gt;Two more practice exams. 57 questions each. Same conditions. No notes. No looking up answers.&lt;/p&gt;

&lt;p&gt;Four exams in two days. 228 questions total.&lt;/p&gt;

&lt;p&gt;But the exams weren't the point. The analysis after them was.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Four-Exam Score Trend
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exam&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Percentage&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exam 1 (Day 28)&lt;/td&gt;
&lt;td&gt;42/57&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;td&gt;First run, nervous&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exam 2 (Day 28)&lt;/td&gt;
&lt;td&gt;44/57&lt;/td&gt;
&lt;td&gt;77%&lt;/td&gt;
&lt;td&gt;Warm-up effect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exam 3 (Today)&lt;/td&gt;
&lt;td&gt;45/57&lt;/td&gt;
&lt;td&gt;79%&lt;/td&gt;
&lt;td&gt;Feeling confident&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exam 4 (Today)&lt;/td&gt;
&lt;td&gt;43/57&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;Fatigued, rushed last 10 questions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Trend analysis:&lt;/strong&gt; Scores are consistently above 70% (range 74-79%). The drop on Exam 4 was fatigue — I finished with only 2 minutes left and rushed the final questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Readiness rating:&lt;/strong&gt; Nearly Ready. My knowledge is solid, but endurance is a factor. On the real exam, I need to pace myself better.&lt;/p&gt;




&lt;h2&gt;
  
  
  Persistent Wrong-Answer Topics
&lt;/h2&gt;

&lt;p&gt;Across all four exams, these topics appeared in my wrong answers &lt;strong&gt;more than once&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;terraform state rm&lt;/code&gt; vs &lt;code&gt;terraform destroy&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wrong understanding:&lt;/strong&gt; Thought &lt;code&gt;state rm&lt;/code&gt; would delete the real resource&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct understanding:&lt;/strong&gt; &lt;code&gt;state rm&lt;/code&gt; removes from state only. &lt;code&gt;destroy&lt;/code&gt; deletes real infrastructure. The resource continues running in AWS after &lt;code&gt;state rm&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;terraform plan -target&lt;/code&gt; includes dependencies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wrong understanding:&lt;/strong&gt; Thought &lt;code&gt;-target&lt;/code&gt; only plans the specified resource&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct understanding:&lt;/strong&gt; It plans the targeted resource AND any resources that depend on it&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;terraform apply -refresh-only&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wrong understanding:&lt;/strong&gt; Thought it was part of normal apply&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct understanding:&lt;/strong&gt; It only updates state to match real infrastructure without making changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Sentinel policy execution timing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wrong understanding:&lt;/strong&gt; Thought Sentinel runs before plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct understanding:&lt;/strong&gt; Runs after plan, before apply — evaluates proposed changes before they happen&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Provider version constraint &lt;code&gt;~&amp;gt; 1.0.0&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wrong understanding:&lt;/strong&gt; Thought it meant any 1.x version&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct understanding:&lt;/strong&gt; It means &amp;gt;= 1.0.0 and &amp;lt; 1.1.0 — only the last digit can increment&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Workspace behaviour
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wrong understanding:&lt;/strong&gt; Thought workspaces are separate directories&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct understanding:&lt;/strong&gt; Each workspace has its own state file; &lt;code&gt;terraform.workspace&lt;/code&gt; returns the workspace name as a string&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Hands-On Exercises to Close the Gaps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Exercise 1: State Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deploy test resource&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'resource "random_id" "test" { byte_length = 4 }'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; main.tf
terraform init &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;

&lt;span class="c"&gt;# Verify resource exists&lt;/span&gt;
terraform state list
&lt;span class="c"&gt;# Output: random_id.test&lt;/span&gt;

&lt;span class="c"&gt;# Remove from state only&lt;/span&gt;
terraform state &lt;span class="nb"&gt;rm &lt;/span&gt;random_id.test
terraform state list
&lt;span class="c"&gt;# Output: (empty) — gone from state&lt;/span&gt;

&lt;span class="c"&gt;# Resource still exists in AWS? For random_id it's generated, but for EC2 it would still run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this reinforced:&lt;/strong&gt; &lt;code&gt;state rm&lt;/code&gt; only removes from state. The real resource continues to exist.&lt;/p&gt;




&lt;h3&gt;
  
  
  Exercise 2: Workspace Practice
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create workspaces&lt;/span&gt;
terraform workspace new dev
terraform workspace new staging
terraform workspace list
&lt;span class="c"&gt;# Output: default, dev, staging&lt;/span&gt;

&lt;span class="c"&gt;# Switch and verify&lt;/span&gt;
terraform workspace &lt;span class="k"&gt;select &lt;/span&gt;dev
terraform workspace show
&lt;span class="c"&gt;# Output: dev&lt;/span&gt;

&lt;span class="c"&gt;# Delete (cannot delete current workspace)&lt;/span&gt;
terraform workspace &lt;span class="k"&gt;select &lt;/span&gt;default
terraform workspace delete staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this reinforced:&lt;/strong&gt; Workspaces are separate state files; &lt;code&gt;terraform.workspace&lt;/code&gt; is the expression you use in config.&lt;/p&gt;




&lt;h3&gt;
  
  
  Exercise 3: Provider Version Constraints
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example 1: ~&amp;gt; 5.0&lt;/span&gt;
&lt;span class="c1"&gt;# Means: &amp;gt;= 5.0.0 and &amp;lt; 6.0.0 (any 5.x version)&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="p"&gt;,&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="c1"&gt;# Example 2: ~&amp;gt; 5.1.0&lt;/span&gt;
&lt;span class="c1"&gt;# Means: &amp;gt;= 5.1.0 and &amp;lt; 5.2.0 (only patch increments)&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="p"&gt;,&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.1.0"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Example 3: &amp;gt;= 5.0, &amp;lt; 6.0&lt;/span&gt;
&lt;span class="c1"&gt;# Means: exactly the same as ~&amp;gt; 5.0 (explicit version)&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="p"&gt;,&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, &amp;lt; 6.0"&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;
  
  
  Final Study Priority List (Day 30)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Specific Focus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;State commands&lt;/td&gt;
&lt;td&gt;Difference between &lt;code&gt;state rm&lt;/code&gt; and &lt;code&gt;destroy&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;CLI flags&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-target&lt;/code&gt;, &lt;code&gt;-refresh-only&lt;/code&gt;, &lt;code&gt;-upgrade&lt;/code&gt; behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Provider constraints&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;~&amp;gt;&lt;/code&gt; vs &lt;code&gt;&amp;gt;=&lt;/code&gt; vs exact version ranges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Workspaces&lt;/td&gt;
&lt;td&gt;CLI workspaces vs TFC workspaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Sentinel policies&lt;/td&gt;
&lt;td&gt;Execution timing and enforcement flow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;&lt;strong&gt;Fatigue is real.&lt;/strong&gt; My score dropped on Exam 4 because I was tired. On the real exam, I need to pace myself and not rush the last questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistent gaps are the only ones that matter.&lt;/strong&gt; A one-off mistake is fine. A topic you miss twice is a genuine gap that needs hands-on practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hands-on beats reading.&lt;/strong&gt; Every concept I got wrong more than once, I fixed by running actual commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm not ready, but I'm nearly ready.&lt;/strong&gt; Consistently above 70% across four exams tells me I know the material. The last day is about closing the persistent gaps and building endurance.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Four exams. 228 questions. 74-79% consistently.&lt;/p&gt;

&lt;p&gt;Not perfect. But clear.&lt;/p&gt;

&lt;p&gt;I know exactly where my gaps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State commands&lt;/li&gt;
&lt;li&gt;CLI flags&lt;/li&gt;
&lt;li&gt;Provider constraints&lt;/li&gt;
&lt;li&gt;Workspaces&lt;/li&gt;
&lt;li&gt;Sentinel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One day left. Hands-on practice only. No more exams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A gap you close today is a point you won't lose tomorrow.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/cli/commands/state" rel="noopener noreferrer"&gt;Terraform State Commands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/providers/requirements" rel="noopener noreferrer"&gt;Provider Version Constraints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/state/workspaces" rel="noopener noreferrer"&gt;Terraform Workspaces&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>terraform</category>
      <category>aws</category>
      <category>practice</category>
      <category>exam</category>
    </item>
    <item>
      <title>How I Prepared for the Terraform Associate Exam with Practice Questions</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Tue, 21 Apr 2026 16:20:02 +0000</pubDate>
      <link>https://dev.to/tink-origami/how-i-prepared-for-the-terraform-associate-exam-with-practice-questions-58o7</link>
      <guid>https://dev.to/tink-origami/how-i-prepared-for-the-terraform-associate-exam-with-practice-questions-58o7</guid>
      <description>&lt;h2&gt;
  
  
  Two Practice Exams. 114 Questions. Zero Looking Up Answers.
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 28 of the 30-Day Terraform Challenge&lt;/strong&gt; and today I did something I've been dreading for weeks.&lt;/p&gt;

&lt;p&gt;Two full practice exams. 57 questions each. 60 minutes each. No notes. No looking things up. No "just this one quick search."&lt;/p&gt;

&lt;p&gt;Just me, the questions, and a timer.&lt;/p&gt;

&lt;p&gt;Here's what happened, what I learned, and how I'm using my wrong answers to close the gaps.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Scores
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exam&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Percentage&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exam 1&lt;/td&gt;
&lt;td&gt;42/57&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;td&gt;✅ Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exam 2&lt;/td&gt;
&lt;td&gt;44/57&lt;/td&gt;
&lt;td&gt;77%&lt;/td&gt;
&lt;td&gt;✅ Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Difference:&lt;/strong&gt; +3% on Exam 2. The warm-up effect is real. By the second exam, my brain was in Terraform mode and I was reading questions more carefully.&lt;/p&gt;

&lt;p&gt;The passing threshold is 70% (40/57). I passed both, but not comfortably. There are clear gaps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Domain Accuracy Breakdown
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Q's Attempted&lt;/th&gt;
&lt;th&gt;Correct&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IaC concepts&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform's purpose&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform basics&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform CLI&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;64%&lt;/strong&gt; ❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform modules&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core workflow&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State management&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;60%&lt;/strong&gt; ❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform Cloud&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;50%&lt;/strong&gt; ❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Three domains below 70%:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform CLI (64%)&lt;/li&gt;
&lt;li&gt;State management (60%)&lt;/li&gt;
&lt;li&gt;Terraform Cloud (50%)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CLI section is the heaviest-weighted domain (26%). This is where I need to focus.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrong-Answer Analysis
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Question 1
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Topic:&lt;/strong&gt; &lt;code&gt;terraform state rm&lt;/code&gt; behaviour&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I answered:&lt;/strong&gt; The EC2 instance is terminated immediately&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correct answer:&lt;/strong&gt; The instance continues running; only removed from state&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I was wrong:&lt;/strong&gt; I confused &lt;code&gt;terraform state rm&lt;/code&gt; with &lt;code&gt;terraform destroy&lt;/code&gt;. &lt;code&gt;state rm&lt;/code&gt; only removes the resource from the state file. The real infrastructure is untouched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doc reference:&lt;/strong&gt; &lt;a href="https://developer.hashicorp.com/terraform/cli/commands/state/rm" rel="noopener noreferrer"&gt;Terraform state rm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hands-on fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform state &lt;span class="nb"&gt;rm &lt;/span&gt;aws_instance.web
&lt;span class="c"&gt;# Instance still running in AWS&lt;/span&gt;
terraform destroy  &lt;span class="c"&gt;# This would terminate it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Question 2
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Topic:&lt;/strong&gt; &lt;code&gt;terraform plan -target&lt;/code&gt; behaviour&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I answered:&lt;/strong&gt; Only plans the targeted resource&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correct answer:&lt;/strong&gt; Plans the targeted resource AND any resources that depend on it&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I was wrong:&lt;/strong&gt; I thought &lt;code&gt;-target&lt;/code&gt; was a surgical tool. It's not — Terraform must maintain dependency integrity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doc reference:&lt;/strong&gt; &lt;a href="https://developer.hashicorp.com/terraform/cli/commands/plan#resource-targeting" rel="noopener noreferrer"&gt;Terraform plan -target&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hands-on fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aws_instance.web
&lt;span class="c"&gt;# Also shows dependent resources like security groups&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Question 3
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Topic:&lt;/strong&gt; &lt;code&gt;terraform apply -refresh-only&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I answered:&lt;/strong&gt; It refreshes the state and applies changes&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correct answer:&lt;/strong&gt; It only updates state to match real infrastructure without making changes&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I was wrong:&lt;/strong&gt; I thought &lt;code&gt;-refresh-only&lt;/code&gt; was part of normal apply. It's a separate operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doc reference:&lt;/strong&gt; &lt;a href="https://developer.hashicorp.com/terraform/cli/commands/apply#refresh-only" rel="noopener noreferrer"&gt;Terraform apply -refresh-only&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hands-on fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply &lt;span class="nt"&gt;-refresh-only&lt;/span&gt;
&lt;span class="c"&gt;# State updated, no resources modified&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Question 4
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Topic:&lt;/strong&gt; Sentinel policy execution timing&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I answered:&lt;/strong&gt; Before plan&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correct answer:&lt;/strong&gt; After plan, before apply&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I was wrong:&lt;/strong&gt; I thought Sentinel was a pre-plan validator. It actually runs after plan to enforce policy on proposed changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doc reference:&lt;/strong&gt; &lt;a href="https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement" rel="noopener noreferrer"&gt;Sentinel policies&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hands-on fix:&lt;/strong&gt; Ran a Sentinel policy in Terraform Cloud workspace to see the enforcement flow.&lt;/p&gt;




&lt;h3&gt;
  
  
  Question 5
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Topic:&lt;/strong&gt; TFC workspace vs directory isolation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I answered:&lt;/strong&gt; TFC workspaces are separate directories&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correct answer:&lt;/strong&gt; TFC workspaces are separate state files with collaboration features&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I was wrong:&lt;/strong&gt; I used S3 backend, not TFC. The mental model was wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doc reference:&lt;/strong&gt; &lt;a href="https://developer.hashicorp.com/terraform/cloud-docs/workspaces" rel="noopener noreferrer"&gt;Terraform Cloud workspaces&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hands-on fix:&lt;/strong&gt; Created a free TFC account and set up a workspace.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hands-On Revision for Weak Areas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Domain: Terraform CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Practiced each command&lt;/span&gt;
terraform state list
terraform state show aws_instance.web
terraform state &lt;span class="nb"&gt;mv &lt;/span&gt;aws_instance.web aws_instance.web_old
terraform state &lt;span class="nb"&gt;rm &lt;/span&gt;aws_instance.web_old
terraform plan &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aws_instance.web
terraform apply &lt;span class="nt"&gt;-refresh-only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Domain: State Management
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Created test resources, ran state commands&lt;/span&gt;
terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
terraform state list
&lt;span class="c"&gt;# Removed from state, observed instance still running&lt;/span&gt;
terraform state &lt;span class="nb"&gt;rm &lt;/span&gt;aws_instance.test
aws ec2 describe-instances &lt;span class="nt"&gt;--instance-ids&lt;/span&gt; i-12345
&lt;span class="c"&gt;# Re-imported&lt;/span&gt;
terraform import aws_instance.test i-12345
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Domain: Terraform Cloud
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Created free TFC account&lt;/li&gt;
&lt;li&gt;Set up workspace connected to GitHub&lt;/li&gt;
&lt;li&gt;Configured variable sets&lt;/li&gt;
&lt;li&gt;Ran a remote plan&lt;/li&gt;
&lt;li&gt;Observed Sentinel policy enforcement&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Pattern Recognition
&lt;/h2&gt;

&lt;p&gt;After two exams, I noticed three patterns in my wrong answers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. CLI flag edge cases&lt;/strong&gt; — I know the main commands but miss the specific flags (&lt;code&gt;-refresh-only&lt;/code&gt;, &lt;code&gt;-target&lt;/code&gt; dependencies, &lt;code&gt;-upgrade&lt;/code&gt; behaviour)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. State vs real infrastructure&lt;/strong&gt; — I consistently confuse commands that affect state vs commands that affect real resources&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. TFC-specific features&lt;/strong&gt; — I've used S3 backend throughout the challenge, so TFC questions are harder&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Hands-on practice with each CLI flag and creating a TFC account to see the features in action.&lt;/p&gt;




&lt;h2&gt;
  
  
  Plan for Days 29 and 30
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Focus&lt;/th&gt;
&lt;th&gt;Activities&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Day 29&lt;/td&gt;
&lt;td&gt;CLI + State&lt;/td&gt;
&lt;td&gt;Drill every command, run state operations on test resources, write practice questions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 30&lt;/td&gt;
&lt;td&gt;TFC + Final Review&lt;/td&gt;
&lt;td&gt;Complete TFC tutorials, review wrong answers, light review only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Specific topics to prioritise:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform state mv&lt;/code&gt; and &lt;code&gt;terraform state rm&lt;/code&gt; edge cases&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform plan -target&lt;/code&gt; dependency behaviour&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform apply -refresh-only&lt;/code&gt; use cases&lt;/li&gt;
&lt;li&gt;TFC workspace vs directory structure&lt;/li&gt;
&lt;li&gt;Sentinel policy execution flow&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;&lt;strong&gt;Practice exams are not about the score.&lt;/strong&gt; They're about finding gaps. My 74% and 77% are not impressive. But they showed me exactly where I need to study.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The wrong-answer review is the most valuable work.&lt;/strong&gt; Writing down why I was wrong forces me to confront the misunderstanding, not just memorise the right answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The warm-up effect is real.&lt;/strong&gt; My second score was higher. On exam day, I'll do a quick review session beforehand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hands-on beats reading.&lt;/strong&gt; Every question I got wrong about CLI flags, I fixed by running the actual command.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before today&lt;/th&gt;
&lt;th&gt;After today&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Thought I was ready&lt;/td&gt;
&lt;td&gt;Know exactly where my gaps are&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vague confidence&lt;/td&gt;
&lt;td&gt;Specific weak domains (CLI, state, TFC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No practice exam experience&lt;/td&gt;
&lt;td&gt;Two full simulations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two days left. CLI, state, and TFC are my focus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A wrong answer you have analysed properly will not fool you on exam day.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/certification/associate/sample-questions" rel="noopener noreferrer"&gt;Official Sample Questions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/cli/commands" rel="noopener noreferrer"&gt;Terraform CLI Commands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/state" rel="noopener noreferrer"&gt;Terraform State Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>exam</category>
      <category>prep</category>
    </item>
    <item>
      <title>Building a 3-Tier Multi-Region High Availability Architecture with Terraform</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Fri, 17 Apr 2026 09:06:36 +0000</pubDate>
      <link>https://dev.to/tink-origami/building-a-3-tier-multi-region-high-availability-architecture-with-terraform-5eki</link>
      <guid>https://dev.to/tink-origami/building-a-3-tier-multi-region-high-availability-architecture-with-terraform-5eki</guid>
      <description>&lt;h2&gt;
  
  
  From Single Region to Production-Grade Global Infrastructure
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 27 of the 30-Day Terraform Challenge&lt;/strong&gt; — and today I built something that can survive an entire AWS region going offline.&lt;/p&gt;

&lt;p&gt;Yesterday I built a scalable web app in one region. Today I built infrastructure that spans two regions, with automatic failover, cross-region database replication, and zero single points of failure.&lt;/p&gt;

&lt;p&gt;This is what production-grade looks like.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌─────────────────────────────────────────────────────────────┐
                    │                    Route53 Failover DNS                      │
                    │                   app.example.com                            │
                    └─────────────────────┬───────────────┬───────────────────────┘
                                          │               │
                    ┌─────────────────────▼───────────────▼───────────────────────┐
                    │                                                             │
                    │  PRIMARY REGION (us-east-1)    SECONDARY REGION (us-west-2) │
                    │                                                             │
                    │  ┌─────────────┐               ┌─────────────┐              │
                    │  │    ALB      │               │    ALB      │              │
                    │  └──────┬──────┘               └──────┬──────┘              │
                    │         │                             │                      │
                    │  ┌──────▼──────┐               ┌──────▼──────┐              │
                    │  │    ASG      │               │    ASG      │              │
                    │  │  (2-4 EC2)  │               │  (2-4 EC2)  │              │
                    │  └──────┬──────┘               └──────┬──────┘              │
                    │         │                             │                      │
                    │  ┌──────▼──────┐               ┌──────▼──────┐              │
                    │  │ RDS Multi-AZ│◄──────────────│ RDS Replica │              │
                    │  │  (Primary)  │   Replication  │  (Read-only)│              │
                    │  └─────────────┘               └─────────────┘              │
                    └─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's happening:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route53 health checks monitor both regions&lt;/li&gt;
&lt;li&gt;If primary fails, DNS automatically routes to secondary&lt;/li&gt;
&lt;li&gt;RDS cross-region replica keeps data in sync&lt;/li&gt;
&lt;li&gt;Each region has its own VPC, ALB, and Auto Scaling Group&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;day27-multi-region-ha/
├── modules/
│   ├── vpc/           # VPC, subnets, NAT gateways
│   ├── alb/           # Load balancer, target group
│   ├── asg/           # Auto Scaling, CloudWatch alarms
│   ├── rds/           # RDS instance with Multi-AZ and replicas
│   └── route53/       # DNS failover routing
├── envs/
│   └── prod/
│       ├── main.tf
│       ├── variables.tf
│       └── terraform.tfvars
├── backend.tf
└── provider.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five modules, each with a single responsibility. The VPC module doesn't know about the ALB. The ALB module doesn't know about the ASG. The calling configuration wires them together.&lt;/p&gt;




&lt;h2&gt;
  
  
  The VPC Module (Network Foundation)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# modules/vpc/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&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;vpc_cidr&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_support&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_hostnames&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_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"public"&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;public_subnet_cidrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&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;cidr_block&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;public_subnet_cidrs&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="nx"&gt;availability_zone&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;availability_zones&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="nx"&gt;map_public_ip_on_launch&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_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"private"&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;private_subnet_cidrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&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;cidr_block&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;private_subnet_cidrs&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="nx"&gt;availability_zone&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;availability_zones&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;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_nat_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"main"&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;public_subnet_cidrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;allocation_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_eip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nat&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="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&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="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why two subnet types:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public subnets&lt;/strong&gt; → ALB (needs internet access)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private subnets&lt;/strong&gt; → EC2 instances (no direct internet access)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT Gateways&lt;/strong&gt; → allow instances to download packages while remaining private&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The ALB Module (Traffic Distribution)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# modules/alb/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb"&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;name&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.name}-alb-${var.region}"&lt;/span&gt;
  &lt;span class="nx"&gt;load_balancer_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application"&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&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_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&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;subnets&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;subnet_ids&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;"web"&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.name}-tg-${var.region}"&lt;/span&gt;
  &lt;span class="nx"&gt;port&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;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_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;vpc_id&lt;/span&gt;

  &lt;span class="nx"&gt;health_check&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;"/health"&lt;/span&gt;
    &lt;span class="nx"&gt;interval&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
    &lt;span class="nx"&gt;healthy_threshold&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;unhealthy_threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The health check endpoint (&lt;code&gt;/health&lt;/code&gt;) is critical — Route53 uses it to determine if the region is healthy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ASG Module (Auto Scaling)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# modules/asg/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_autoscaling_group"&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;min_size&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;min_size&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;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max_size&lt;/span&gt;
  &lt;span class="nx"&gt;desired_capacity&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;desired_capacity&lt;/span&gt;
  &lt;span class="nx"&gt;target_group_arns&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;target_group_arns&lt;/span&gt;
  &lt;span class="nx"&gt;health_check_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ELB"&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;"cpu_high"&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;"web-cpu-high-${var.environment}-${var.region}"&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;70&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_autoscaling_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scale_out&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;&lt;strong&gt;The connection:&lt;/strong&gt; &lt;code&gt;target_group_arns&lt;/code&gt; links the ASG to the ALB. Without this, instances launch but never receive traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The RDS Module (Database Tier)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# modules/rds/main.tf&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;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;identifier&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;identifier&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;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;multi_az&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;multi_az&lt;/span&gt;
  &lt;span class="nx"&gt;storage_encrypted&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# For cross-region replica&lt;/span&gt;
  &lt;span class="nx"&gt;replicate_source_db&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;replicate_source_db&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Multi-AZ&lt;/strong&gt; (primary region): Synchronous replication to a standby in another AZ. Failover within minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-region replica&lt;/strong&gt; (secondary region): Asynchronous replication. Used for disaster recovery, not failover.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Route53 Module (DNS Failover)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# modules/route53/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_health_check"&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;fqdn&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;primary_alb_dns_name&lt;/span&gt;
  &lt;span class="nx"&gt;port&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;type&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_path&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/health"&lt;/span&gt;
  &lt;span class="nx"&gt;failure_threshold&lt;/span&gt; &lt;span class="p"&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_route53_record"&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;zone_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;hosted_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;set_identifier&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;health_check_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route53_health_check&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;id&lt;/span&gt;

  &lt;span class="nx"&gt;failover_routing_policy&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;"PRIMARY"&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="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;primary_alb_dns_name&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;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary_alb_zone_id&lt;/span&gt;
    &lt;span class="nx"&gt;evaluate_target_health&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;&lt;strong&gt;How failover works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Route53 health checks ping the ALB's &lt;code&gt;/health&lt;/code&gt; endpoint every 30 seconds&lt;/li&gt;
&lt;li&gt;After 3 failures (90 seconds), health check marks region as unhealthy&lt;/li&gt;
&lt;li&gt;Route53 stops sending traffic to primary, starts sending to secondary&lt;/li&gt;
&lt;li&gt;DNS TTL (60 seconds) + health check interval = ~2-3 minute failover&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Calling Configuration (Wiring Everything Together)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# envs/prod/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc_primary"&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/vpc"&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="c1"&gt;# ... VPC config&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;"alb_primary"&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/alb"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_primary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_primary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_subnet_ids&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;"asg_primary"&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/asg"&lt;/span&gt;
  &lt;span class="nx"&gt;target_group_arns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb_primary&lt;/span&gt;&lt;span class="p"&gt;.&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;launch_template_ami&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;primary_ami_id&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;"rds_primary"&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/rds"&lt;/span&gt;
  &lt;span class="nx"&gt;multi_az&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;module&lt;/span&gt; &lt;span class="s2"&gt;"rds_replica"&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/rds"&lt;/span&gt;
  &lt;span class="nx"&gt;is_replica&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;replicate_source_db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rds_primary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_instance_arn&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;"route53"&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/route53"&lt;/span&gt;
  &lt;span class="nx"&gt;primary_alb_dns_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb_primary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb_dns_name&lt;/span&gt;
  &lt;span class="nx"&gt;secondary_alb_dns_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb_secondary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb_dns_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The data flow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;VPC module outputs subnet IDs&lt;/li&gt;
&lt;li&gt;ALB module uses those to place the load balancer&lt;/li&gt;
&lt;li&gt;ASG module uses ALB's target group ARN to register instances&lt;/li&gt;
&lt;li&gt;RDS replica uses primary's ARN to set up replication&lt;/li&gt;
&lt;li&gt;Route53 uses both ALB DNS names for failover&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Deployment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;

Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 19 added, 1 changed, 0 destroyed.

Outputs:
alb_url &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://alb-us-east-1-234339925.eu-north-1.elb.amazonaws.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ALB distributes traffic to healthy instances&lt;/li&gt;
&lt;li&gt;ASG maintains 2-4 instances based on CPU&lt;/li&gt;
&lt;li&gt;CloudWatch alarms trigger scaling at 70% CPU&lt;/li&gt;
&lt;li&gt;RDS Multi-AZ protects against AZ failure&lt;/li&gt;
&lt;li&gt;Cross-region replica keeps secondary region in sync&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What happens during a region outage:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Health checks fail (90 seconds)&lt;/li&gt;
&lt;li&gt;Route53 stops sending traffic to primary&lt;/li&gt;
&lt;li&gt;Traffic shifts to secondary region&lt;/li&gt;
&lt;li&gt;Users continue accessing the application with minimal interruption&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;&lt;strong&gt;Multi-AZ ≠ cross-region.&lt;/strong&gt; Multi-AZ protects against AZ failure within a region. Cross-region replicas protect against full regional outages. You need both for true high availability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Health checks are critical.&lt;/strong&gt; Without them, Route53 has no way to know a region is down. Every ALB needs a &lt;code&gt;/health&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modules must be focused.&lt;/strong&gt; The VPC module shouldn't know about the ALB. The ALB module shouldn't know about the ASG. Each module does one thing well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The calling configuration is the "glue."&lt;/strong&gt; All the wiring happens in &lt;code&gt;envs/prod/main.tf&lt;/code&gt;. The modules stay generic and reusable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Protects Against&lt;/th&gt;
&lt;th&gt;Failover Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Multi-AZ RDS&lt;/td&gt;
&lt;td&gt;AZ failure&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-region replica&lt;/td&gt;
&lt;td&gt;Regional outage&lt;/td&gt;
&lt;td&gt;Manual promotion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto Scaling Group&lt;/td&gt;
&lt;td&gt;Instance failure&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route53 failover&lt;/td&gt;
&lt;td&gt;Regional outage&lt;/td&gt;
&lt;td&gt;2-3 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is what production-grade infrastructure looks like. No single points of failure. Automatic failover. Cross-region replication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One terraform apply. Two regions. Zero downtime.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>30daychallenge</category>
      <category>terraform</category>
      <category>aws</category>
    </item>
    <item>
      <title>Building a Scalable Web Application on AWS with EC2, ALB, and Auto Scaling using Terraform</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Thu, 16 Apr 2026 16:04:49 +0000</pubDate>
      <link>https://dev.to/tink-origami/building-a-scalable-web-application-on-aws-with-ec2-alb-and-auto-scaling-using-terraform-4k2a</link>
      <guid>https://dev.to/tink-origami/building-a-scalable-web-application-on-aws-with-ec2-alb-and-auto-scaling-using-terraform-4k2a</guid>
      <description>&lt;h2&gt;
  
  
  From Static Website to Dynamic, Auto-Scaling Infrastructure
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 26 of the 30-Day Terraform Challenge&lt;/strong&gt; — and today I graduated from static to dynamic infrastructure.&lt;/p&gt;

&lt;p&gt;Yesterday I deployed a static website on S3 with CloudFront. Today I built something that actually thinks for itself.&lt;/p&gt;

&lt;p&gt;A web application that grows when traffic increases and shrinks when it drops. All without human intervention. All managed through Terraform.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet → ALB (Port 80) → Auto Scaling Group → EC2 Instances (2-4)
                              ↓
                        CloudWatch Alarms
                              ↓
                    Scale Out at 70% CPU
                    Scale In at 30% CPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application Load Balancer&lt;/strong&gt; distributes traffic across instances&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto Scaling Group&lt;/strong&gt; maintains desired instance count (2-4)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Launch Template&lt;/strong&gt; defines instance configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Alarms&lt;/strong&gt; trigger scaling based on CPU utilization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three Terraform modules&lt;/strong&gt; keep everything DRY and reusable&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;day26-scalable-web-app/
├── modules/
│   ├── ec2/           # Launch Template + Security Group
│   ├── alb/           # Load Balancer + Target Group
│   └── asg/           # Auto Scaling Group + Scaling Policies
├── envs/
│   └── dev/
│       ├── main.tf
│       ├── variables.tf
│       └── terraform.tfvars
├── backend.tf
└── provider.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each module has a single responsibility. The EC2 module doesn't know about the ALB. The ALB module doesn't know about scaling. The ASG module connects them both.&lt;/p&gt;




&lt;h2&gt;
  
  
  The EC2 Module (Launch Template)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# modules/ec2/main.tf&lt;/span&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;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name_prefix&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-lt-${var.environment}-"&lt;/span&gt;
  &lt;span class="nx"&gt;image_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;ami_id&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;instance_type&lt;/span&gt;

  &lt;span class="nx"&gt;user_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;USERDATA&lt;/span&gt;&lt;span class="sh"&gt;
    #!/bin/bash
    yum update -y
    yum install -y httpd
    systemctl start httpd
    echo "&amp;lt;h1&amp;gt;Environment: ${var.environment}&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;Instance ID: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)&amp;lt;/p&amp;gt;" 
    &amp;gt; /var/www/html/index.html
&lt;/span&gt;&lt;span class="no"&gt;  USERDATA
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The launch template defines what every instance looks like: AMI, instance type, security groups, and the user data script that runs at boot.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ALB Module (Load Balancer)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# modules/alb/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb"&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;name&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.name}-alb-${var.environment}"&lt;/span&gt;
  &lt;span class="nx"&gt;load_balancer_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application"&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&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_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&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;subnets&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;subnet_ids&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;"web"&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.name}-tg-${var.environment}"&lt;/span&gt;
  &lt;span class="nx"&gt;port&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;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_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;vpc_id&lt;/span&gt;

  &lt;span class="nx"&gt;health_check&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;"/"&lt;/span&gt;
    &lt;span class="nx"&gt;healthy_threshold&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;unhealthy_threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ALB receives traffic and forwards it to healthy instances in the target group. The health check ensures traffic only goes to instances that actually respond.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ASG Module (Auto Scaling)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# modules/asg/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_autoscaling_group"&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;min_size&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;min_size&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;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max_size&lt;/span&gt;
  &lt;span class="nx"&gt;desired_capacity&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;desired_capacity&lt;/span&gt;
  &lt;span class="nx"&gt;target_group_arns&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;target_group_arns&lt;/span&gt;

  &lt;span class="nx"&gt;launch_template&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;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;launch_template_id&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;"$Latest"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;health_check_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ELB"&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;"cpu_high"&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;"web-cpu-high-${var.environment}"&lt;/span&gt;
  &lt;span class="nx"&gt;threshold&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;cpu_scale_out_threshold&lt;/span&gt;  &lt;span class="c1"&gt;# 70%&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_autoscaling_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scale_out&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;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;scaling_adjustment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ASG maintains the desired number of instances. When CPU exceeds 70%, the CloudWatch alarm triggers the scale-out policy, adding one instance. When CPU drops below 30%, it scales in.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Modules Collaborate
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.ec2 (Launch Template ID) ──► module.asg
                                      │
module.alb (Target Group ARN) ──────► module.asg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The calling configuration wires everything together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# envs/dev/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"ec2"&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/ec2"&lt;/span&gt;
  &lt;span class="nx"&gt;ami_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;ami_id&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="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"alb"&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/alb"&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;app_name&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_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;vpc_id&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_ids&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;public_subnet_ids&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;"asg"&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/asg"&lt;/span&gt;
  &lt;span class="nx"&gt;launch_template_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;launch_template_id&lt;/span&gt;
  &lt;span class="nx"&gt;target_group_arns&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;.&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;min_size&lt;/span&gt;           &lt;span class="p"&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="mi"&gt;4&lt;/span&gt;
  &lt;span class="nx"&gt;desired_capacity&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;target_group_arns&lt;/code&gt; input is critical. Without it, instances would launch but never receive traffic. The ALB wouldn't know they exist.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Deployment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;

Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 11 added, 0 changed, 0 destroyed.

Outputs:
alb_url &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://web-challenge-day26-alb-dev-1419490799.eu-north-1.elb.amazonaws.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ALB URL:&lt;/strong&gt; &lt;code&gt;http://web-challenge-day26-alb-dev-1419490799.eu-north-1.elb.amazonaws.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you see:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scalable Web App — Environment: dev
Instance ID: i-0abcd1234efgh5678
Deployed with Terraform on Day 26!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What happens behind the scenes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 instances initially (desired_capacity = 2)&lt;/li&gt;
&lt;li&gt;When CPU hits 70%, CloudWatch triggers scale-out → 3 instances&lt;/li&gt;
&lt;li&gt;When CPU drops to 30%, scale-in triggers → back to 2 instances&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; A single EC2 instance. If it crashes, the site goes down. If traffic spikes, users see errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; Auto Scaling Group with 2-4 instances. If one crashes, the ASG replaces it. If traffic spikes, the ASG adds capacity automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The business value:&lt;/strong&gt; You don't over-provision (paying for idle servers) and you don't under-provision (losing users during spikes).&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Module composition &amp;gt; monolithic config.&lt;/strong&gt; Three small modules are easier to understand, test, and reuse than one giant file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;health_check_type = "ELB"&lt;/code&gt; is critical.&lt;/strong&gt; Without it, the ASG only checks if the EC2 instance is running, not if the application is responding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudWatch alarms + scaling policies = auto-scaling.&lt;/strong&gt; The alarm detects the condition. The policy executes the action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The target group is the bridge.&lt;/strong&gt; The ALB sends traffic to the target group. The ASG registers instances with the target group. Without this connection, nothing works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Clean Up
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always destroy test infrastructure to avoid unexpected AWS charges.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Today I built infrastructure that adapts to traffic. No manual scaling. No over-provisioning. No downtime during spikes.&lt;/p&gt;

&lt;p&gt;Three modules working together: EC2 for instance configuration, ALB for traffic distribution, ASG for scaling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is what production-grade infrastructure looks like.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. The moment I saw CloudWatch trigger a scale-out and a new instance appear in the target group, I understood why teams love auto scaling. It's not magic. It's just well-designed infrastructure.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>aws</category>
      <category>ec2</category>
      <category>terraform</category>
      <category>30daychallenge</category>
    </item>
    <item>
      <title>Deploying a Static Website on AWS S3 with Terraform: A Beginner's Guide</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Thu, 16 Apr 2026 07:57:46 +0000</pubDate>
      <link>https://dev.to/tink-origami/deploying-a-static-website-on-aws-s3-with-terraform-a-beginners-guide-1gf5</link>
      <guid>https://dev.to/tink-origami/deploying-a-static-website-on-aws-s3-with-terraform-a-beginners-guide-1gf5</guid>
      <description>&lt;h2&gt;
  
  
  From Zero to a Live, Globally Distributed Website in One Day
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 25 of the 30-Day Terraform Challenge&lt;/strong&gt; — and today I built something I can actually show people.&lt;/p&gt;

&lt;p&gt;Not just infrastructure. Not just a cluster that responds to curl. An actual website. With a URL. That works in a browser.&lt;/p&gt;

&lt;p&gt;Using nothing but Terraform code.&lt;/p&gt;




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

&lt;p&gt;A fully serverless static website hosted on AWS S3, served globally through CloudFront, with HTTPS, custom error pages, and environment isolation.&lt;/p&gt;

&lt;p&gt;All deployed with one command: &lt;code&gt;terraform apply&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → CloudFront (HTTPS) → S3 Bucket (Static Files)
                              ├── index.html
                              └── error.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;S3 bucket&lt;/strong&gt; stores the HTML files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bucket policy&lt;/strong&gt; makes the files publicly readable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront&lt;/strong&gt; distributes content globally and adds HTTPS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform&lt;/strong&gt; manages everything as code&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;day25-static-website/
├── modules/
│   └── s3-static-website/
│       ├── main.tf          # S3 bucket + CloudFront
│       ├── variables.tf     # Configurable inputs
│       └── outputs.tf       # CloudFront URL
├── envs/
│   └── dev/
│       ├── main.tf          # Module call
│       ├── variables.tf
│       └── terraform.tfvars # Environment config
├── backend.tf               # Remote state
└── provider.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure enforces &lt;strong&gt;DRY&lt;/strong&gt; — the module is reusable, and the environment configuration is minimal.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Module (Reusable)
&lt;/h2&gt;

&lt;p&gt;The module encapsulates everything needed for a static website:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# modules/s3-static-website/main.tf&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;"website"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket_name&lt;/span&gt;
  &lt;span class="nx"&gt;force_destroy&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="err"&gt;!&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_website_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"website"&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="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website&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;index_document&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;suffix&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;index_document&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;error_document&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;key&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;error_document&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_cloudfront_distribution"&lt;/span&gt; &lt;span class="s2"&gt;"website"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;domain_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket_website_configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;website_endpoint&lt;/span&gt;
    &lt;span class="nx"&gt;origin_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3-website"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;default_cache_behavior&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;viewer_protocol_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"redirect-to-https"&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;&lt;strong&gt;Key design decisions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;force_destroy = var.environment != "production"&lt;/code&gt; — dev buckets can be destroyed easily, production buckets are protected&lt;/li&gt;
&lt;li&gt;CloudFront adds HTTPS automatically — no certificate needed for the default domain&lt;/li&gt;
&lt;li&gt;Module outputs the CloudFront URL so callers can access it&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Environment Configuration (Clean)
&lt;/h2&gt;

&lt;p&gt;Because the module does all the heavy lifting, the dev environment config is tiny:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# envs/dev/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"static_website"&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/s3-static-website"&lt;/span&gt;

  &lt;span class="nx"&gt;bucket_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;bucket_name&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;index_document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"index.html"&lt;/span&gt;
  &lt;span class="nx"&gt;error_document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"error.html"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cloudfront_url"&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;"https://${module.static_website.cloudfront_domain_name}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# envs/dev/terraform.tfvars&lt;/span&gt;
&lt;span class="nx"&gt;bucket_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-terraform-website-day25-20260416"&lt;/span&gt;
&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. All the complexity lives in the module.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Deployment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;envs/dev
terraform init
terraform plan
terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;

Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 6 added, 0 changed, 0 destroyed.

Outputs:
cloudfront_url &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://d123.cloudfront.net"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;After waiting 10 minutes for CloudFront to propagate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://d123.cloudfront.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;&lt;/span&gt;Terraform Static Website&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;🚀 Day 25: Deployed with Terraform!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Environment: dev&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This website was deployed using Terraform on Day 25!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A live, globally distributed website. Deployed entirely through code.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;It's real.&lt;/strong&gt; Not a demo. Not a "hello world" that only works on localhost. A real website with a real URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's complete.&lt;/strong&gt; S3 + CloudFront + HTTPS + error handling + environment isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's reusable.&lt;/strong&gt; The module can deploy dev, staging, and production with different variables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It demonstrates everything from Days 1-24:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modules (Day 8-9)&lt;/li&gt;
&lt;li&gt;Remote state (Day 6)&lt;/li&gt;
&lt;li&gt;DRY principle (Day 4)&lt;/li&gt;
&lt;li&gt;Environment isolation (Day 7)&lt;/li&gt;
&lt;li&gt;Tags and best practices (Day 16)&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;&lt;strong&gt;S3 static websites are simple but have limits.&lt;/strong&gt; No HTTPS on the S3 endpoint — that's why you need CloudFront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudFront takes time.&lt;/strong&gt; 5-15 minutes to propagate globally. Be patient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The module pattern is powerful.&lt;/strong&gt; I can now deploy a static website in any environment with 5 lines of code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remote state protects your work.&lt;/strong&gt; The state file is encrypted in S3, not on my laptop.&lt;/p&gt;




&lt;h2&gt;
  
  
  The DRY Principle in Practice
&lt;/h2&gt;

&lt;p&gt;Without a module, I would have written 100+ lines of S3 + CloudFront configuration for every environment. With a module, each environment needs only 5 lines.&lt;/p&gt;

&lt;p&gt;That's the difference between a one-off script and production-grade infrastructure.&lt;/p&gt;




</description>
      <category>terraform</category>
      <category>aws</category>
      <category>s3</category>
      <category>static</category>
    </item>
    <item>
      <title>My Final Preparation for the Terraform Associate Exam</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Mon, 13 Apr 2026 15:15:27 +0000</pubDate>
      <link>https://dev.to/tink-origami/my-final-preparation-for-the-terraform-associate-exam-45go</link>
      <guid>https://dev.to/tink-origami/my-final-preparation-for-the-terraform-associate-exam-45go</guid>
      <description>&lt;h2&gt;
  
  
  60 Minutes. 57 Questions. No Looking Back.
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 24 of the 30-Day Terraform Challenge&lt;/strong&gt; — and today I did something I've been dreading for weeks.&lt;/p&gt;

&lt;p&gt;I simulated the real exam.&lt;/p&gt;

&lt;p&gt;No notes. No pauses. No second chances to look something up.&lt;/p&gt;

&lt;p&gt;Just me, 57 questions, and a 60-minute timer.&lt;/p&gt;

&lt;p&gt;Here's what happened, what I learned, and my plan for exam day.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Simulation Results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Score:&lt;/strong&gt; 43/57 (75%) — just above the 70% passing threshold&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time remaining:&lt;/strong&gt; 4 minutes&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions flagged for review:&lt;/strong&gt; 12&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domains I struggled with:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform CLI (26% weight) — missed 5 questions&lt;/li&gt;
&lt;li&gt;State management (8% weight) — missed 3 questions&lt;/li&gt;
&lt;li&gt;Terraform Cloud (4% weight) — missed 2 questions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CLI section is what almost got me. The questions about &lt;code&gt;-target&lt;/code&gt;, &lt;code&gt;-refresh-only&lt;/code&gt;, and &lt;code&gt;terraform state mv&lt;/code&gt; were harder than I expected.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned From My Mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The CLI traps:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform plan -target&lt;/code&gt; doesn't just plan the targeted resource — it also plans any resources that depend on it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform state mv&lt;/code&gt; requires the full resource address, not just the name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform apply -refresh-only&lt;/code&gt; updates state to match real infrastructure without changing anything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The state traps:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A stale state file can cause &lt;code&gt;terraform plan&lt;/code&gt; to show no changes even when infrastructure has drifted&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform refresh&lt;/code&gt; is deprecated — use &lt;code&gt;terraform apply -refresh-only&lt;/code&gt; instead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Terraform Cloud traps:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sentinel policies run after plan, before apply&lt;/li&gt;
&lt;li&gt;Workspaces in TFC are separate state files, not separate directories&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Flash Card Answers (Without Looking)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. What file does &lt;code&gt;terraform init&lt;/code&gt; create to record provider versions?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;.terraform.lock.hcl&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Difference between &lt;code&gt;terraform.workspace&lt;/code&gt; and a Terraform Cloud workspace?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;terraform.workspace&lt;/code&gt; is an expression used in config to get current workspace name. TFC workspace is a separate state file with collaboration features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. If you run &lt;code&gt;terraform state rm aws_instance.web&lt;/code&gt;, what happens to the EC2 instance?&lt;/strong&gt;&lt;br&gt;
Nothing — it continues running. Only removed from state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. What does &lt;code&gt;depends_on&lt;/code&gt; do and when should you use it?&lt;/strong&gt;&lt;br&gt;
Creates explicit dependency when Terraform can't infer it. Use when a resource needs to wait for another that isn't referenced in its arguments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Purpose of &lt;code&gt;.terraform.lock.hcl&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
Locks provider versions so every team member uses the same version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. How does &lt;code&gt;for_each&lt;/code&gt; differ from &lt;code&gt;count&lt;/code&gt; when items are removed from middle?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;count&lt;/code&gt; reindexes and recreates subsequent resources. &lt;code&gt;for_each&lt;/code&gt; keys by value, so only removed item is affected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. What does &lt;code&gt;terraform apply -refresh-only&lt;/code&gt; do?&lt;/strong&gt;&lt;br&gt;
Updates state file to match real infrastructure without modifying resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Maximum items in a single &lt;code&gt;terraform import&lt;/code&gt; command?&lt;/strong&gt;&lt;br&gt;
One — you can only import one resource at a time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. What happens when you run &lt;code&gt;terraform plan&lt;/code&gt; against a workspace that has never been applied?&lt;/strong&gt;&lt;br&gt;
It shows all resources as "to be created" since state is empty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. What does &lt;code&gt;prevent_destroy&lt;/code&gt; do and what does it NOT prevent?&lt;/strong&gt;&lt;br&gt;
Blocks &lt;code&gt;terraform destroy&lt;/code&gt; from deleting the resource. Does NOT prevent manual deletion in AWS Console.&lt;/p&gt;




&lt;h2&gt;
  
  
  High-Weight Domain Drill
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Terraform basics (24%):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;templatefile()&lt;/code&gt; reads a template file and renders it with variables — useful for user_data scripts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;merge()&lt;/code&gt; combines multiple maps; later keys overwrite earlier ones&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tomap()&lt;/code&gt; converts a list of objects to a map, but fails if keys aren't unique&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Terraform CLI (26%):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform init -upgrade&lt;/code&gt; forces provider version upgrades even when lock file pins them&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform apply -auto-approve&lt;/code&gt; skips interactive approval — never use in production&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform plan -out=file.tfplan&lt;/code&gt; saves plan to apply exactly later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;IaC concepts (16%):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Declarative = describe desired state, system figures out how to achieve it&lt;/li&gt;
&lt;li&gt;Idempotency = applying same config multiple times produces same result&lt;/li&gt;
&lt;li&gt;Drift = difference between declared state and actual state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;State management (8%):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State is the source of truth — Terraform compares config to state, not directly to AWS&lt;/li&gt;
&lt;li&gt;State locking prevents concurrent writes using DynamoDB&lt;/li&gt;
&lt;li&gt;State versioning in S3 allows recovery from corruption&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Common Exam Traps (I Added 3 More)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. "Terraform plan shows no changes" doesn't mean infrastructure is correct&lt;/strong&gt; — stale state can mask drift&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;terraform destroy&lt;/code&gt; vs &lt;code&gt;terraform state rm&lt;/code&gt;&lt;/strong&gt; — destroy deletes real resources, state rm only removes from state&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Module source &lt;code&gt;?ref=main&lt;/code&gt; vs &lt;code&gt;?ref=v1.0.0&lt;/code&gt;&lt;/strong&gt; — branch is mutable, tag is immutable. Always pin to tags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;sensitive = true&lt;/code&gt; does NOT prevent secrets from being stored in state&lt;/strong&gt; — only masks terminal output&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Multi-select questions&lt;/strong&gt; — if it says "select TWO," exactly two. One or three = wrong regardless of which you picked&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. &lt;code&gt;terraform plan -target&lt;/code&gt; includes dependencies&lt;/strong&gt; — not just the targeted resource&lt;/p&gt;




&lt;h2&gt;
  
  
  Exam-Day Strategy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read the question twice&lt;/strong&gt; before looking at answers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spend max 90 seconds per question&lt;/strong&gt; — flag and move on if stuck&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eliminate clearly wrong answers first&lt;/strong&gt; — often gets you from 4 to 2 options&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flag any question you're unsure about&lt;/strong&gt; — don't waste time overthinking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answer all flagged questions in remaining time&lt;/strong&gt; — even guessing beats leaving blank&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for "select TWO" instructions&lt;/strong&gt; — don't over-select or under-select&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust your gut on first pass&lt;/strong&gt; — your first instinct is usually right&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Remaining Red Topics
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Terraform Cloud features&lt;/strong&gt; — still red. I've used S3 backend, not TFC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How I'll address this:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete TFC "Get Started" tutorials (1 hour)&lt;/li&gt;
&lt;li&gt;Create a free TFC account and run a remote plan&lt;/li&gt;
&lt;li&gt;Write 10 practice questions about TFC features&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;75% on my first simulation. Not great. Not failing.&lt;/p&gt;

&lt;p&gt;The CLI section is my biggest risk. The questions about specific flags and edge cases are harder than I expected.&lt;/p&gt;

&lt;p&gt;But I know exactly where my gaps are now. And I have six days to close them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whatever the score is on exam day, I know this material better than I did 24 days ago.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's go.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/certification/associate" rel="noopener noreferrer"&gt;Official Study Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/certification/associate/sample-questions" rel="noopener noreferrer"&gt;Official Sample Questions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devjournal</category>
      <category>devops</category>
      <category>learning</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Preparing for the Terraform Associate Exam — Key Resources and Tips</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Mon, 13 Apr 2026 06:47:25 +0000</pubDate>
      <link>https://dev.to/tink-origami/preparing-for-the-terraform-associate-exam-key-resources-and-tips-4146</link>
      <guid>https://dev.to/tink-origami/preparing-for-the-terraform-associate-exam-key-resources-and-tips-4146</guid>
      <description>&lt;h2&gt;
  
  
  How I Audited My Gaps and Built a Study Plan
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 23 of the 30-Day Terraform Challenge&lt;/strong&gt; — and today I shifted from building infrastructure to preparing for the certification.&lt;/p&gt;

&lt;p&gt;After 22 days of hands-on work, I thought I was ready. Then I looked at the official exam objectives.&lt;/p&gt;

&lt;p&gt;I wasn't as ready as I thought.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Domain Audit
&lt;/h2&gt;

&lt;p&gt;The exam covers 9 domains with different weights. Here's my honest self-assessment:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;th&gt;My Rating&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IaC concepts&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;td&gt;🟢 Green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform's purpose&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;🟢 Green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform basics&lt;/td&gt;
&lt;td&gt;24%&lt;/td&gt;
&lt;td&gt;🟢 Green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform CLI&lt;/td&gt;
&lt;td&gt;26%&lt;/td&gt;
&lt;td&gt;🟡 Yellow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modules&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;td&gt;🟢 Green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core workflow&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;td&gt;🟢 Green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State management&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;td&gt;🟡 Yellow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;td&gt;🟢 Green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform Cloud&lt;/td&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;td&gt;🔴 Red&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Green&lt;/strong&gt; = I can explain and have done it hands-on.&lt;br&gt;
&lt;strong&gt;Yellow&lt;/strong&gt; = I understand conceptually but need practice.&lt;br&gt;
&lt;strong&gt;Red&lt;/strong&gt; = I need to learn this.&lt;/p&gt;

&lt;p&gt;The CLI and state sections are more detailed than I expected. Terraform Cloud is almost entirely new to me.&lt;/p&gt;


&lt;h2&gt;
  
  
  CLI Commands — Know Them Cold
&lt;/h2&gt;

&lt;p&gt;The exam tests specific command flags. You need to know what each command does and when to use it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Downloads providers, initializes backend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform validate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks syntax and consistency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform fmt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Formats code to canonical style&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows proposed changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates or updates infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform destroy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes all resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads outputs from state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform state list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lists resources in state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform state show&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows resource attributes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform state mv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Moves resource between states&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform state rm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes from state (no destroy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform import&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Imports existing resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform workspace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manages multiple state files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform providers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows provider versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform graph&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Outputs dependency graph&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The trickiest is understanding what &lt;code&gt;terraform state rm&lt;/code&gt; does to real infrastructure — nothing. It only removes from state.&lt;/p&gt;


&lt;h2&gt;
  
  
  Non-Cloud Providers
&lt;/h2&gt;

&lt;p&gt;Terraform isn't just for AWS. The random and local providers appear frequently on the exam:&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;"suffix"&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;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"db_pass"&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="mi"&gt;16&lt;/span&gt;
  &lt;span class="nx"&gt;special&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;"local_file"&lt;/span&gt; &lt;span class="s2"&gt;"config"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Password: ${random_password.db_pass.result}"&lt;/span&gt;
  &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/config.txt"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; The exam expects you to know Terraform works with DNS, TLS, random values, and local files — not just cloud providers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Terraform Cloud — My Biggest Gap
&lt;/h2&gt;

&lt;p&gt;I used S3 + DynamoDB for remote state. Terraform Cloud has features I haven't touched:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remote runs (vs local runs)&lt;/li&gt;
&lt;li&gt;Sentinel policies (run after plan, before apply)&lt;/li&gt;
&lt;li&gt;Private module registry&lt;/li&gt;
&lt;li&gt;Workspace-specific variables&lt;/li&gt;
&lt;li&gt;Cost estimation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is only 4% of the exam, but it's 100% new to me.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Study Plan (Days 24-30)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Study Method&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;terraform state commands&lt;/td&gt;
&lt;td&gt;Run each command on test resources&lt;/td&gt;
&lt;td&gt;45 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentinel policies&lt;/td&gt;
&lt;td&gt;Read docs + write 2 policies&lt;/td&gt;
&lt;td&gt;60 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform Cloud&lt;/td&gt;
&lt;td&gt;Complete TFC lab exercises&lt;/td&gt;
&lt;td&gt;90 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI flags (-out, -target)&lt;/td&gt;
&lt;td&gt;Practice each flag&lt;/td&gt;
&lt;td&gt;45 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random/local providers&lt;/td&gt;
&lt;td&gt;Build example configs&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Official sample questions&lt;/td&gt;
&lt;td&gt;Complete all&lt;/td&gt;
&lt;td&gt;60 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Practice Questions — Write Your Own
&lt;/h2&gt;

&lt;p&gt;Writing questions forces you to understand the material deeply. Here's one I wrote:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; You run &lt;code&gt;terraform state rm aws_instance.web&lt;/code&gt;. What happens to the actual EC2 instance?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A) Terminated immediately&lt;/li&gt;
&lt;li&gt;B) Removed from state only (continues running) ✅&lt;/li&gt;
&lt;li&gt;C) Stopped&lt;/li&gt;
&lt;li&gt;D) Removed from state and terminated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why the wrong answers are wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A, C, D describe actions that affect real infrastructure&lt;/li&gt;
&lt;li&gt;Only B correctly states that &lt;code&gt;state rm&lt;/code&gt; affects only the state file&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Official Sample Questions
&lt;/h2&gt;

&lt;p&gt;I scored 8/10 on my first attempt. The two I missed were about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sentinel policy execution timing (runs after plan, before apply)&lt;/li&gt;
&lt;li&gt;Terraform Cloud workspace vs directory structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These went straight into my study plan.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;The CLI section is more detailed than most people expect.&lt;/strong&gt; You need to know specific flags, not just commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-cloud providers are tested.&lt;/strong&gt; Don't skip the random and local providers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform Cloud is different from open source.&lt;/strong&gt; If you only used S3 backend, you need to study TFC separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing practice questions is the best study technique.&lt;/strong&gt; It forces you to think like the exam writer.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;The exam tests specific knowledge, not just experience. You can build infrastructure for weeks and still miss questions about &lt;code&gt;terraform state mv&lt;/code&gt; or Sentinel policy syntax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My advice:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Print the official study guide&lt;/li&gt;
&lt;li&gt;Audit yourself honestly against every objective&lt;/li&gt;
&lt;li&gt;Build a concrete study plan for your gaps&lt;/li&gt;
&lt;li&gt;Write your own practice questions&lt;/li&gt;
&lt;li&gt;Don't underestimate the CLI section&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/certification/associate" rel="noopener noreferrer"&gt;Official Study Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/certification/associate/sample-questions" rel="noopener noreferrer"&gt;Official Sample Questions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/cli/commands" rel="noopener noreferrer"&gt;Terraform CLI Commands&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>exam</category>
      <category>terraform</category>
      <category>30daychallenge</category>
    </item>
    <item>
      <title>Putting It All Together: My 22-Day Terraform Journey</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Mon, 13 Apr 2026 06:26:35 +0000</pubDate>
      <link>https://dev.to/tink-origami/putting-it-all-together-my-22-day-terraform-journey-hlh</link>
      <guid>https://dev.to/tink-origami/putting-it-all-together-my-22-day-terraform-journey-hlh</guid>
      <description>&lt;h2&gt;
  
  
  From "What's a provider?" to a Complete CI/CD Pipeline
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 22 of the 30-Day Terraform Challenge&lt;/strong&gt; — and I can't believe how far I've come.&lt;/p&gt;

&lt;p&gt;Twenty-two days ago, I didn't know what a provider alias was. I hardcoded instance types. I thought &lt;code&gt;terraform destroy&lt;/code&gt; was the scariest command in the world.&lt;/p&gt;

&lt;p&gt;Today, I have a complete integrated pipeline. And I'm about to finish the book.&lt;/p&gt;

&lt;p&gt;Here's what I built. What I learned. And what surprised me most.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Journey in 22 Days
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Days&lt;/th&gt;
&lt;th&gt;What I Built&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Introduction to the challenge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Setting up AWS CLI, credentials, Terraform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;First EC2 instance with user data script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Configurable web server with variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5-6&lt;/td&gt;
&lt;td&gt;Auto Scaling Group + Application Load Balancer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Remote state with S3 + DynamoDB locking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8-9&lt;/td&gt;
&lt;td&gt;Reusable modules (webserver cluster)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Loops (&lt;code&gt;count&lt;/code&gt;, &lt;code&gt;for_each&lt;/code&gt;) and conditionals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Environment-aware module (dev vs prod)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Zero-downtime deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Secrets management with AWS Secrets Manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14-15&lt;/td&gt;
&lt;td&gt;Multiple providers (multi-region, EKS, Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Production-grade refactor (tags, alarms, validation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Manual testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;Automated testing (terraform test + Terratest)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;IaC adoption strategy for teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20-21&lt;/td&gt;
&lt;td&gt;Application + infrastructure deployment workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;Integrated CI/CD pipeline + Sentinel policies&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's a complete infrastructure platform. Most engineers don't build this much in their first year.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built (The Highlights)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Week 1 (Days 1-7):&lt;/strong&gt; I went from a single hardcoded EC2 instance to a configurable, load-balanced, auto-scaling cluster with remote state. The jump from Day 3 to Day 5 was the biggest learning curve — understanding ASGs and ALBs took hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 2 (Days 8-14):&lt;/strong&gt; I built reusable modules, added conditionals for multi-environment deployments, learned zero-downtime with &lt;code&gt;create_before_destroy&lt;/code&gt;, and deployed an EKS cluster with Kubernetes. The EKS section was the most complex — 68 resources, 15 minutes of waiting, and a lot of coffee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 3 (Days 15-22):&lt;/strong&gt; I refactored everything to production-grade standards (tags, alarms, validation), wrote manual and automated tests, designed an adoption strategy for teams, and finally built an integrated CI/CD pipeline with Sentinel policies.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Integrated Pipeline
&lt;/h2&gt;

&lt;p&gt;My final workflow combines everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Infrastructure CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform fmt -check -recursive&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform init -backend=false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform validate&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform test&lt;/span&gt;

  &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;validate&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform plan -out=ci.tfplan&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform-plan&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ci.tfplan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Format check on every PR&lt;/li&gt;
&lt;li&gt;Validation and unit tests&lt;/li&gt;
&lt;li&gt;Plan generation saved as immutable artifact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same plan can be promoted from dev to prod — no regeneration, no drift.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sentinel Policies
&lt;/h2&gt;

&lt;p&gt;I wrote two policies to enforce standards:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policy 1: Require ManagedBy tag&lt;/strong&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;main&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;tfplan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_changes&lt;/span&gt; &lt;span class="nx"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;after&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="s2"&gt;"ManagedBy"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&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;&lt;strong&gt;Policy 2: Allow only approved instance types&lt;/strong&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;allowed_types&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"t3.small"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"t3.medium"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These caught several violations in my early code — security groups without tags, t2.micro instances — and forced me to fix them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Side-by-Side Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Application Code&lt;/th&gt;
&lt;th&gt;Infrastructure Code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source of truth&lt;/td&gt;
&lt;td&gt;Git repository&lt;/td&gt;
&lt;td&gt;Git repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local run&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm start&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Artifact&lt;/td&gt;
&lt;td&gt;Docker image&lt;/td&gt;
&lt;td&gt;Saved &lt;code&gt;.tfplan&lt;/code&gt; file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Versioning&lt;/td&gt;
&lt;td&gt;Semantic tag&lt;/td&gt;
&lt;td&gt;Semantic tag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automated tests&lt;/td&gt;
&lt;td&gt;Unit + integration&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;terraform test&lt;/code&gt; + Terratest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy enforcement&lt;/td&gt;
&lt;td&gt;Linting&lt;/td&gt;
&lt;td&gt;Sentinel policies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;CI/CD pipeline&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform apply &amp;lt;plan&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback&lt;/td&gt;
&lt;td&gt;Redeploy previous image&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform apply &amp;lt;previous plan&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The parallel is striking. Infrastructure code follows the same discipline as application code.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changed in How I Think
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The single biggest mental shift:&lt;/strong&gt; Infrastructure is not a one-time setup. It's a software product.&lt;/p&gt;

&lt;p&gt;Before: "I'll set up the servers once and never touch them again."&lt;/p&gt;

&lt;p&gt;After: "Every infrastructure change goes through version control, review, testing, and CI/CD — just like application code."&lt;/p&gt;

&lt;p&gt;Infrastructure isn't a project. It's a product that needs continuous improvement.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Was Harder Than Expected
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;State management.&lt;/strong&gt; Not the technical part — the understanding that state is the source of truth. A corrupted state file is worse than corrupted code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing.&lt;/strong&gt; Unit tests were easy. Integration tests (Terratest) were hard — writing Go code, handling timeouts, waiting for ALBs to provision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-region deployments.&lt;/strong&gt; I thought adding a second region would be trivial. Provider aliases, module design, remote state — everything got more complicated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EKS.&lt;/strong&gt; The cluster took 15 minutes to provision, and &lt;code&gt;kubectl&lt;/code&gt; authentication was a nightmare. But it worked.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Would Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Week 1:&lt;/strong&gt; Create a &lt;code&gt;.gitignore&lt;/code&gt; before writing any code. I committed state files and provider binaries. Embarrassing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 2:&lt;/strong&gt; Write unit tests earlier. I waited until Day 18. Testing from Day 1 would have caught bugs earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 3:&lt;/strong&gt; Separate state buckets by environment earlier. One bucket for everything was fine for learning but dangerous for production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overall:&lt;/strong&gt; Read the book before starting the challenge, not during. But that's cheating.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Terraform Associate Certification.&lt;/strong&gt; I've been studying the exam objectives. My weak areas are Terraform Cloud features (I used S3 backend) and Sentinel policies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First real project:&lt;/strong&gt; Refactor my team's development environment. Five developers, each with a manually configured AWS sandbox. I'll write a module that provisions identical sandboxes for everyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Longer term:&lt;/strong&gt; Contribute to open source Terraform modules. I've learned enough to help others.&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 10's Most Important Insight
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Immutable versioned artifacts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The same Docker image that passes tests in CI gets promoted to staging, then production. No rebuilding. No "but it worked in dev."&lt;/p&gt;

&lt;p&gt;For infrastructure, the saved &lt;code&gt;.tfplan&lt;/code&gt; file is that immutable artifact. The plan reviewed in the PR is the exact plan applied in production. No drift. No surprises.&lt;/p&gt;

&lt;p&gt;This insight changes everything. Infrastructure deployment becomes predictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Twenty-two days ago, I was afraid of &lt;code&gt;terraform destroy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Today, I trust it because I know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State is backed up in versioned S3&lt;/li&gt;
&lt;li&gt;Plans are reviewed before apply&lt;/li&gt;
&lt;li&gt;Tests run automatically&lt;/li&gt;
&lt;li&gt;Sentinel enforces rules&lt;/li&gt;
&lt;li&gt;Rollback is one command away&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure as Code isn't just a tool. It's a discipline.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And it's one I'll carry into every project from now on.&lt;/p&gt;




</description>
      <category>aws</category>
      <category>beginners</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>A Workflow for Deploying Infrastructure Code with Terraform</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Sun, 12 Apr 2026 15:08:49 +0000</pubDate>
      <link>https://dev.to/tink-origami/a-workflow-for-deploying-infrastructure-code-with-terraform-40p6</link>
      <guid>https://dev.to/tink-origami/a-workflow-for-deploying-infrastructure-code-with-terraform-40p6</guid>
      <description>&lt;h2&gt;
  
  
  Seven Steps. One Plan File. Zero Surprises.
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 21 of the 30-Day Terraform Challenge&lt;/strong&gt; — and today I learned that deploying infrastructure code safely requires discipline that application deployments don't need.&lt;/p&gt;

&lt;p&gt;Yesterday I mapped the seven-step application workflow. Today I applied it to infrastructure — and discovered where the two workflows diverge and why those differences matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Seven-Step Infrastructure Workflow
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Version control (Git)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Run locally (&lt;code&gt;terraform plan -out&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Make code changes (feature branch)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Submit for review (PR with plan output)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Run automated tests (CI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Merge and release (tag)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Deploy (apply saved plan file)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Step 1: Version Control
&lt;/h2&gt;

&lt;p&gt;Every infrastructure change starts in Git. Not in the AWS Console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Day 21: Initial web server"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Every change has an author, a timestamp, and a reason. No more mystery infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Run Locally — The Infrastructure Difference
&lt;/h2&gt;

&lt;p&gt;For application code, running locally means executing the binary. For infrastructure, it means running &lt;code&gt;terraform plan&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;day21.tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The critical difference:&lt;/strong&gt; You're not running the code. You're generating a diff against your state file. The plan shows exactly what will change in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Make Code Changes
&lt;/h2&gt;

&lt;p&gt;Create a feature branch and make your change. I added a CloudWatch alarm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; add-cloudwatch-alarm
&lt;span class="c"&gt;# Edit main.tf to add the alarm resource&lt;/span&gt;
git add main.tf
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add CloudWatch CPU alarm"&lt;/span&gt;
git push origin add-cloudwatch-alarm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Submit for Review — The Infrastructure Difference
&lt;/h2&gt;

&lt;p&gt;For application code, you review the code diff. For infrastructure, you review the plan output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My PR description:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## What this changes&lt;/span&gt;
Adds a CloudWatch metric alarm for CPU utilization

&lt;span class="gu"&gt;## Terraform plan output&lt;/span&gt;
Plan: 1 to add, 0 to change, 0 to destroy.

&lt;span class="gu"&gt;## Resources affected&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Created: 1 (CloudWatch alarm)

&lt;span class="gu"&gt;## Blast radius&lt;/span&gt;
Low. Only adds monitoring. No impact on existing resources.

&lt;span class="gu"&gt;## Rollback plan&lt;/span&gt;
Remove the alarm resource and re-apply.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; The reviewer sees exactly what will change in production without running Terraform themselves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Run Automated Tests
&lt;/h2&gt;

&lt;p&gt;GitHub Actions runs &lt;code&gt;terraform validate&lt;/code&gt;, &lt;code&gt;terraform fmt --check&lt;/code&gt;, and unit tests automatically on every PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Catch syntax and formatting errors before a human ever reviews the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Merge and Release
&lt;/h2&gt;

&lt;p&gt;After approval, merge and tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout main
git pull origin main
git tag &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"v1.1.0"&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add CloudWatch CPU alarm"&lt;/span&gt;
git push origin v1.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Tags create rollback points. Every release is versioned.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Deploy — The Infrastructure Difference
&lt;/h2&gt;

&lt;p&gt;For application code, you deploy from CI. For infrastructure, you apply the saved plan file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply day21.tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The critical difference:&lt;/strong&gt; Using the saved plan guarantees that exactly what was reviewed is what gets applied. No surprises. No drift between plan and apply.&lt;/p&gt;




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

&lt;p&gt;A web server with a CloudWatch alarm:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EC2 instance&lt;/td&gt;
&lt;td&gt;Running, serving HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security group&lt;/td&gt;
&lt;td&gt;Allow port 80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudWatch alarm&lt;/td&gt;
&lt;td&gt;Monitoring CPU &amp;gt; 80%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Verification:&lt;/strong&gt;&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="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://56.228.18.125
&amp;lt;h1&amp;gt;Day 21: Infrastructure Deployment Workflow&amp;lt;/h1&amp;gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;aws cloudwatch describe-alarms &lt;span class="nt"&gt;--alarm-names&lt;/span&gt; day21-high-cpu
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"AlarmName"&lt;/span&gt;: &lt;span class="s2"&gt;"day21-high-cpu"&lt;/span&gt;,
    &lt;span class="s2"&gt;"StateValue"&lt;/span&gt;: &lt;span class="s2"&gt;"OK"&lt;/span&gt;,
    &lt;span class="s2"&gt;"Threshold"&lt;/span&gt;: 80.0
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Infrastructure-Specific Safeguards
&lt;/h2&gt;

&lt;p&gt;These have no equivalent in application code deployment:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Plan File Pinning
&lt;/h3&gt;

&lt;p&gt;Always apply from a saved plan, never from a fresh plan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Correct — apply exactly what was reviewed&lt;/span&gt;
terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;reviewed.tfplan
terraform apply reviewed.tfplan

&lt;span class="c"&gt;# Risky — the plan may differ&lt;/span&gt;
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Blast Radius Documentation
&lt;/h3&gt;

&lt;p&gt;Every PR must document what breaks if the apply fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. State Backup
&lt;/h3&gt;

&lt;p&gt;S3 bucket versioning must be enabled. Know how to restore a previous state.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Approval Gates for Destructive Changes
&lt;/h3&gt;

&lt;p&gt;Any plan showing resource destruction requires explicit approval beyond PR review.&lt;/p&gt;




&lt;h2&gt;
  
  
  Infrastructure vs Application: Key Differences
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Difference&lt;/th&gt;
&lt;th&gt;Why It Exists&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plan files, not binaries&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Infrastructure changes affect real resources; plan must be reviewed before apply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Blast radius&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A bad infrastructure deploy can destroy production data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Terraform tracks real resources; state corruption breaks everything&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Most Dangerous Step
&lt;/h2&gt;

&lt;p&gt;The gap between &lt;code&gt;terraform plan&lt;/code&gt; and &lt;code&gt;terraform apply&lt;/code&gt; is where things go wrong. If infrastructure changes between plan and apply, the plan becomes invalid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The safeguard:&lt;/strong&gt; Save the plan file and apply it directly. Never run &lt;code&gt;terraform apply&lt;/code&gt; without a plan file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Safe&lt;/span&gt;
terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;day21.tfplan
terraform apply day21.tfplan

&lt;span class="c"&gt;# Risky&lt;/span&gt;
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;&lt;strong&gt;Plan output is the most important review artifact.&lt;/strong&gt; Include it in every PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blast radius matters.&lt;/strong&gt; A bad infrastructure deploy can break more than a bad code deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plan file pinning is non-negotiable.&lt;/strong&gt; Without it, you're applying something that might not match what was reviewed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State is the source of truth.&lt;/strong&gt; Protect it with versioning and backups.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Deploying infrastructure code requires the same seven steps as application code — plus infrastructure-specific safeguards.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Console clicks&lt;/td&gt;
&lt;td&gt;Pull requests with plan output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Who changed this?"&lt;/td&gt;
&lt;td&gt;Git blame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hope it works&lt;/td&gt;
&lt;td&gt;Plan file proves it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can't roll back&lt;/td&gt;
&lt;td&gt;Tags and state versioning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Seven steps. Plan files. Blast radius documentation. State backups.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Infrastructure deployment shouldn't be risky. It should be reviewed, tested, and applied exactly as planned.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;P.S. The moment I applied a saved plan file and saw exactly what was reviewed happen in production, I understood why teams adopt this workflow. It's not slower. It's safer.&lt;/em&gt; 🔧&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>terraform</category>
      <category>30daychallenge</category>
      <category>aws</category>
    </item>
    <item>
      <title>A Workflow for Deploying Application Code with Terraform</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Sun, 12 Apr 2026 14:12:01 +0000</pubDate>
      <link>https://dev.to/tink-origami/a-workflow-for-deploying-application-code-with-terraform-59f9</link>
      <guid>https://dev.to/tink-origami/a-workflow-for-deploying-application-code-with-terraform-59f9</guid>
      <description>&lt;h2&gt;
  
  
  Seven Steps from Local Change to Production
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 20 of the 30-Day Terraform Challenge&lt;/strong&gt; — and today I learned that infrastructure deployment should look exactly like application deployment.&lt;/p&gt;

&lt;p&gt;The same seven steps developers trust to ship code safely can be used to ship infrastructure. No more "clicking around in the console." No more "who changed that security group?" No more "it works on my machine."&lt;/p&gt;

&lt;p&gt;Here's how it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Seven-Step Workflow
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Use version control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Run the code locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Make code changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Submit changes for review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Run automated tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Merge and release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Step 1: Version Control
&lt;/h2&gt;

&lt;p&gt;Every infrastructure change starts in Git. Not in the AWS Console. Not in a script. Not in a Slack message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial web server - Version 1"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Every change has an author, a timestamp, and a reason. No more mystery changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Run Locally
&lt;/h2&gt;

&lt;p&gt;Before proposing changes, run them locally to see what will happen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;day20.tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output shows exactly what will change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which resources will be created&lt;/li&gt;
&lt;li&gt;Which will be modified&lt;/li&gt;
&lt;li&gt;Which will be destroyed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; You catch mistakes before they reach production. Not after.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Make Code Changes
&lt;/h2&gt;

&lt;p&gt;Create a feature branch and make your change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; update-to-v3
&lt;span class="c"&gt;# Edit main.tf&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Update web server to Version 3"&lt;/span&gt;
git push origin update-to-v3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Changes are isolated. Main branch stays deployable. Multiple engineers can work simultaneously.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Submit for Review
&lt;/h2&gt;

&lt;p&gt;Open a pull request on GitHub. Include the &lt;code&gt;terraform plan&lt;/code&gt; output in the description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Code review catches mistakes. The plan output shows the reviewer exactly what will happen in production — without them running Terraform themselves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Run Automated Tests
&lt;/h2&gt;

&lt;p&gt;Your CI pipeline runs &lt;code&gt;terraform validate&lt;/code&gt;, &lt;code&gt;terraform fmt&lt;/code&gt;, and &lt;code&gt;terraform test&lt;/code&gt; automatically on every PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Catch syntax errors, formatting issues, and logic mistakes before a human ever looks at them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Merge and Release
&lt;/h2&gt;

&lt;p&gt;After approval, merge the PR and tag the release:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout main
git pull origin main
git tag &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"v3.0.0"&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Version 3 release"&lt;/span&gt;
git push origin v3.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Tags give you a history of what changed when. Rollback is as simple as checking out an old tag.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Deploy
&lt;/h2&gt;

&lt;p&gt;Apply the saved plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply day20.tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then verify:&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="nv"&gt;$ &lt;/span&gt;curl http://my-server.com
&amp;lt;h1&amp;gt;Version 3: Day 20 workflow &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; The plan was reviewed, tested, and approved before apply. No surprises.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Actually Deployed
&lt;/h2&gt;

&lt;p&gt;A simple web server that shows its version:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;v1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Version 1: Hello from Day 20!"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform plan &amp;amp;&amp;amp; apply&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tag changed, but user_data didn't run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;Added &lt;code&gt;user_data_replace_on_change = true&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;"Version 3: Day 20 workflow complete!"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The third attempt worked because I learned that EC2 instances don't re-run user_data unless you force replacement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Workflow Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Application Code&lt;/th&gt;
&lt;th&gt;Infrastructure Code&lt;/th&gt;
&lt;th&gt;Key Difference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Version control&lt;/td&gt;
&lt;td&gt;Git for source code&lt;/td&gt;
&lt;td&gt;Git for .tf files&lt;/td&gt;
&lt;td&gt;State file is NOT in Git&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run locally&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npm start&lt;/code&gt; / &lt;code&gt;python app.py&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Plan shows what will change, not a running app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Make changes&lt;/td&gt;
&lt;td&gt;Edit source files&lt;/td&gt;
&lt;td&gt;Edit .tf files&lt;/td&gt;
&lt;td&gt;Changes affect real cloud resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review&lt;/td&gt;
&lt;td&gt;Code diff in PR&lt;/td&gt;
&lt;td&gt;Plan output in PR&lt;/td&gt;
&lt;td&gt;Reviewer must understand cloud implications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automated tests&lt;/td&gt;
&lt;td&gt;Unit tests, linting&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Infra tests deploy real resources → cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merge and release&lt;/td&gt;
&lt;td&gt;Merge + tag&lt;/td&gt;
&lt;td&gt;Merge + tag&lt;/td&gt;
&lt;td&gt;Module consumers must pin versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;CI/CD pipeline&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Apply must be run from trusted, locked environment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Biggest Difference
&lt;/h2&gt;

&lt;p&gt;Application code: You run it and see if it works.&lt;/p&gt;

&lt;p&gt;Infrastructure code: You run &lt;code&gt;plan&lt;/code&gt; and predict what will happen. Then you trust the prediction.&lt;/p&gt;

&lt;p&gt;That trust comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code review&lt;/li&gt;
&lt;li&gt;Automated tests&lt;/li&gt;
&lt;li&gt;A history of successful deploys&lt;/li&gt;
&lt;li&gt;The ability to roll back&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;&lt;strong&gt;Plan output is the most powerful review tool.&lt;/strong&gt; Include it in every PR. It shows exactly what will change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User_data doesn't re-run on existing instances.&lt;/strong&gt; You need &lt;code&gt;user_data_replace_on_change = true&lt;/code&gt; or a launch template with &lt;code&gt;create_before_destroy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git workflow works for infrastructure.&lt;/strong&gt; The same seven steps developers trust can be used to deploy infrastructure safely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags matter.&lt;/strong&gt; Every release gets a tag. Every tag is a rollback point.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Infrastructure deployment doesn't need to be risky or mysterious.&lt;/p&gt;

&lt;p&gt;The same seven-step workflow that engineers trust to ship application code works perfectly for Terraform.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Console clicks&lt;/td&gt;
&lt;td&gt;Pull requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Who changed this?"&lt;/td&gt;
&lt;td&gt;Git blame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual testing&lt;/td&gt;
&lt;td&gt;Automated tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hope it works&lt;/td&gt;
&lt;td&gt;Plan output proves it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can't roll back&lt;/td&gt;
&lt;td&gt;Tags = rollback points&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Version control. Plan. Review. Test. Merge. Tag. Deploy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Seven steps. Every time. No exceptions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. The moment I saw &lt;code&gt;terraform plan&lt;/code&gt; show exactly what would change, and then &lt;code&gt;curl&lt;/code&gt; confirm it worked, I finally understood why teams adopt this workflow. It's not slower. It's safer.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>30daychallenge</category>
      <category>iac</category>
    </item>
    <item>
      <title>How to Convince Your Team to Adopt Infrastructure as Code</title>
      <dc:creator>Mukami</dc:creator>
      <pubDate>Sun, 12 Apr 2026 13:28:56 +0000</pubDate>
      <link>https://dev.to/tink-origami/how-to-convince-your-team-to-adopt-infrastructure-as-code-13fj</link>
      <guid>https://dev.to/tink-origami/how-to-convince-your-team-to-adopt-infrastructure-as-code-13fj</guid>
      <description>&lt;h2&gt;
  
  
  The Technical Part Is Easy. The People Part Is Hard.
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Day 19 of the 30-Day Terraform Challenge&lt;/strong&gt; — and today I learned something uncomfortable.&lt;/p&gt;

&lt;p&gt;The technical part of Terraform is the easy part. Writing configurations, managing state, setting up modules — that's straightforward compared to what's actually hard:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting a team to change how they work.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Engineers Don't Resist Technology
&lt;/h2&gt;

&lt;p&gt;Engineers don't resist technology. They resist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changing habits they've had for years&lt;/li&gt;
&lt;li&gt;Learning new tools when the old ones "work"&lt;/li&gt;
&lt;li&gt;Slowing down to do things the "right" way&lt;/li&gt;
&lt;li&gt;Trusting automation over their own judgment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're trying to introduce IaC to a team, you're not solving a technical problem. You're solving a people problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Business Case (What Leadership Cares About)
&lt;/h2&gt;

&lt;p&gt;Leadership doesn't care about "better infrastructure." They care about outcomes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Business Problem&lt;/th&gt;
&lt;th&gt;IaC Solution&lt;/th&gt;
&lt;th&gt;Measurable Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure incidents from manual errors&lt;/td&gt;
&lt;td&gt;Code review catches mistakes before apply&lt;/td&gt;
&lt;td&gt;Fewer production outages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hours spent on repetitive environment setup&lt;/td&gt;
&lt;td&gt;Reusable modules provision in minutes&lt;/td&gt;
&lt;td&gt;Engineering time freed for product work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No audit trail for compliance&lt;/td&gt;
&lt;td&gt;Every change is a git commit with author and timestamp&lt;/td&gt;
&lt;td&gt;Full audit trail for auditors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev environments differ from production&lt;/td&gt;
&lt;td&gt;Same config for all environments&lt;/td&gt;
&lt;td&gt;Fewer "works on my machine" incidents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow onboarding for new engineers&lt;/td&gt;
&lt;td&gt;Documented, version-controlled configs&lt;/td&gt;
&lt;td&gt;Faster onboarding time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Frame the conversation around outcomes they already care about.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Most IaC Adoptions Fail
&lt;/h2&gt;

&lt;p&gt;According to the author, the most common reason IaC adoption fails is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trying to do too much at once.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Teams attempt to migrate all their existing infrastructure to Terraform in one big project. It takes months. People get frustrated. Things break. Management loses confidence. The project dies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Start small. Win early. Build momentum.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Incremental Adoption Strategy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1: Start with Something New (2-4 weeks)
&lt;/h3&gt;

&lt;p&gt;Do not migrate existing infrastructure first. Pick one new piece of infrastructure — a new S3 bucket, a new IAM role, a monitoring dashboard — and provision it entirely with Terraform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero migration risk (it's new, not replacing anything)&lt;/li&gt;
&lt;li&gt;Quick win (days, not months)&lt;/li&gt;
&lt;li&gt;Team learns without pressure&lt;/li&gt;
&lt;li&gt;Creates a success story&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Success criteria:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration is code-reviewed and merged&lt;/li&gt;
&lt;li&gt;Remote state is configured in S3&lt;/li&gt;
&lt;li&gt;Team members can run &lt;code&gt;terraform plan&lt;/code&gt; and understand the output&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Phase 2: Import Existing Infrastructure (4-6 weeks)
&lt;/h3&gt;

&lt;p&gt;Once the team is comfortable with the workflow, begin importing critical existing resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example: import an existing S3 bucket&lt;/span&gt;
terraform import aws_s3_bucket.existing_logs my-existing-logs-bucket

&lt;span class="c"&gt;# Example: import an existing security group&lt;/span&gt;
terraform import aws_security_group.existing sg-0abc123def456789
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prioritise resources that:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change frequently&lt;/li&gt;
&lt;li&gt;Have caused incidents&lt;/li&gt;
&lt;li&gt;Are well-understood&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Do not try to import everything at once.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Phase 3: Establish Team Practices (Ongoing)
&lt;/h3&gt;

&lt;p&gt;Once multiple engineers are writing Terraform, establish the practices that prevent chaos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Module versioning&lt;/li&gt;
&lt;li&gt;Code review requirements for all infrastructure changes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform plan&lt;/code&gt; output as a required part of every PR&lt;/li&gt;
&lt;li&gt;Automated &lt;code&gt;terraform validate&lt;/code&gt; and &lt;code&gt;terraform fmt&lt;/code&gt; in CI&lt;/li&gt;
&lt;li&gt;State locking enforced via DynamoDB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No manual console changes to Terraform-managed resources&lt;/strong&gt; — ever&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Phase 4: Automate Deployments (6-8 weeks)
&lt;/h3&gt;

&lt;p&gt;Connect Terraform to your CI/CD pipeline so that merges to main trigger &lt;code&gt;terraform apply&lt;/code&gt; automatically.&lt;/p&gt;

&lt;p&gt;At this stage, infrastructure changes go through the same review and deployment process as application code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cultural Shift
&lt;/h2&gt;

&lt;p&gt;Technical changes require cultural changes. Here's what needs to shift:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;From&lt;/th&gt;
&lt;th&gt;To&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"It works on my machine"&lt;/td&gt;
&lt;td&gt;"It works in the code"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual console changes&lt;/td&gt;
&lt;td&gt;Pull requests for everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blaming the tool&lt;/td&gt;
&lt;td&gt;Blaming the process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heroic fixes&lt;/td&gt;
&lt;td&gt;Reliable rollbacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"I know what changed"&lt;/td&gt;
&lt;td&gt;"The git log knows"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Building Trust in Automation
&lt;/h2&gt;

&lt;p&gt;Teams don't trust automation because they've been burned before. Build trust through:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Visibility&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;terraform plan&lt;/code&gt; output is the most transparent change preview possible. Use it in every PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Safety&lt;/strong&gt;&lt;br&gt;
Start with read-only changes. Let the team run &lt;code&gt;terraform plan&lt;/code&gt; for weeks before anyone runs &lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Rollback capability&lt;/strong&gt;&lt;br&gt;
Show the team how to revert a change in minutes. Trust comes from knowing you can recover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Gradual rollout&lt;/strong&gt;&lt;br&gt;
Start with low-risk resources. Work up to critical infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I've Observed
&lt;/h2&gt;

&lt;p&gt;In my experience, the hardest part of IaC adoption isn't technical. It's getting experienced engineers to trust code over console.&lt;/p&gt;

&lt;p&gt;Engineers who have been burned by bad automation resist. Engineers who have fixed things manually for years don't see the problem.&lt;/p&gt;

&lt;p&gt;The solution isn't better tooling. It's small wins that build confidence over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;The technical part of Terraform is straightforward. The real challenge is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convincing leadership to invest in IaC&lt;/li&gt;
&lt;li&gt;Getting engineers to change their workflow&lt;/li&gt;
&lt;li&gt;Building trust in automation&lt;/li&gt;
&lt;li&gt;Moving incrementally when everyone wants to move fast&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Start small. Win early. Build momentum. The rest follows.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. The next time someone says "we should just do it manually this once," you'll know that's how drift starts. One manual change becomes ten. Ten becomes a hundred. The only way to win is to never start.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>30daychallenge</category>
      <category>iac</category>
    </item>
  </channel>
</rss>
