<?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: Zakariyau Mukhtar</title>
    <description>The latest articles on DEV Community by Zakariyau Mukhtar (@alafiz).</description>
    <link>https://dev.to/alafiz</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%2F3630903%2F30f74b32-f519-4b3a-a2e0-d52c7cc46e53.png</url>
      <title>DEV Community: Zakariyau Mukhtar</title>
      <link>https://dev.to/alafiz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alafiz"/>
    <language>en</language>
    <item>
      <title>From Installation Freezes to PowerShell Automation: My Windows Server 2019 Journey.</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Thu, 15 Jan 2026 17:35:25 +0000</pubDate>
      <link>https://dev.to/alafiz/from-installation-freezes-to-powershell-automation-my-windows-server-2019-journey-168h</link>
      <guid>https://dev.to/alafiz/from-installation-freezes-to-powershell-automation-my-windows-server-2019-journey-168h</guid>
      <description>&lt;h3&gt;
  
  
  &lt;strong&gt;Introduction: The Challenge&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I recently tackled a comprehensive lab project: deploying a functional Windows Server 2019 environment from scratch. The goal wasn't just to install the OS, but to configure it like a real-world sysadmin using automation to handle users, groups, and secure file sharing.&lt;/p&gt;

&lt;p&gt;While I started this project feeling a bit like a beginner (or as we say, an "olodo"), by the end, I was writing multi-line PowerShell scripts to manage the infrastructure.&lt;/p&gt;

&lt;p&gt;Here is a breakdown of how I built my lab using Oracle VirtualBox and how I overcame some tricky hurdles along the way.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Phase 1: The Setup and "The Freeze"&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;My lab environment ran on Oracle VirtualBox. The installation process taught me my first major lesson in virtualization troubleshooting.&lt;/p&gt;

&lt;p&gt;Initially, my Windows Server installation kept freezing at the "spinning dots" boot screen. After some deep diving into settings, I realized that running a modern server OS requires specific virtualization hardware settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
It turned out my VM configuration was missing a critical processor feature. I had to ensure &lt;strong&gt;PAE/NX&lt;/strong&gt; was enabled in the VirtualBox processor settings and increase my video memory. Once those were tweaked, the installation sailed through to the desktop experience.&lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;Phase 2: Base Configuration (The Manual Stuff)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once logged in as the Administrator, the first step was giving the server an identity.&lt;/p&gt;

&lt;p&gt;In a real network, servers need static addresses so they can always be found. I configured the network adapter with a static IPv4 address suitable for my lab environment (&lt;code&gt;10.0.2.15&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;I also renamed the computer from its generic Windows name to something meaningful: &lt;code&gt;WIN-SERVER-01&lt;/code&gt;. While I could have used the GUI, I used PowerShell for a quick rename and restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Rename-Computer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-NewName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WIN-SERVER-01"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Restart&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;strong&gt;Phase 3: The Power of Automation (The Cool Stuff)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is where the project got interesting. The requirement was to create multiple users (Alice, Bob, Charlie), assign them to departments (HR, IT), and ensure they changed their passwords on first login.&lt;/p&gt;

&lt;p&gt;Doing this manually involves dozens of clicks. Instead, I wrote a PowerShell script to handle it in seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Scripting Approach:&lt;/strong&gt;&lt;br&gt;
I used ISE to write a script that utilized loops and conditionals. It checked if a user or group existed before trying to create it, making the script "idempotent" (safe to run multiple times).&lt;/p&gt;

&lt;p&gt;Here is a snippet of the user creation loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Snippet: Automating User Creation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Charlie"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;# Create the user&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;New-LocalUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$securePassword&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Staff"&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c"&gt;# Force password change at next login&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$userObj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ADSI&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="s2"&gt;"WinNT://&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;COMPUTERNAME&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt;,user"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$userObj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PasswordExpired&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$userObj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetInfo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6brun20a4t5keulopw9b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6brun20a4t5keulopw9b.png" alt="Users scripts" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Phase 4: Permissions and Hardening&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The final step was securing the environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File Sharing:&lt;/strong&gt;&lt;br&gt;
I created restricted folders &lt;code&gt;HRDocs&lt;/code&gt; and &lt;code&gt;ITDocs&lt;/code&gt;. I used PowerShell to ensure only the HR group could access their documents, and ditto for IT. This enforced the principle of least privilege.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security Hardening:&lt;/strong&gt;&lt;br&gt;
As a basic security measure, I disabled the built-in Guest account to prevent anonymous access. Again, PowerShell made this easy to verify:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffy5z8pg1h76bam406dvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffy5z8pg1h76bam406dvg.png" alt="Security Hardening" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion and Lessons Learned&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This project was a great crash course in Windows Server administration. The biggest takeaway wasn't just knowing &lt;em&gt;what&lt;/em&gt; to configure, but knowing &lt;em&gt;how&lt;/em&gt; to do it efficiently.&lt;/p&gt;

&lt;p&gt;While GUIs are helpful for learning, PowerShell is essential for scalable, repeatable system administration. Moving past the initial installation hurdles and seeing my scripts successfully deploy the environment felt like a massive win.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>beginners</category>
      <category>devjournal</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>From Vibe-Coding to Engineering: My 48-Hour Battle with Docker &amp; Windows</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Mon, 12 Jan 2026 18:08:30 +0000</pubDate>
      <link>https://dev.to/alafiz/from-vibe-coding-to-engineering-my-48-hour-battle-with-docker-windows-b06</link>
      <guid>https://dev.to/alafiz/from-vibe-coding-to-engineering-my-48-hour-battle-with-docker-windows-b06</guid>
      <description>&lt;p&gt;I started this weekend with a simple goal: set up a local development environment. I wanted a vanilla HTML/JS frontend, a Node.js backend, and a MongoDB database running in a Docker container (plus Mongo Express to visualize the data).&lt;/p&gt;

&lt;p&gt;Prior to this, I had zero knowledge of JavaScript. I was just following a video tutorial, copying code I didn't fully understand, and hoping for the best.&lt;/p&gt;

&lt;p&gt;I thought it would take an hour. Instead, it took a frustrated bike ride home, a Sunday spent journaling, and a deep dive into how Docker actually works to get it running. Here is the full story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup (Copying blindly)
&lt;/h2&gt;

&lt;p&gt;I started by copying the frontend code. It was a simple User Profile page where you could toggle between "View" and "Edit" modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My &lt;code&gt;index.html&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#input-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
        &lt;span class="c1"&gt;// ... more DOM manipulation&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;My &lt;code&gt;server.js&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; 
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app listening on port 3000!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far, so good. The frontend worked. Then came Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1 The "Name Does Not Resolve" Loop:
&lt;/h2&gt;

&lt;p&gt;I pulled the &lt;code&gt;mongo&lt;/code&gt; and &lt;code&gt;mongo-express&lt;/code&gt; images and tried to run them using long CLI commands I found online. I checked &lt;code&gt;docker ps&lt;/code&gt;, and everything looked fine.&lt;/p&gt;

&lt;p&gt;But when I checked the logs for Mongo Express, I saw this error over and over:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;/docker-entrypoint.sh: line 15: mongo: Name does not resolve&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I spent 30 minutes spinning up containers, deleting them, and spinning them up again. I eventually turned to AI for help. It suggested I stop everything, wipe the containers, and create a dedicated network.&lt;/p&gt;

&lt;p&gt;I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mongo-network&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I restarted the containers attached to that network. The error persisted. I was stuck in a loop of trying random commands without understanding &lt;em&gt;why&lt;/em&gt; they failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: The Mystery of "Pass" vs "Password"-
&lt;/h2&gt;

&lt;p&gt;Once I finally got the containers talking, I couldn't log in to Mongo Express.&lt;/p&gt;

&lt;p&gt;I had explicitly set my environment variables to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ME_CONFIG_BASICAUTH_USERNAME=admin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ME_CONFIG_BASICAUTH_PASSWORD=password&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But every time I tried to log in with &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;password&lt;/code&gt;, it failed.&lt;/p&gt;

&lt;p&gt;After a lot of trial and error, I tried logging in with &lt;strong&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/strong&gt;. It worked.&lt;/p&gt;

&lt;p&gt;I realized that because of how I was passing arguments in PowerShell, Docker wasn't picking up my password variable. It was defaulting to the container's built-in security settings (&lt;code&gt;admin:pass&lt;/code&gt;). I had to completely restructure my &lt;code&gt;docker run&lt;/code&gt; command to ensure the variables were actually being read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: The Saturday Night "Vibecoding" Crash-
&lt;/h2&gt;

&lt;p&gt;By 9 PM on Saturday, I hit a wall.&lt;/p&gt;

&lt;p&gt;I ran my Node.js server and tried to edit a profile. The terminal said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Connecting to the db....&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And then... silence. It just froze.&lt;/p&gt;

&lt;p&gt;I tried everything the AI suggested, but nothing worked. I didn't even understand half the commands I was typing. I realized I was just "vibecoding" typing random things hoping for a miracle. I was frustrated. I packed up, left the office, and took a bike ride home to clear my head.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 4: The Monday Morning Fix
&lt;/h2&gt;

&lt;p&gt;On Sunday, I didn't touch the code. I just journaled. I wrote down a plan: &lt;strong&gt;Understand the problem, then fix it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On Monday, I came back fresh. I wiped every container and image (&lt;code&gt;docker system prune&lt;/code&gt;) and started from scratch.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Windows Hang" (IPv4 vs IPv6)
&lt;/h3&gt;

&lt;p&gt;I realized why the app was freezing. On Windows, &lt;code&gt;localhost&lt;/code&gt; often resolves to the IPv6 address &lt;code&gt;::1&lt;/code&gt;. However, Docker Desktop for Windows listens on the IPv4 address &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My Node app was knocking on the IPv6 door, but Docker was at the IPv4 house.&lt;/p&gt;

&lt;p&gt;I changed my connection string in &lt;code&gt;server.js&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bad:&lt;/strong&gt; &lt;code&gt;mongodb://admin:password@localhost:27017&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good:&lt;/strong&gt; &lt;code&gt;mongodb://admin:password@127.0.0.1:27017&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Version Mismatch
&lt;/h3&gt;

&lt;p&gt;Even with the connection fixed, I got errors. I looked at my &lt;code&gt;package.json&lt;/code&gt;.&lt;br&gt;
I had installed the latest &lt;code&gt;mongodb&lt;/code&gt; version (v6.0+), which uses Promises. But the tutorial code I copied was written for version 3.8, which used Callbacks.&lt;/p&gt;

&lt;p&gt;I uninstalled the new version and forced a downgrade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mongodb&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;3.7.3&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I restarted the server. I clicked "Save Profile." &lt;strong&gt;It worked.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;This project was supposed to be a quick setup, but it turned into a crash course in DevOps. I learned:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't trust &lt;code&gt;localhost&lt;/code&gt; on Windows.&lt;/strong&gt; Always use &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check your dependencies.&lt;/strong&gt; Copy-pasting old code requires old packages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop "Vibecoding."&lt;/strong&gt; If you don't understand the command, don't run it. Step back, journal, and come back fresh.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>devops</category>
      <category>programming</category>
      <category>cloud</category>
    </item>
    <item>
      <title>From "Permission Denied" to Production: My AWS &amp; Terraform Journey</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Tue, 30 Dec 2025 14:58:59 +0000</pubDate>
      <link>https://dev.to/alafiz/from-permission-denied-to-production-my-aws-terraform-journey-34p8</link>
      <guid>https://dev.to/alafiz/from-permission-denied-to-production-my-aws-terraform-journey-34p8</guid>
      <description>&lt;p&gt;Provisioning infrastructure and deploying applications manually is a recipe for inconsistency. Today, I took on the challenge of automating a server setup using Terraform to host a Spring Boot Java application. It wasn't a straight path. I ran into regional payment blocks, architecture mismatches and SSH "ghosts" but here is exactly how I solved them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pivot From DigitalOcean to AWS:
&lt;/h2&gt;

&lt;p&gt;I was following a course where the instructor used DigitalOcean to deploy the Java app. I tried to follow along, but I hit a brick wall, DigitalOcean kept rejecting my cards because I am in Nigeria.&lt;br&gt;
I felt bad for a few days, but then I remembered my &lt;strong&gt;#30ofAWSTerraform&lt;/strong&gt; challenge. AWS is known for its complexity, but it’s also the industry leader. I decided to stop feeling bad and start building. If the course used a DigitalOcean "Firewall," I knew I could translate that into an &lt;strong&gt;AWS Security Group&lt;/strong&gt;. I decided to test my knowledge and gain real experience.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Provisioning Infrastructure with Terraform:
&lt;/h2&gt;

&lt;p&gt;My goal was to use Infrastructure as Code (IaC) to create an EC2 instance. I used the &lt;code&gt;aws_instance&lt;/code&gt; and &lt;code&gt;aws_key_pair&lt;/code&gt; resources to define my server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Challenge: SSH Key Management&lt;/strong&gt;&lt;br&gt;
Initially, passing the public key as a raw string variable led to formatting errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;br&gt;
I used the Terraform &lt;code&gt;file()&lt;/code&gt; function to read my public key directly from my local disk. This ensured that the exact bytes on my Windows machine matched what AWS received.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_key_pair"&lt;/span&gt; &lt;span class="s2"&gt;"my_ssh_key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;key_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"zacks-key"&lt;/span&gt;
  &lt;span class="nx"&gt;public_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"C:/Users/Public/id_aws.pub"&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;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F472sjl1st8rgbltaku9p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F472sjl1st8rgbltaku9p.png" alt="Main.tf: My Terraform Code Block" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0pldo15o69a3f12tu0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0pldo15o69a3f12tu0w.png" alt="Main.tf: My Terraform Code Block" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Battle of AMI IDs and Usernames:
&lt;/h2&gt;

&lt;p&gt;Even after the server was "running," I couldn't get in. I kept seeing &lt;code&gt;InvalidAMIID.NotFound&lt;/code&gt; or &lt;code&gt;Permission denied (publickey)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Lessons Learned:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Region Specificity:&lt;/strong&gt; AMI IDs are not universal. An Ubuntu ID in London won't work in North Virginia (&lt;code&gt;us-east-1&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture Matters:&lt;/strong&gt; I discovered that a &lt;code&gt;t2.micro&lt;/code&gt; instance requires an &lt;strong&gt;x86&lt;/strong&gt; image. Trying to use an ARM-based AMI will result in a "Not Found" error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Default User:&lt;/strong&gt; Every OS has a different "front door." For Amazon Linux, it’s &lt;code&gt;ec2-user&lt;/code&gt;. For Ubuntu, it’s &lt;code&gt;ubuntu&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Deploying the Application (The JAR File):
&lt;/h2&gt;

&lt;p&gt;Once the server was accessible, I needed to move my Java artifact from my local &lt;code&gt;build/libs&lt;/code&gt; folder to the cloud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Tool: SCP (Secure Copy)&lt;/strong&gt;&lt;br&gt;
I used the following command to securely upload the JAR:&lt;br&gt;
&lt;code&gt;scp -i C:\Users\Public\id_aws "C:\Users\...\java-app-1.0-SNAPSHOT.jar" ubuntu@&amp;lt;IP&amp;gt;:/home/ubuntu/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13ulvfzo9hiqg0px4995.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13ulvfzo9hiqg0px4995.png" alt="Build Successful" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Translating Firewall to Security Group:&lt;/strong&gt;&lt;br&gt;
When my instructor added a firewall on DigitalOcean, I knew exactly what to do in AWS. I updated my &lt;strong&gt;Security Group&lt;/strong&gt; in Terraform to open &lt;strong&gt;Port 8080&lt;/strong&gt;, allowing traffic to reach my Spring Boot application.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Linux User Management &amp;amp; Security
&lt;/h2&gt;

&lt;p&gt;For security best practices, I created a personal user: &lt;code&gt;zacks&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Steps taken:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Creation:&lt;/strong&gt; &lt;code&gt;sudo adduser zacks&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions:&lt;/strong&gt; I gave the user administrative power using &lt;code&gt;sudo usermod -aG sudo zacks&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Final SSH Boss:&lt;/strong&gt; I mistakenly named my key file &lt;code&gt;Authentication_keys&lt;/code&gt;. Linux ignores this, It must be named &lt;code&gt;authorized_keys&lt;/code&gt;. Furthermore, I had to use &lt;code&gt;chown&lt;/code&gt; to ensure the user &lt;code&gt;zacks&lt;/code&gt; actually owned the file and &lt;code&gt;chmod 600&lt;/code&gt; to make it private.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mv&lt;/span&gt; /home/zacks/.ssh/Authentication_keys /home/zacks/.ssh/authorized_keys
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; zacks:zacks /home/zacks/.ssh
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /home/zacks/.ssh/authorized_keys

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Today was a masterclass in troubleshooting. I learned that DevOps isn't just about writing code; it's about being adaptable. When one cloud door closed, I used Terraform to open a better one on AWS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxw93py90dsrb8mx1yrj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxw93py90dsrb8mx1yrj.png" alt="Java Application started on server" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure is live, the JAR is running, and the "Whitelabel Error Page" never looked so beautiful!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kttxeqsarr7tx25z96i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kttxeqsarr7tx25z96i.png" alt="Application Page" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
    <item>
      <title>Day 17 of #30DaysofAWSTerraform: Blue-Green Deployment with Elastic Beanstalk (The Hard Way)</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Thu, 25 Dec 2025 14:38:27 +0000</pubDate>
      <link>https://dev.to/alafiz/day-17-of-30daysofawsterraform-blue-green-deployment-with-elastic-beanstalk-the-hard-way-5cag</link>
      <guid>https://dev.to/alafiz/day-17-of-30daysofawsterraform-blue-green-deployment-with-elastic-beanstalk-the-hard-way-5cag</guid>
      <description>&lt;p&gt;Day 17 was one of those days that really tested my patience but also deepened my understanding of &lt;strong&gt;real-world deployment strategies&lt;/strong&gt;. Today, I learned about &lt;strong&gt;Blue-Green deployment&lt;/strong&gt;, how &lt;strong&gt;Elastic Beanstalk&lt;/strong&gt; fits into that strategy, and how to actually &lt;strong&gt;swap environments safely&lt;/strong&gt; without downtime. It wasn’t smooth at all; IAM policies almost broke my spirit but the lesson stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Blue-Green Deployment Really Is:
&lt;/h2&gt;

&lt;p&gt;Blue-Green deployment is a strategy where you maintain &lt;strong&gt;two identical environments&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blue&lt;/strong&gt; → current production (stable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green&lt;/strong&gt; → new version (staging).
Instead of deploying directly to production, you deploy the new version to the green environment, test it thoroughly, and then &lt;strong&gt;swap traffic instantly&lt;/strong&gt;. If something goes wrong, rollback is just another swap away. This drastically reduces downtime and deployment risk something every serious production system needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Elastic Beanstalk?
&lt;/h2&gt;

&lt;p&gt;Elastic Beanstalk abstracts a lot of infrastructure complexity while still giving control when needed. It manages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EC2 instances.&lt;/li&gt;
&lt;li&gt;Load balancers.&lt;/li&gt;
&lt;li&gt;Auto Scaling.&lt;/li&gt;
&lt;li&gt;Health checks.&lt;/li&gt;
&lt;li&gt;Application versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this demo, Elastic Beanstalk made Blue-Green deployment &lt;strong&gt;practical&lt;/strong&gt;, especially when paired with Terraform for infrastructure consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform Setup: Providers and IAM (Where the Pain Started)
&lt;/h2&gt;

&lt;p&gt;I started with provider configuration and IAM roles and this is where things got tough. Elastic Beanstalk requires &lt;strong&gt;multiple IAM roles&lt;/strong&gt;, and incorrect attachments will break everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt;5.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The EC2 role for Elastic Beanstalk instances:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"eb_ec2_role"&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.app_name}-eb-ec2-role"&lt;/span&gt;
  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&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;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ec2.amazonaws.com"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attaching the correct managed policies was &lt;strong&gt;critical&lt;/strong&gt; and yes, I hit multiple errors here due to wrong role attachments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Elastic Beanstalk Application:
&lt;/h2&gt;

&lt;p&gt;Once IAM stopped fighting me, I defined the Elastic Beanstalk application itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_elastic_beanstalk_application"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;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;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Blue-Green Deployment Demo Application"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also created an S3 bucket to store application versions each version mapped to either blue or green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blue Environment (Production):
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;blue environment&lt;/strong&gt; represents production running version 1.0:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_elastic_beanstalk_environment"&lt;/span&gt; &lt;span class="s2"&gt;"blue"&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.app_name}-blue"&lt;/span&gt;
  &lt;span class="nx"&gt;application&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_elastic_beanstalk_application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;solution_stack_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;solution_stack_name&lt;/span&gt;
  &lt;span class="nx"&gt;version_label&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_elastic_beanstalk_application_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;

  &lt;span class="nx"&gt;setting&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws:elasticbeanstalk:application:environment"&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;"Environment"&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;"blue"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This environment served stable traffic and stayed untouched while testing the new version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Green Environment (Staging):
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;green environment&lt;/strong&gt; hosted version 2.0 with new features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_elastic_beanstalk_environment"&lt;/span&gt; &lt;span class="s2"&gt;"green"&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.app_name}-green"&lt;/span&gt;
  &lt;span class="nx"&gt;application&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_elastic_beanstalk_application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;solution_stack_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;solution_stack_name&lt;/span&gt;
  &lt;span class="nx"&gt;version_label&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_elastic_beanstalk_application_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;

  &lt;span class="nx"&gt;setting&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws:elasticbeanstalk:application:environment"&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;"Environment"&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;"green"&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;Both environments were load-balanced, scalable, and completely independent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swapping Blue and Green (The Cool Part):
&lt;/h2&gt;

&lt;p&gt;After testing the green environment, I used a swap script to redirect traffic instantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws elasticbeanstalk swap-environment-cnames &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source-environment-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BLUE_ENV&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--destination-environment-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GREEN_ENV&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This swap takes &lt;strong&gt;1–2 minutes&lt;/strong&gt;, with near-zero downtime. Production traffic moves to green, and blue becomes the fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commands Used:
&lt;/h2&gt;

&lt;p&gt;Throughout this setup, I relied on the basics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform init&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform plan&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform apply --auto-approve&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform destroy --auto-approve&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple commands complex consequences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Today was &lt;strong&gt;tough&lt;/strong&gt;. IAM policies and attachments caused multiple failures, and Elastic Beanstalk doesn’t forgive mistakes. But that’s exactly why this day mattered. I didn’t just learn Blue-Green deployment in theory I &lt;strong&gt;felt it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/fTVx2m5fEbQ"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Day 17 reminded me that &lt;strong&gt;real DevOps is messy&lt;/strong&gt;, but the payoff is systems that deploy safely, scale reliably, and recover fast.&lt;/p&gt;

&lt;p&gt;On to Day 18 🚀&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>aws</category>
      <category>terraform</category>
      <category>devops</category>
    </item>
    <item>
      <title>Day 16 : Managing IAM Users, Groups, and MFA at Scale with Terraform</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Fri, 19 Dec 2025 16:55:33 +0000</pubDate>
      <link>https://dev.to/alafiz/day-16-managing-iam-users-groups-and-mfa-at-scale-with-terraform-1m9c</link>
      <guid>https://dev.to/alafiz/day-16-managing-iam-users-groups-and-mfa-at-scale-with-terraform-1m9c</guid>
      <description>&lt;p&gt;Day 16 of my &lt;strong&gt;#30DaysOfAWSTerraform&lt;/strong&gt; challenge focused on one of the most sensitive parts of cloud infrastructure: &lt;strong&gt;identity and access management (IAM)&lt;/strong&gt;.Unlike EC2 or networking, IAM mistakes don’t usually “fail loudly” they create &lt;strong&gt;security risks&lt;/strong&gt;. Today’s goal was to learn how to &lt;strong&gt;create, manage, and secure multiple IAM users at scale using Terraform&lt;/strong&gt;.&lt;br&gt;
This day was relatively straightforward conceptually, but the implementation forced me to think like a real administrator rather than a hobbyist.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Using Terraform, I automated the creation of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple IAM users from a CSV file.&lt;/li&gt;
&lt;li&gt;IAM login profiles with forced password reset.&lt;/li&gt;
&lt;li&gt;Department-based IAM groups.&lt;/li&gt;
&lt;li&gt;Conditional group memberships.&lt;/li&gt;
&lt;li&gt;Custom IAM policies per department.&lt;/li&gt;
&lt;li&gt;Mandatory MFA enforcement for &lt;strong&gt;all users&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Account-wide password policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this was done declaratively and reproducibly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating Multiple IAM Users from CSV:
&lt;/h2&gt;

&lt;p&gt;Instead of hardcoding users, I used a &lt;strong&gt;CSV file&lt;/strong&gt; as the source of truth. Terraform’s &lt;code&gt;csvdecode()&lt;/code&gt; function made this clean and scalable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;csvdecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"users.csv"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each user was created dynamically using &lt;code&gt;for_each&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user"&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;first_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${substr(each.value.first_name, 0, 1)}${each.value.last_name}"&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;"/users/"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;DisplayName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${each.value.first_name} ${each.value.last_name}"&lt;/span&gt;
    &lt;span class="nx"&gt;Department&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;department&lt;/span&gt;
    &lt;span class="nx"&gt;JobTitle&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;job_title&lt;/span&gt;
    &lt;span class="nx"&gt;Email&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
    &lt;span class="nx"&gt;Phone&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach allowed me to scale user creation without touching Terraform code again adding a user only requires editing the CSV.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Console Access and Password Policies:
&lt;/h2&gt;

&lt;p&gt;Each user received a login profile with a &lt;strong&gt;forced password reset on first login&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user_login_profile"&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;

  &lt;span class="nx"&gt;user&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;password_reset_required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also enforced a strong &lt;strong&gt;account-wide password policy&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_account_password_policy"&lt;/span&gt; &lt;span class="s2"&gt;"strong"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;minimum_password_length&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="nx"&gt;require_lowercase_characters&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;require_uppercase_characters&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;allow_users_to_change_password&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;max_password_age&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures consistency and security across all users.&lt;/p&gt;

&lt;h2&gt;
  
  
  IAM Groups and Automatic Membership:
&lt;/h2&gt;

&lt;p&gt;I created four IAM groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Management.&lt;/li&gt;
&lt;li&gt;Sales.&lt;/li&gt;
&lt;li&gt;Accounting.&lt;/li&gt;
&lt;li&gt;HR.
Membership was determined &lt;strong&gt;dynamically&lt;/strong&gt; using tags instead of manual assignments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example Management group membership based on job title:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_group_membership"&lt;/span&gt; &lt;span class="s2"&gt;"management_members"&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;"management-group-membership"&lt;/span&gt;
  &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;management&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="nx"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"JobTitle"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
       &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Manager|CEO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JobTitle&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern removes human error and keeps access aligned with organizational roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforcing MFA for Every User:
&lt;/h2&gt;

&lt;p&gt;One of the most important parts of today was &lt;strong&gt;forcing MFA&lt;/strong&gt;. I created a custom IAM policy that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allows users to set up their own MFA devices.&lt;/li&gt;
&lt;li&gt;Denies &lt;strong&gt;all AWS actions&lt;/strong&gt; if MFA is not enabled.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy"&lt;/span&gt; &lt;span class="s2"&gt;"force_mfa"&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;"Force-MFA-Policy"&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&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;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Sid&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DenyAllExceptMFASetupIfNoMFA"&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Deny"&lt;/span&gt;
        &lt;span class="nx"&gt;NotAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"iam:CreateVirtualMFADevice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"iam:EnableMFADevice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"iam:GetUser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"sts:GetSessionToken"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"iam:ChangePassword"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
        &lt;span class="nx"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;BoolIfExists&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"aws:MultifactorAuthPresent"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"false"&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This policy was attached to &lt;strong&gt;every user&lt;/strong&gt; automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"enforce_mfa"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;policy_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;force_mfa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Department-Specific Policies:
&lt;/h2&gt;

&lt;p&gt;Each department received tailored permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Management&lt;/strong&gt;: Broad access, restricted IAM management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sales&lt;/strong&gt;: Read-only access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accounting&lt;/strong&gt;: Billing and cost management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HR&lt;/strong&gt;: IAM user and credential management only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These policies were attached at the &lt;strong&gt;group level&lt;/strong&gt;, not the user level a best practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outputs and Visibility:
&lt;/h2&gt;

&lt;p&gt;I exposed useful outputs for visibility and auditing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"group_members"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Management&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;aws_iam_group_membership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;management_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;Sales&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;aws_iam_group_membership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sales_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;Accounting&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;aws_iam_group_membership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounting_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;HR&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;aws_iam_group_membership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hr_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Day 16 showed me that &lt;strong&gt;IAM is not about permissions it’s about structure&lt;/strong&gt;. Terraform forces discipline, consistency, and repeatability in access control, which is exactly what real organizations need.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/33dWo4esH1U"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;No chaos today just clean, deliberate infrastructure.&lt;/p&gt;

&lt;p&gt;On to Day 17.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>devops</category>
      <category>te</category>
    </item>
    <item>
      <title>Day 15:Building Multi-Region VPC Peering with Terraform</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Thu, 18 Dec 2025 14:50:29 +0000</pubDate>
      <link>https://dev.to/alafiz/day-15building-multi-region-vpc-peering-with-terraform-5eha</link>
      <guid>https://dev.to/alafiz/day-15building-multi-region-vpc-peering-with-terraform-5eha</guid>
      <description>&lt;p&gt;Day 15 of my &lt;strong&gt;#30DaysOfAWSTerraform&lt;/strong&gt; challenge was one of the most demanding so farnot because the concepts were impossible, but because this was the first time everything felt &lt;em&gt;real-world messy&lt;/em&gt;. Today I worked extensively with &lt;strong&gt;VPC peering&lt;/strong&gt;, multi-region infrastructure, routing, security groups, and Git conflicts the full DevOps experience.&lt;/p&gt;

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

&lt;p&gt;The core focus of today was understanding &lt;strong&gt;how VPC peering works beyond theory&lt;/strong&gt;. I didn’t just connect two VPCs, I created &lt;strong&gt;three VPCs across three different AWS regions&lt;/strong&gt; and connected all of them together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Production VPC&lt;/strong&gt; in &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing VPC&lt;/strong&gt; in &lt;code&gt;us-west-2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Development VPC&lt;/strong&gt; in &lt;code&gt;eu-west-2&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This immediately forced me to think about &lt;strong&gt;provider aliases&lt;/strong&gt;, &lt;strong&gt;region isolation&lt;/strong&gt;, and how AWS networking behaves when traffic crosses VPC and regional boundaries.&lt;br&gt;
I also deepened my understanding of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route tables and why peering alone is not enough.&lt;/li&gt;
&lt;li&gt;Internet Gateways and their role in outbound traffic.&lt;/li&gt;
&lt;li&gt;Subnets and availability zones.&lt;/li&gt;
&lt;li&gt;Security group ingress rules for controlled cross-VPC access.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Creating the VPCs (Terraform Code):
&lt;/h2&gt;

&lt;p&gt;Each VPC was defined with its own provider alias and CIDR block. For example, the &lt;strong&gt;Production VPC&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"prod_vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prod&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;prod_vpc_cidr&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="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;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Production-VPC-${var.prod_A}"&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;Purpose&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VPC-Peering-Demo"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I repeated this pattern for Testing and Development VPCs, each tied to its own AWS region.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subnets, Internet Gateways and Route Tables:
&lt;/h2&gt;

&lt;p&gt;Each VPC had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A public subnet.&lt;/li&gt;
&lt;li&gt;An Internet Gateway.&lt;/li&gt;
&lt;li&gt;A route table with &lt;code&gt;0.0.0.0/0&lt;/code&gt; pointing to the IGW.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example &lt;strong&gt;Production Route Table&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"prod_rt"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prod&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;prod_vpc&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;route&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="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
    &lt;span class="nx"&gt;gateway_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prod_igw&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without properly associating route tables with subnets, nothing works and Terraform won’t warn you. This was a critical lesson.&lt;/p&gt;

&lt;h2&gt;
  
  
  VPC Peering Connections (The Core of the Day):
&lt;/h2&gt;

&lt;p&gt;I created &lt;strong&gt;three peering connections&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production ↔ Testing.&lt;/li&gt;
&lt;li&gt;Production ↔ Development.&lt;/li&gt;
&lt;li&gt;Testing ↔ Development.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each peering required:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A requester connection.&lt;/li&gt;
&lt;li&gt;An accepter connection.&lt;/li&gt;
&lt;li&gt;Explicit routes in &lt;strong&gt;both VPC route tables&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example &lt;strong&gt;Production to Testing peering&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_peering_connection"&lt;/span&gt; &lt;span class="s2"&gt;"production_to_testing"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prod&lt;/span&gt;
  &lt;span class="nx"&gt;peer_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;test_vpc&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;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;prod_vpc&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;peer_region&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;test_B&lt;/span&gt;
  &lt;span class="nx"&gt;auto_accept&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the accepter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_peering_connection_accepter"&lt;/span&gt; &lt;span class="s2"&gt;"testing_to_production"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_peering_connection_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc_peering_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;production_to_testing&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;auto_accept&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the &lt;strong&gt;route entries&lt;/strong&gt; the most commonly missed step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route"&lt;/span&gt; &lt;span class="s2"&gt;"prod_to_test"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prod&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prod_rt&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;destination_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;test_vpc_cidr&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_peering_connection_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc_peering_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;production_to_testing&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;Without this, peering exists but traffic dies silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Groups and EC2 Validation:
&lt;/h2&gt;

&lt;p&gt;To validate connectivity, I deployed &lt;strong&gt;EC2 instances in each VPC&lt;/strong&gt; with security groups that allowed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSH (port 22)&lt;/li&gt;
&lt;li&gt;ICMP (ping)&lt;/li&gt;
&lt;li&gt;Cross-VPC CIDR access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example &lt;strong&gt;Production Security Group ingress&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;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SSH from Test and Dev VPC"&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&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;"tcp"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test_vpc_cidr&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;dev_vpc_cidr&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each EC2 instance used &lt;strong&gt;Ubuntu 24.04 LTS&lt;/strong&gt;, pulled via data sources, and included user-data scripts to install Apache and display region-specific info.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Struggle: Git Merge Conflicts
&lt;/h2&gt;

&lt;p&gt;This day was chaotic.&lt;/p&gt;

&lt;p&gt;While pushing changes to Git, I ran into &lt;strong&gt;merge conflicts&lt;/strong&gt; repeatedly. I ended up rewriting parts of the infrastructure &lt;strong&gt;three different times&lt;/strong&gt; before I finally understood why the conflicts were happening.&lt;/p&gt;

&lt;p&gt;It wasn’t Terraform’s fault.&lt;br&gt;
It wasn’t AWS.&lt;br&gt;
It was version control discipline.&lt;/p&gt;

&lt;p&gt;That pain was worth it. I now understand merge conflicts at a deeper level not as errors, but as signals.&lt;/p&gt;
&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Day 15 taught me something important:&lt;br&gt;
&lt;strong&gt;DevOps is not clean. It’s structured chaos.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/WGt000THDmQ"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;VPC peering works beautifully &lt;em&gt;when every dependency is correct&lt;/em&gt;. One missing route, one wrong CIDR, or one overlooked provider alias, and everything breaks silently.&lt;br&gt;
Today, I didn’t just learn Terraform.&lt;br&gt;
I learned how &lt;strong&gt;real infrastructure behaves under pressure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On to Day 16.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>terraform</category>
      <category>networking</category>
    </item>
    <item>
      <title>Day 14 of #30DaysOfAWSTerraform: Hosting a Static Website with S3 and CloudFront</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Mon, 15 Dec 2025 19:11:31 +0000</pubDate>
      <link>https://dev.to/alafiz/day-14-of-30daysofawsterraform-hosting-a-static-website-with-s3-and-cloudfront-4i7a</link>
      <guid>https://dev.to/alafiz/day-14-of-30daysofawsterraform-hosting-a-static-website-with-s3-and-cloudfront-4i7a</guid>
      <description>&lt;p&gt;Day 14 was all about &lt;strong&gt;static websites&lt;/strong&gt; and how AWS handles them at scale. Before this lesson, I had a surface-level idea of what a static website was, but today made everything click from how files are stored, to how they are securely served globally using AWS services.&lt;/p&gt;

&lt;p&gt;The core focus of today’s lesson was &lt;strong&gt;S3&lt;/strong&gt;, &lt;strong&gt;CloudFront&lt;/strong&gt;, and how Terraform ties everything together as Infrastructure as Code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding Static Websites
&lt;/h2&gt;

&lt;p&gt;A static website is exactly what it sounds like a site that serves fixed content such as HTML, CSS, JavaScript, images, and assets. There’s no backend logic, no database queries, and no server-side rendering. What you upload is what users see.&lt;/p&gt;

&lt;p&gt;This makes static websites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast&lt;/li&gt;
&lt;li&gt;Cheap to host&lt;/li&gt;
&lt;li&gt;Highly scalable&lt;/li&gt;
&lt;li&gt;Secure when configured correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS excels at this through &lt;strong&gt;Amazon S3&lt;/strong&gt; and &lt;strong&gt;Amazon CloudFront&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep Dive into Amazon S3
&lt;/h2&gt;

&lt;p&gt;S3 (Simple Storage Service) is not just a storage bucket it’s a highly durable object storage service designed to store and retrieve any amount of data.&lt;/p&gt;

&lt;p&gt;Key things I deeply understood today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;S3 stores objects, not filesystems&lt;/li&gt;
&lt;li&gt;Every object is accessed via a unique key&lt;/li&gt;
&lt;li&gt;S3 is globally durable but regionally hosted&lt;/li&gt;
&lt;li&gt;Public access must be &lt;strong&gt;explicitly controlled&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my setup, I created an S3 bucket using Terraform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"firstbucket"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To prevent accidental public exposure, I added a &lt;strong&gt;public access block&lt;/strong&gt;, which is critical for security:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_public_access_block"&lt;/span&gt; &lt;span class="s2"&gt;"block"&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;firstbucket&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;block_public_acls&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;block_public_policy&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;ignore_public_acls&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;restrict_public_buckets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that the bucket cannot be accessed publicly unless explicitly allowed through CloudFront.&lt;/p&gt;




&lt;h2&gt;
  
  
  Uploading Website Files to S3
&lt;/h2&gt;

&lt;p&gt;Instead of uploading files manually, Terraform can manage website content using &lt;code&gt;aws_s3_object&lt;/code&gt;. This is powerful because your infrastructure and content stay in sync.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_object"&lt;/span&gt; &lt;span class="s2"&gt;"object"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/www"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"**/*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;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;firstbucket&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;key&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/www/${each.value}"&lt;/span&gt;
  &lt;span class="nx"&gt;etag&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filemd5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/www/${each.value}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;content_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s2"&gt;"html"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"text/html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"css"&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"text/css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"js"&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application/javascript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"json"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"png"&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"image/png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"jpg"&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"image/jpeg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"svg"&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"image/svg+xml"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"application/octet-stream"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This automatically uploads &lt;strong&gt;HTML, CSS, and JavaScript&lt;/strong&gt; from the &lt;code&gt;www/&lt;/code&gt; directory into S3 with correct content types.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing CloudFront (The Game Changer)
&lt;/h2&gt;

&lt;p&gt;CloudFront is AWS’s &lt;strong&gt;Content Delivery Network (CDN)&lt;/strong&gt;. Instead of serving content directly from S3, CloudFront caches content at edge locations close to users, dramatically improving performance and security.&lt;/p&gt;

&lt;p&gt;I created an &lt;strong&gt;Origin Access Control (OAC)&lt;/strong&gt; so CloudFront can securely access the S3 bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudfront_origin_access_control"&lt;/span&gt; &lt;span class="s2"&gt;"origin_access_control"&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;"demo-oac"&lt;/span&gt;
  &lt;span class="nx"&gt;origin_access_control_origin_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt;
  &lt;span class="nx"&gt;signing_behavior&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"always"&lt;/span&gt;
  &lt;span class="nx"&gt;signing_protocol&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sigv4"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I defined the CloudFront distribution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudfront_distribution"&lt;/span&gt; &lt;span class="s2"&gt;"s3_distribution"&lt;/span&gt; &lt;span class="p"&gt;{&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&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstbucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket_regional_domain_name&lt;/span&gt;
    &lt;span class="nx"&gt;origin_access_control_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cloudfront_origin_access_control&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin_access_control&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;origin_id&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin_id&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;default_root_object&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;default_cache_behavior&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;allowed_methods&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;cached_methods&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;target_origin_id&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin_id&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="nx"&gt;viewer_certificate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cloudfront_default_certificate&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;This configuration ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTPS is enforced&lt;/li&gt;
&lt;li&gt;Content is cached globally&lt;/li&gt;
&lt;li&gt;S3 is never directly exposed to users&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Locking Down S3 with Bucket Policy
&lt;/h2&gt;

&lt;p&gt;To complete the security setup, I added a bucket policy allowing &lt;strong&gt;only CloudFront&lt;/strong&gt; to access the S3 objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_policy"&lt;/span&gt; &lt;span class="s2"&gt;"allow_cf"&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;firstbucket&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;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cloudfront.amazonaws.com"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;
      &lt;span class="nx"&gt;Resource&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${aws_s3_bucket.firstbucket.arn}/*"&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a best practice for production-grade static websites.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenge Faced
&lt;/h2&gt;

&lt;p&gt;I couldn’t fully complete the deployment because my AWS account wasn’t approved to create CloudFront distributions. That was frustrating, but also a real-world lesson: &lt;strong&gt;permissions and account limits matter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Even so, the Terraform configuration is solid and production-ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Day 14 was a big milestone. I now understand:&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/bK6RimAv2nQ"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How static websites work on AWS&lt;/li&gt;
&lt;li&gt;Why S3 is ideal for hosting static content&lt;/li&gt;
&lt;li&gt;How CloudFront improves performance and security&lt;/li&gt;
&lt;li&gt;How Terraform ties everything together cleanly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This wasn’t just theory, it was real infrastructure thinking.&lt;/p&gt;

&lt;p&gt;On to Day 15&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>aws</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Day 13 : Understanding Terraform Data Sources Like a Human, Not a Robot</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Tue, 09 Dec 2025 18:25:17 +0000</pubDate>
      <link>https://dev.to/alafiz/day-13-understanding-terraform-data-sources-like-a-human-not-a-robot-4p04</link>
      <guid>https://dev.to/alafiz/day-13-understanding-terraform-data-sources-like-a-human-not-a-robot-4p04</guid>
      <description>&lt;p&gt;Today’s session was one of those “lightbulb days” in Terraform the point where things finally &lt;em&gt;click&lt;/em&gt;. Day 13 was all about &lt;strong&gt;Terraform Data Sources&lt;/strong&gt; and honestly, this is one of the core concepts that separates beginners from engineers who actually understand infrastructure workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Data Sources: The Missing Puzzle Piece&lt;/strong&gt;:
&lt;/h3&gt;

&lt;p&gt;I’ve been creating resources in Terraform for days EC2 instances, S3 buckets, IAM, and more but today I finally understood why &lt;strong&gt;Data Sources&lt;/strong&gt; matter so much. While resources are things Terraform &lt;em&gt;creates&lt;/em&gt;, &lt;strong&gt;data sources are things Terraform &lt;em&gt;reads&lt;/em&gt; from AWS&lt;/strong&gt;. This difference is massive. Data sources allow you to use existing AWS components instead of hardcoding IDs or manually copying values from your console. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more manually typing VPC IDs.&lt;/li&gt;
&lt;li&gt;No more guessing subnets.&lt;/li&gt;
&lt;li&gt;No more hunting for AMI IDs.&lt;/li&gt;
&lt;li&gt;No more breaking infrastructure because an ID changed.
Terraform stays synced with AWS, and your code becomes smarter, reusable and more consistent.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Fetch the Default VPC&lt;/strong&gt;:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"vpc_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filter&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;"tag:Name"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of hardcoding a VPC ID, this fetches the one tagged “default.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Small change and big improvement in reliability.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Fetch a Subnet Inside that VPC&lt;/strong&gt;:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"shared"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filter&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;"tag:Name"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"subneta"&lt;/span&gt;&lt;span class="p"&gt;]&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;data&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;vpc_name&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;This pulls the subnet tagged “subneta,” within the VPC I retrieved earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now Terraform can dynamically locate a subnet without me touching the AWS console.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Fetch the Latest Amazon Linux 2 AMI&lt;/strong&gt;:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_ami"&lt;/span&gt; &lt;span class="s2"&gt;"linux2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;owners&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"amazon"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;most_recent&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;filter&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;"name"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"amzn2-ami-hvm-*-x86_64-gp2"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;filter&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;"virtualization-type"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hvm"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  This is the cleanest way to ensure your instance always uses the &lt;strong&gt;latest stable Amazon Linux 2 image&lt;/strong&gt;.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Deploy an EC2 Instance Using ONLY Dynamic Values&lt;/strong&gt;:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;linux2&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;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shared&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;tags&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;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No hardcoded IDs.&lt;br&gt;
No guesswork.&lt;br&gt;
No outdated AMIs.&lt;br&gt;
Everything is tied to real AWS values at the time of deployment.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Backend Configuration (S3 Remote State)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I also configured my backend for state storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend "s3" {
  bucket       = "devopswithzacks-terraform-state"
  key          = "dev/terraform.tfstate"
  region       = "us-east-1"
  encrypt      = true
  use_lockfile = true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This moved my Terraform state from local to AWS S3, enabling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Team collaboration.&lt;/li&gt;
&lt;li&gt;State locking.&lt;/li&gt;
&lt;li&gt;Zero risk of losing state locally.&lt;/li&gt;
&lt;li&gt;Better security and versioning.
This is how real-world environments are structured.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Commands I Used&lt;/strong&gt;:
&lt;/h2&gt;

&lt;p&gt;Same workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform apply --auto-approve&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform destroy --auto-approve&lt;/code&gt;
Smooth, clean and predictable especially now that everything is dynamic.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;My Experience Today&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Day 13 was surprisingly straightforward compared to the last few days.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/MSr67lWCyD8"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;No major blockers.&lt;br&gt;
No syntax confusion.&lt;br&gt;
No AWS permission issues.&lt;/p&gt;

&lt;p&gt;Just clean logic and a clearer understanding of how Terraform interacts with existing AWS infrastructure.&lt;/p&gt;

&lt;p&gt;Data sources are the bridge between &lt;em&gt;what you already have&lt;/em&gt; and &lt;em&gt;what you're trying to build&lt;/em&gt;. Today made that crystal clear.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Data sources are easily one of the most powerful features in Terraform. They eliminate hardcoding, reduce errors, and make infrastructure code scalable and environment-aware.&lt;br&gt;
Today felt like a big step forward, my Terraform code finally behaves like it belongs in production.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Day 11 &amp; 12 of My #30DaysOfAWSTerraform Journey : Mastering Terraform Functions (Parts 1 &amp; 2)</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Mon, 08 Dec 2025 18:01:28 +0000</pubDate>
      <link>https://dev.to/alafiz/day-11-12-of-my-30daysofawsterraform-journey-mastering-terraform-functions-parts-1-2-43k9</link>
      <guid>https://dev.to/alafiz/day-11-12-of-my-30daysofawsterraform-journey-mastering-terraform-functions-parts-1-2-43k9</guid>
      <description>&lt;p&gt;Today’s learning felt like I unlocked “Terraform Superpowers.” Days 11 and 12 focused entirely on &lt;strong&gt;Terraform functions&lt;/strong&gt; and honestly, this is the point where Terraform stops feeling like a configuration tool and starts behaving like a full scripting engine. These two days were packed with transformations, validations, formatting operations, type coercions, and even time-based computations. Everything I learned here answers a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“How do you make your Terraform code smarter, cleaner, and more automated?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s break it down.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned (Day 11 + 12 Combined):
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. String, Numeric, Type Conversion and Collection Functions&lt;/strong&gt;:
&lt;/h3&gt;

&lt;p&gt;I worked with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;lower()&lt;/code&gt;, &lt;code&gt;substr()&lt;/code&gt;, &lt;code&gt;replace()&lt;/code&gt; to clean bucket names&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;split()&lt;/code&gt;, &lt;code&gt;concat()&lt;/code&gt;, &lt;code&gt;toset()&lt;/code&gt; to structure lists and sets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max()&lt;/code&gt;, &lt;code&gt;min()&lt;/code&gt;, &lt;code&gt;sum()&lt;/code&gt;, &lt;code&gt;abs()&lt;/code&gt; to compute costs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lookup()&lt;/code&gt; to select instance sizes based on environment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;merge()&lt;/code&gt; to neatly combine tag maps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These functions allowed me to sanitize inputs, enforce formats, build dynamic rules, and build more reusable infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Date &amp;amp; Time Functions&lt;/strong&gt;:
&lt;/h3&gt;

&lt;p&gt;This was one of my favorite parts. I generated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;timestamps with &lt;code&gt;timestamp()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;multiple time formats with &lt;code&gt;formatdate()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helped me create dynamic backup names like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backup-20250212
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also reinforced something important:&lt;br&gt;
&lt;strong&gt;format strings in Terraform are case-sensitive&lt;/strong&gt; and that tripped me up until I figured it out.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;3. File Functions &amp;amp; JSON Handling&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Terraform can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check if a file exists (&lt;code&gt;fileexists()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;read file contents (&lt;code&gt;file()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;parse JSON into an object (&lt;code&gt;jsondecode()&lt;/code&gt;).
I used this to read a real &lt;code&gt;config.json&lt;/code&gt; file and push that JSON directly into AWS Secrets Manager. That alone felt like a full real-world DevOps task.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;4. Validations &amp;amp; Error Handling&lt;/strong&gt;:
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;validation&lt;/code&gt; block in variables saved me from bad inputs.&lt;br&gt;
I enforced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;instance types starting with &lt;code&gt;t2&lt;/code&gt; or &lt;code&gt;t3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;backup names ending with &lt;code&gt;_backup&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes Terraform behave like it has guardrails.&lt;/p&gt;
&lt;h1&gt;
  
  
  Practical Tasks I Completed
&lt;/h1&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;1. Sanitizing Inputs for S3 Buckets &amp;amp; Projects&lt;/strong&gt;:
&lt;/h3&gt;

&lt;p&gt;I cleaned up project and bucket names automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;converted to lowercase.&lt;/li&gt;
&lt;li&gt;removed spaces.&lt;/li&gt;
&lt;li&gt;trimmed unwanted symbols.&lt;/li&gt;
&lt;li&gt;ensured bucket names stayed within the AWS length limit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures Terraform produces valid AWS names every time.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;2. Generating Dynamic Security Group Rules&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;split()&lt;/code&gt; and &lt;code&gt;for&lt;/code&gt; expressions, I built security group rules from a comma-separated string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;80,443,8080,3306
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform automatically created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rule names&lt;/li&gt;
&lt;li&gt;port numbers&lt;/li&gt;
&lt;li&gt;descriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final output was clean and automated.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Dynamically Selecting Instance Sizes&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lookup(var.instance_sizes, var.environment, "t2.micro")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform selects the right instance size based on the environment. No manual switching.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Reading Config from JSON and Storing It in Secrets Manager&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This was the most real-world part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detected if the file existed&lt;/li&gt;
&lt;li&gt;Decoded the JSON&lt;/li&gt;
&lt;li&gt;Passed it into AWS Secrets Manager&lt;/li&gt;
&lt;li&gt;Output the secret ARN&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real DevOps workflow unlocked right here.&lt;/p&gt;




&lt;h1&gt;
  
  
  Challenges I Faced
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Case Sensitivity in &lt;code&gt;formatdate()&lt;/code&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I spent time debugging formatted timestamps because I didn’t know the format string was case-sensitive.&lt;br&gt;
Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;YYYYMMDD  ≠  yyyymmdd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform humbled me.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Missing IAM Permission for Secrets Manager&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Terraform kept failing until I realized I hadn’t added SecretsManagerReadWrite permissions to my IAM user.&lt;br&gt;
Once I fixed that, everything ran smoothly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd19fdrxegez6rynopvtx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd19fdrxegez6rynopvtx.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xs2vj0nb3fsfvwg7kys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xs2vj0nb3fsfvwg7kys.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwthu89evtcwaxox542sj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwthu89evtcwaxox542sj.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Commands I Used
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform apply --auto-approve&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform destroy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple commands but yet powerful execution.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/-dKsmU4Z1hM"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/ZYCCu9rZkU8"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;These two days changed the way I think about Terraform. Before now, I was writing static infrastructure. After Day 11 and 12, I’m writingcintelligent infrastructure.&lt;/p&gt;

&lt;p&gt;Functions allowed me to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sanitize inputs.&lt;/li&gt;
&lt;li&gt;validate configurations.&lt;/li&gt;
&lt;li&gt;automate naming.&lt;/li&gt;
&lt;li&gt;compute values.&lt;/li&gt;
&lt;li&gt;parse files.&lt;/li&gt;
&lt;li&gt;build dynamic outputs.&lt;/li&gt;
&lt;li&gt;and control infrastructure behavior using pure logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the kind of knowledge that separates someone who “uses Terraform” from someone who engineers Terraform solutions.&lt;/p&gt;

&lt;p&gt;Tomorrow, we climb another level.&lt;br&gt;
But today, I’m proud of this milestone.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>devops</category>
      <category>awschallenge</category>
    </item>
    <item>
      <title>Day 10 : Conditionals, Dynamic Blocks &amp; Splat Expressions</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Thu, 04 Dec 2025 18:07:45 +0000</pubDate>
      <link>https://dev.to/alafiz/day-10-conditionals-dynamic-blocks-splat-expressions-41ib</link>
      <guid>https://dev.to/alafiz/day-10-conditionals-dynamic-blocks-splat-expressions-41ib</guid>
      <description>&lt;p&gt;Today was one of those days where everything just clicked. I didn’t just learn Terraform concepts, I finally felt like I understood why they matter and how they fit into real-world infrastructure. Compared to some earlier days where things felt abstract. Day 10 was smooth, logical, and honestly very satisfying.&lt;br&gt;
I focused on three main concepts today: conditional expressions, dynamic blocks and splat expressions. All three are tools that help clean up Terraform configurations, reduce repetition and make infrastructure adapt to inputs automatically. Once I saw them in action, it finally made sense why more experienced DevOps engineers rely on them heavily.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;1. Conditional Expressions (Choosing Resources Based on Environment)&lt;/strong&gt;:
&lt;/h3&gt;

&lt;p&gt;The first thing I learned was how to use conditional expressions in Terraform. These allow you to change behavior based on variables. Instead of hardcoding instance types, you can make Terraform decide automatically.&lt;/p&gt;

&lt;p&gt;In my &lt;code&gt;aws_instance&lt;/code&gt; resource, I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;instance_type = var.environment == "dev" ? "t2.micro" : "t3.micro"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one line alone felt powerful, It means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the environment is &lt;strong&gt;dev&lt;/strong&gt;, use a &lt;strong&gt;t2.micro&lt;/strong&gt; instance.&lt;/li&gt;
&lt;li&gt;Otherwise staging, production, anything else use a &lt;strong&gt;t3.micro&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It removes the headache of manually editing resource blocks whenever the environment changes. Just set the environment in &lt;code&gt;terraform.tfvars&lt;/code&gt;, run the plan, and Terraform decides for you.&lt;/p&gt;

&lt;p&gt;In my case, I set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;environment = "staging"
instance_count = 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform automatically picked &lt;strong&gt;t3.micro&lt;/strong&gt; exactly as expected, Clean and efficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Dynamic Blocks (Generating Nested Arguments Automatically)&lt;/strong&gt;:
&lt;/h3&gt;

&lt;p&gt;Next, I went deeper into &lt;strong&gt;dynamic blocks&lt;/strong&gt;, which turned out to be one of the most useful features so far. Instead of manually writing multiple ingress rules inside a security group, I used a dynamic block to loop through all rules defined in &lt;code&gt;ingress_rules&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My block looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dynamic "ingress" {
  for_each = var.ingress_rules
  content {
    from_port   = ingress.value.from_port
    to_port     = ingress.value.to_port
    cidr_blocks = ingress.value.cidr_blocks
    protocol    = ingress.value.protocol
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform took each rule from the variable and automatically generated the nested ingress configurations inside the security group. No duplication, No messy code, Just one clean dynamic block. The &lt;code&gt;ingress_rules&lt;/code&gt; variable contains two rules: port 80 and port 443. &lt;br&gt;
Terraform turned them into two separate ingress blocks without me writing them manually. This is one of those features that separates beginners from people who are starting to structure Terraform properly.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;3. Splat Expressions (Collecting Values From Multiple Resources)&lt;/strong&gt; :
&lt;/h3&gt;

&lt;p&gt;Since I deployed multiple EC2 instances (thanks to &lt;code&gt;count = var.instance_count&lt;/code&gt;), I had multiple instance IDs. Instead of referencing each one separately, I used a &lt;strong&gt;splat expression&lt;/strong&gt; to grab all their IDs at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;locals {
  all_instance_ids = aws_instance.example[*].id
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I output them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"instance_ids"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all_instance_ids&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tf init
tf plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform clearly showed all instance IDs generated. Simple, elegant and exactly what splat expressions are meant for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands I Used:
&lt;/h3&gt;

&lt;p&gt;Nothing complicated today just the essentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;tf init&lt;/strong&gt; : initialize Terraform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tf plan&lt;/strong&gt; : preview changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Were the Tasks Hard?&lt;/strong&gt;:
&lt;/h3&gt;

&lt;p&gt;Not at all and that’s not because they were easy, but because every lesson before this point built up to today. By the time I started writing conditionals, dynamic blocks, and splat expressions, the syntax felt natural.&lt;/p&gt;

&lt;p&gt;Everything was logical, predictable and connected to real Terraform use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Day 10 wasn’t heavy or confusing. It was clean, organized, and practical. I used features that real DevOps engineers rely on daily:&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/R4ShnFDJwI8"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Selecting resources per environment&lt;/li&gt;
&lt;li&gt;Dynamically generating nested blocks&lt;/li&gt;
&lt;li&gt;Collecting attributes from multiple resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part is that I didn’t just follow instructions I understood why each part matters. And If i keep at this pace, Terraform won’t just be something I’m learning. It’ll be something I can confidently use to manage infrastructure the right way.&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>devops</category>
      <category>learning</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Day 9 of #30DaysofAWSTerraform: Mastering Lifecycle Rules (This One Humbled Me )</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Thu, 04 Dec 2025 11:16:05 +0000</pubDate>
      <link>https://dev.to/alafiz/day-9-of-30daysofawsterraform-mastering-lifecycle-rules-this-one-humbled-me--723</link>
      <guid>https://dev.to/alafiz/day-9-of-30daysofawsterraform-mastering-lifecycle-rules-this-one-humbled-me--723</guid>
      <description>&lt;p&gt;Day 9 was easily one of the most ah, so this is what real Terraform work looks like moments for me.&lt;br&gt;
Lifecycle rules are one of those things people don’t talk about enough, but they are absolutely critical once you start managing real infrastructure not just classroom examples.&lt;br&gt;
Today was tough. I struggled, but the struggle paid off because everything finally clicked.&lt;/p&gt;

&lt;p&gt;What I Learned Today:&lt;/p&gt;
&lt;h2&gt;
  
  
  Lifecycle Rules:
&lt;/h2&gt;

&lt;p&gt;I finally understood how Terraform decides when to replace a resource, when to keep it, and what to do if something changes.&lt;/p&gt;

&lt;p&gt;The lifecycle block gives you deep control, and today I explored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
prevent_destroy

replace_triggered_by

ignore_changes

preconditions

postconditions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each one solves a real problem that Terraform can’t magically guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  1 - create_before_destroy (No more downtime):
&lt;/h2&gt;

&lt;p&gt;I created an EC2 instance where Terraform should provision the new one before terminating the old one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lifecycle {
  create_before_destroy = true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is perfect for production environments. If an update happens, Terraform won’t delete your server first before it replaces it safely.&lt;/p&gt;

&lt;p&gt;My web_server resource used this rule, and seeing Terraform gracefully handle replacement was actually beautiful.&lt;/p&gt;

&lt;h2&gt;
  
  
  2 - prevent_destroy (Protecting Critical Resources):
&lt;/h2&gt;

&lt;p&gt;Next, I worked on an S3 bucket that represents critical production data. Even if someone tries a terraform destroy, Terraform should refuse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lifecycle {
  prevent_destroy = false
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally this should be true, but in my setup, it was kept false for learning purposes. Still, the concept is clear because this flag is how you avoid accidental disasters.&lt;/p&gt;

&lt;h2&gt;
  
  
  3 - ignore_changes (Keeping Terraform From Being Too Strict):
&lt;/h2&gt;

&lt;p&gt;I used this rule in the AutoScaling Group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lifecycle {
  ignore_changes = [
    desired_capacity
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Terraform:&lt;br&gt;
“Don’t fight me over these attributes. If AWS auto-scales it, don’t force it back.” It prevents an endless cycle of “Terraform wants 2 AWS scaled to 3.”&lt;/p&gt;
&lt;h2&gt;
  
  
  4 - replace_triggered_by (Automatic Replacement When Another Resource Changes):
&lt;/h2&gt;

&lt;p&gt;This one was interesting and super practical. I linked an EC2 instance to a security group using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;replace_triggered_by = [
  aws_security_group.app_sg.id
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;br&gt;
If the security group changes, replace the instance automatically.&lt;/p&gt;
&lt;h2&gt;
  
  
  5 - Preconditions &amp;amp; Postconditions Validating Before and After Creation: This is where things got really smart.
&lt;/h2&gt;
&lt;h2&gt;
  
  
  Precondition Example:
&lt;/h2&gt;

&lt;p&gt;Before creating a bucket, I validate the region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;precondition {
  condition     = contains(var.allowed_regions, data.aws_region.current.name)
  error_message = "Region not allowed!"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform stops immediately if the region isn’t permitted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postcondition Example:
&lt;/h2&gt;

&lt;p&gt;After creating a bucket, I validated tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postcondition {
  condition     = contains(keys(self.tags), "Compliance")
  error_message = "Bucket must have a Compliance tag!"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s like giving Terraform the ability to self-audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commands I Ran:
&lt;/h2&gt;

&lt;p&gt;These were essential for testing lifecycles:&lt;br&gt;
&lt;/p&gt;

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

terraform apply --auto-approve.

terraform destroy --auto-approve.

terraform output.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This day involved a lot of destroying and recreating resources to fully understand lifecycle behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did I Struggle?
&lt;/h2&gt;

&lt;p&gt;Absolutely. Lifecycle rules feel simple when someone explains them, but once you start applying them across EC2, S3, ASG, SG, and templates…&lt;br&gt;
your brain melts a little. But after a few failed applies, bucket recreations, and region validation errors, everything started making perfect sense. This was one of the most important days so far.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/60tOSwpvldY"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Day 9 made Terraform feel like a real-world IaC tool, not just resource and variable definitions. Now I understand why lifecycle rules are considered advanced. They give you the power to prevent accidental disasters, manage downtime, control how replacements happen, apply compliance, enforce guardrails.&lt;/p&gt;

&lt;p&gt;Another big step forward.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>devops</category>
      <category>awschallenge</category>
    </item>
    <item>
      <title>Day 8: Terraform Finally Makes Sense (Loops, Count, For_Each &amp; Dependencies)</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Wed, 03 Dec 2025 08:50:33 +0000</pubDate>
      <link>https://dev.to/alafiz/day-8-terraform-finally-makes-sense-loops-count-foreach-dependencies-4n9j</link>
      <guid>https://dev.to/alafiz/day-8-terraform-finally-makes-sense-loops-count-foreach-dependencies-4n9j</guid>
      <description>&lt;p&gt;Today was one of those days where everything just clicked.&lt;br&gt;
No stress, no confusion… just straight understanding. Terraform started feeling less like “DevOps theory” and more like something I can actually control confidently.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;I finally understood &lt;strong&gt;meta-arguments&lt;/strong&gt; especially &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;for_each&lt;/code&gt;, and &lt;code&gt;depends_on&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Learned how to use &lt;strong&gt;count.index&lt;/strong&gt; to loop through lists.&lt;/li&gt;
&lt;li&gt;Learned how &lt;strong&gt;for_each&lt;/strong&gt; works with sets and how it gives better resource naming.&lt;/li&gt;
&lt;li&gt;Saw why dependencies matter so Terraform doesn’t rush resources in the wrong order.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honestly, these concepts looked scary from afar, but once I practiced them… they were simple.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Created &lt;strong&gt;two S3 buckets&lt;/strong&gt; using &lt;code&gt;count&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Used a list of names, looped with &lt;code&gt;count.index&lt;/code&gt;, and everything created perfectly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Created &lt;strong&gt;two more S3 buckets&lt;/strong&gt; using &lt;code&gt;for_each&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Used a set this time and referenced bucket names using &lt;code&gt;each.value&lt;/code&gt;.&lt;br&gt;
Added &lt;code&gt;depends_on&lt;/code&gt; just to make sure Terraform behaves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Displayed bucket names and IDs
&lt;/h3&gt;

&lt;p&gt;Used for-expressions to print out everything cleanly with &lt;code&gt;tf output&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands I ran
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;tf plan&lt;/code&gt;&lt;br&gt;
&lt;code&gt;tf apply --auto-approve&lt;/code&gt;&lt;br&gt;
&lt;code&gt;tf output&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Everything worked without drama.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Bit of My Code:
&lt;/h2&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="nx"&gt;aws_s3_bucket&lt;/span&gt; &lt;span class="s2"&gt;"bucket5"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&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="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;tags&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;tags&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="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"s3_bucket_names"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="nx"&gt;in&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;bucket1&lt;/span&gt; &lt;span class="err"&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;bucket&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;Today was smooth.&lt;br&gt;
The tasks didn’t overwhelm me at all.&lt;br&gt;
It felt like the previous lessons were preparing me for today, and everything connected naturally.&lt;/p&gt;

&lt;p&gt;The more I practice Terraform, the more I feel like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Okay… I can actually do this DevOps thing.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  DAY 8 : 

  &lt;iframe src="https://www.youtube.com/embed/XMMsnkovNX4"&gt;
  &lt;/iframe&gt;



&lt;/h2&gt;

&lt;h2&gt;
  
  
  Key Lessons from Day 8
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;count&lt;/code&gt; = great for lists&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;for_each&lt;/code&gt; = cleaner when you want unique names&lt;/li&gt;
&lt;li&gt;Sets work nicely with &lt;code&gt;for_each&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;depends_on&lt;/code&gt; prevents Terraform from rushing&lt;/li&gt;
&lt;li&gt;For-expressions make outputs neat and professional&lt;/li&gt;
&lt;/ul&gt;

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