<?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.us-east-2.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>Leveling Up CI/CD: Automated Versioning and the Leap to AWS</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Sat, 15 Aug 2026 21:28:05 +0000</pubDate>
      <link>https://dev.to/alafiz/leveling-up-cicd-automated-versioning-and-the-leap-to-aws-4oc8</link>
      <guid>https://dev.to/alafiz/leveling-up-cicd-automated-versioning-and-the-leap-to-aws-4oc8</guid>
      <description>&lt;p&gt;This week has been all about refining the continuous integration and continuous deployment (CI/CD) pipeline. After successfully containerizing a Node.js application and pushing it to Docker Hub, it was time to tackle a crucial DevOps practice: &lt;strong&gt;Automated Versioning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is a breakdown of what I implemented this week, the hurdles I overcame, and what is next on the roadmap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demystifying Semantic Versioning (SemVer)
&lt;/h3&gt;

&lt;p&gt;Before automating anything, it is essential to understand how application versions are structured. A standard version number is broken down into three parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Major Version:&lt;/strong&gt; Contains big changes that are not backward-compatible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minor Version:&lt;/strong&gt; Introduces new API features but remains backward-compatible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patch:&lt;/strong&gt; Dedicated strictly to minor changes and bug fixes without altering the API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build tools come equipped with commands to increase these versions dynamically during the build process, which is exactly what I integrated into the Jenkins pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Automated Versioning Pipeline
&lt;/h3&gt;

&lt;p&gt;The goal for this module was straightforward: &lt;strong&gt;Increment Version $\rightarrow$ Build App $\rightarrow$ Build Image $\rightarrow$ Push to Docker Repo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To achieve this, I updated the &lt;code&gt;Jenkinsfile&lt;/code&gt; to run &lt;code&gt;npm version patch --no-git-tag-version&lt;/code&gt;. This command dynamically extracts the new version (e.g., bumping &lt;code&gt;1.0.1&lt;/code&gt; to &lt;code&gt;1.0.2&lt;/code&gt;), which I then stored as an environment variable (&lt;code&gt;env.IMAGE_NAME&lt;/code&gt;) to tag the newly packaged Docker image automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Local vs. Remote" Challenge
&lt;/h3&gt;

&lt;p&gt;During implementation, I ran into a classic CI/CD hurdle. When the Jenkins pipeline ran the &lt;code&gt;npm version&lt;/code&gt; command, the new version was only updated &lt;em&gt;locally&lt;/em&gt; within the Jenkins workspace. It did not update the actual source code repository on GitHub. This meant every subsequent build would pull the old version number from the repo, causing an endless overwrite loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;br&gt;
The fix required adding a dedicated Git stage to the end of the pipeline. By securely injecting GitHub credentials into the workspace, Jenkins now automatically stages the modified &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; files and pushes the updated version directly back to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;Here is the stage that handles this synchronization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Updating to Github'&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Updating the Github repo with the latest version....'&lt;/span&gt;
            &lt;span class="n"&gt;withCredentials&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="n"&gt;usernamePassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;credentialsId:&lt;/span&gt; &lt;span class="s1"&gt;'github.credentials'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;passwordVariable:&lt;/span&gt; &lt;span class="s1"&gt;'PASS'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;usernameVariable:&lt;/span&gt; &lt;span class="s1"&gt;'USER'&lt;/span&gt;&lt;span class="o"&gt;)])&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'git config user.email "jenkins@example.com"'&lt;/span&gt;
                &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'git config user.name "jenkins"'&lt;/span&gt; 
                &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'git remote set-url origin https://${USER}:${PASS}@github.com/AlAfiz/Devops-app.git'&lt;/span&gt;
                &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'git add package.json package-lock.json'&lt;/span&gt;
                &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'git commit -m "feat: update the github repo with the latest version"'&lt;/span&gt;
                &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'git push origin HEAD:main'&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: I also made sure to implement a &lt;code&gt;Clean Workspace&lt;/code&gt; step (&lt;code&gt;rm -f *.tgz&lt;/code&gt;) at the very beginning of the pipeline to prevent old artifact tarballs from getting swallowed up in the new builds!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Next: Infrastructure as a Service (AWS)
&lt;/h3&gt;

&lt;p&gt;With a robust, self-versioning pipeline now fully operational, I am officially shifting gears into a brand new module: &lt;strong&gt;AWS Services&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Understanding Infrastructure as a Service (IaaS) is the next major milestone. Over the coming weeks, the focus will be on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managing users and permissions with AWS IAM.&lt;/li&gt;
&lt;li&gt;Understanding Regions and Availability Zones.&lt;/li&gt;
&lt;li&gt;Configuring private networks using VPCs.&lt;/li&gt;
&lt;li&gt;Creating virtual servers via EC2.&lt;/li&gt;
&lt;li&gt;Connecting the Jenkins pipeline to continuously deploy directly to an EC2 instance!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The journey from a local environment to the cloud is underway. Stay tuned!&lt;/p&gt;

</description>
      <category>automation</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Automating the Workflow: My Journey from Jenkins Freestyle Jobs to Declarative Pipelines</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Sun, 09 Aug 2026 21:16:05 +0000</pubDate>
      <link>https://dev.to/alafiz/automating-the-workflow-my-journey-from-jenkins-freestyle-jobs-to-declarative-pipelines-4pag</link>
      <guid>https://dev.to/alafiz/automating-the-workflow-my-journey-from-jenkins-freestyle-jobs-to-declarative-pipelines-4pag</guid>
      <description>&lt;h3&gt;
  
  
  The Infrastructure: Setting Up Jenkins on AWS
&lt;/h3&gt;

&lt;p&gt;The foundation of this project began by provisioning an Ubuntu EC2 instance on AWS. Setting up the environment meant defining strict networking rules (opening Port &lt;code&gt;22&lt;/code&gt; for SSH and Port &lt;code&gt;8080&lt;/code&gt; for the Jenkins UI) and structuring the Jenkins environment with clear access controls.&lt;/p&gt;

&lt;p&gt;In Jenkins, maintaining a secure and organized environment generally falls into two roles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Administrators:&lt;/strong&gt; Responsible for managing the Jenkins cluster, installing necessary plugins, and handling data backups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Users:&lt;/strong&gt; Focused purely on creating jobs to run their respective workflows.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Magic of Docker-out-of-Docker (DooD)
&lt;/h3&gt;

&lt;p&gt;One of the most critical architectural choices was deciding how to let Jenkins build Docker images without installing a heavy, nested Docker engine inside the Jenkins container itself. The solution was a Docker-out-of-Docker configuration.&lt;/p&gt;

&lt;p&gt;By running the following command, I spun up the Jenkins container while binding it directly to the host machine's Docker socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;-p&lt;/span&gt; 50000:50000 &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-v&lt;/span&gt; jenkins_home:/var/jenkins_home &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-v&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;which docker&lt;span class="si"&gt;)&lt;/span&gt;:/usr/bin/docker jenkins/jenkins:lts

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

&lt;/div&gt;



&lt;p&gt;This single command did a lot of heavy lifting. It mapped port &lt;code&gt;8080&lt;/code&gt; for the UI and &lt;code&gt;50000&lt;/code&gt; for Jenkins agent communication. More importantly, mapping &lt;code&gt;/var/run/docker.sock&lt;/code&gt; gave the Jenkins container the ability to pass &lt;code&gt;docker build&lt;/code&gt; and &lt;code&gt;docker push&lt;/code&gt; commands directly to the EC2 host’s Docker engine. (Just remember to ensure your &lt;code&gt;jenkins&lt;/code&gt; user has the right permissions to access that socket!).&lt;/p&gt;

&lt;h3&gt;
  
  
  Hitting the Wall: The Limitations of Freestyle Jobs
&lt;/h3&gt;

&lt;p&gt;Initially, I set up the application lifecycle running &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;npm test&lt;/code&gt;, and &lt;code&gt;npm pack&lt;/code&gt; using a standard Jenkins Freestyle job.&lt;/p&gt;

&lt;p&gt;Freestyle jobs are great for quick, isolated tasks. However, their limitations become glaringly obvious when you try to build a project with multiple automation steps. Orchestrating a complex workflow by chaining multiple Freestyle jobs together is inefficient and quickly leads to overloaded jobs that are impossible to maintain or debug.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Embracing Declarative Pipelines
&lt;/h3&gt;

&lt;p&gt;To solve this, I migrated the entire workflow to a Pipeline job. Pipelines are written in Groovy (a language similar to Java) and are designed specifically to orchestrate long-running, complex activities that can span multiple build agents.&lt;/p&gt;

&lt;p&gt;The repository for this project contains all the necessary configuration files, including the &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;Jenkinsfile&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt;, and the main application code in &lt;code&gt;server.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By placing a &lt;code&gt;Jenkinsfile&lt;/code&gt; directly in the repository, the pipeline became part of the application's source code (Infrastructure as Code). The declarative syntax allowed me to clearly define distinct stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checkout:&lt;/strong&gt; Pulling the latest code from GitHub.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install &amp;amp; Test:&lt;/strong&gt; Running standard Node.js NPM commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package &amp;amp; Build:&lt;/strong&gt; Compiling the &lt;code&gt;.tgz&lt;/code&gt; artifact and building the Docker image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Push:&lt;/strong&gt; Authenticating and pushing the final image to a private DockerHub repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Pro-Tip:&lt;/em&gt; When pushing to a private registry in a pipeline, you need to use Jenkins' "Secret text or file" plugin to securely pass your credentials into the &lt;code&gt;docker login&lt;/code&gt; command via a &lt;code&gt;withCredentials&lt;/code&gt; block, preventing sensitive data from leaking into your build logs!&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Next?
&lt;/h3&gt;

&lt;p&gt;Moving forward, I plan to implement &lt;strong&gt;Multibranch Pipelines&lt;/strong&gt;, which will allow Jenkins to automatically scan my repository, detect any branch containing a &lt;code&gt;Jenkinsfile&lt;/code&gt;, and dynamically build a pipeline for it using &lt;code&gt;when&lt;/code&gt; condition statements. I am also exploring Jenkins &lt;strong&gt;Parameters&lt;/strong&gt; to inject external configurations, making it incredibly easy to select specific application versions for deployment dynamically.&lt;/p&gt;

&lt;p&gt;The transition from clicking through the Jenkins UI to writing robust, version-controlled pipelines has completely transformed how I approach CI/CD.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>automation</category>
      <category>techtalks</category>
      <category>containers</category>
    </item>
    <item>
      <title>From Localhost to AWS ECR: Architecting a 3-Tier Node.js App with Docker.</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Wed, 08 Jul 2026 20:16:42 +0000</pubDate>
      <link>https://dev.to/alafiz/from-localhost-to-aws-ecr-architecting-a-3-tier-nodejs-app-with-docker-3c4e</link>
      <guid>https://dev.to/alafiz/from-localhost-to-aws-ecr-architecting-a-3-tier-nodejs-app-with-docker-3c4e</guid>
      <description>&lt;p&gt;As a Cloud and DevOps Engineer, one of the most satisfying milestones is taking an application that "works on my machine" and transforming it into a robust, portable, and scalable architecture.&lt;/p&gt;

&lt;p&gt;Recently, I decided to containerize an Infrastructure Asset Tracker application I’ve been developing. The goal wasn't just to wrap the Node.js backend in a Docker container, but to orchestrate a complete 3-tier architecture (Backend, Database, and Admin Visualizer) using Docker Compose, and securely host the final image in AWS Elastic Container Registry (ECR).&lt;/p&gt;

&lt;p&gt;Here is a deep dive into the architecture, the optimizations I made, and the real-world debugging it took to get everything running seamlessly.&lt;/p&gt;

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

&lt;p&gt;The stack consists of three independent microservices communicating over an internal Docker network:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Backend (&lt;code&gt;devops-app:v1&lt;/code&gt;):&lt;/strong&gt; A custom Node.js REST API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Database (&lt;code&gt;mongodb&lt;/code&gt;):&lt;/strong&gt; The official MongoDB image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Visualizer (&lt;code&gt;mongo-express&lt;/code&gt;):&lt;/strong&gt; A web-based admin interface for MongoDB.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead of building a "Frankenstein" container (shoving the database and the app into one massive image), I strictly adhered to the one-concern-per-container rule. This ensures each layer can scale independently and fail gracefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: Crafting an Optimized Dockerfile
&lt;/h2&gt;

&lt;p&gt;Writing a &lt;code&gt;Dockerfile&lt;/code&gt; is easy. Writing an &lt;em&gt;optimized&lt;/em&gt; one takes deliberate engineering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Base Image &amp;amp; Shell Gotchas&lt;/strong&gt;&lt;br&gt;
I opted for &lt;code&gt;FROM node:18-alpine&lt;/code&gt;. Alpine Linux is incredibly lightweight and highly secure, keeping the final image size down to just ~51MB. However, this comes with a classic DevOps gotcha: Alpine strips out heavier utilities like &lt;code&gt;bash&lt;/code&gt;. When I needed to shell into the running container to verify my file structures, &lt;code&gt;docker exec -it &amp;lt;container&amp;gt; /bin/bash&lt;/code&gt; failed. I had to pivot to using the native &lt;code&gt;/bin/sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Leveraging Layer Caching&lt;/strong&gt;&lt;br&gt;
To make my builds lightning fast, I separated the dependency installation from the source code copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Because Docker caches layers sequentially, copying &lt;em&gt;only&lt;/em&gt; the &lt;code&gt;package.json&lt;/code&gt; first protects the heavy &lt;code&gt;npm install&lt;/code&gt; step. Now, if I change a single line of JavaScript or HTML, Docker uses the cached node modules and updates the code layer in less than a second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The &lt;code&gt;.dockerignore&lt;/code&gt; File&lt;/strong&gt;&lt;br&gt;
Crucially, I added a &lt;code&gt;.dockerignore&lt;/code&gt; file containing &lt;code&gt;node_modules&lt;/code&gt; and &lt;code&gt;.env&lt;/code&gt;. This prevents host-OS compiled C++ bindings from breaking the Alpine container and ensures my local database passwords never accidentally get baked into the production image.&lt;/p&gt;
&lt;h2&gt;
  
  
  Phase 2: Orchestration &amp;amp; Persistence with Docker Compose
&lt;/h2&gt;

&lt;p&gt;To wire the three containers together, I built a &lt;code&gt;docker-compose.yml&lt;/code&gt; (named &lt;code&gt;mongo.yaml&lt;/code&gt;) master blueprint. Two specific configurations were critical here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solving the Startup Race Condition&lt;/strong&gt;&lt;br&gt;
If the Node.js backend or Mongo Express boots up faster than the MongoDB database, they will throw connection timeouts and crash. To prevent this, I utilized &lt;code&gt;depends_on&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;This acts as a traffic cop, explicitly forcing the backend and visualizer to wait until the database container is fully initialized before attempting to connect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ensuring Data Persistence&lt;/strong&gt;&lt;br&gt;
Docker containers are ephemeral if you spin them down, the data inside dies with them. To ensure the infrastructure assets logged in my app survived a restart, I implemented a named Docker Volume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongodb_data:/data/db&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This maps a secure, permanent directory on the host machine directly into MongoDB’s internal storage path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: Real-World Debugging
&lt;/h2&gt;

&lt;p&gt;No deployment goes perfectly on the first try. Here are two specific hurdles I had to debug during the orchestration phase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Port Collisions (&lt;code&gt;ERR_CONNECTION_REFUSED&lt;/code&gt;):&lt;/strong&gt; During deployment, my Node.js container failed to bind to port 3000. Running &lt;code&gt;docker ps&lt;/code&gt; revealed the container was isolated (&lt;code&gt;3000/tcp&lt;/code&gt;) instead of mapped (&lt;code&gt;0.0.0.0:3000-&amp;gt;3000/tcp&lt;/code&gt;). The culprit? A ghost Node.js process still running on my local host. A quick &lt;code&gt;pkill node&lt;/code&gt; cleared the parking space, and a fresh &lt;code&gt;docker compose up -d&lt;/code&gt; successfully mapped the port.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Disconnected Daemon:&lt;/strong&gt; I encountered a massive red error: &lt;code&gt;failed to connect to the docker API at unix:///home/.../docker.sock&lt;/code&gt;. This was a context mismatch between my terminal and the Docker engine. Ensuring the Docker background service was awake and resetting the terminal context resolved the socket connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Phase 4: Pushing to AWS ECR
&lt;/h2&gt;

&lt;p&gt;With the architecture running flawlessly locally, I tagged my custom image and pushed it to a private AWS ECR repository.&lt;/p&gt;

&lt;p&gt;A fascinating detail about modern Docker (using the BuildKit engine) is how it handles the push. In the AWS console, I didn't just see one file; I saw three:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Image Index (Manifest List):&lt;/strong&gt; The master pointer for the &lt;code&gt;v1&lt;/code&gt; tag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Application Image:&lt;/strong&gt; The actual 51MB Node.js container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Attestation:&lt;/strong&gt; A cryptographic receipt proving exactly how the image was built and what dependencies were used a massive win for modern supply-chain security.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Containerizing this architecture solidified my mental model of how host machines, isolated networks, and storage layers interact. We've officially moved past "it works on my machine" to "it works exactly as designed, anywhere."&lt;/p&gt;

&lt;p&gt;Next on the roadmap: Automating this deployment flow with a complete CI/CD pipeline!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>docker</category>
      <category>node</category>
    </item>
    <item>
      <title>How I Escaped "Tutorial Hell" (And Put My First Real App on the Internet)</title>
      <dc:creator>Zakariyau Mukhtar</dc:creator>
      <pubDate>Mon, 18 May 2026 18:41:10 +0000</pubDate>
      <link>https://dev.to/alafiz/how-i-escaped-tutorial-hell-and-put-my-first-real-app-on-the-internet-284p</link>
      <guid>https://dev.to/alafiz/how-i-escaped-tutorial-hell-and-put-my-first-real-app-on-the-internet-284p</guid>
      <description>&lt;p&gt;Have you ever watched hours of coding tutorials, felt like you learned a lot, but still couldn't build a real project from scratch?&lt;/p&gt;

&lt;p&gt;That is called "tutorial hell." As someone diving into Cloud and DevOps engineering, I was stuck there. To break out, I realized I needed to stop watching videos and start building. Instead of creating a fake practice project, I took a real website my brother built and challenged myself to put it live on the internet using professional tools.&lt;/p&gt;

&lt;p&gt;Here is exactly how I did it, without the heavy tech jargon:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Packing It Up in a "Magic Box":&lt;/strong&gt;&lt;br&gt;
Usually, a website that works perfectly on one computer might completely break on another. To fix this, I used a tool called Docker. Think of Docker as a magic shipping container. I packed my brother's website, along with everything it needs to run, inside this invisible box. Now, no matter whose computer or cloud server opens that box, the website works perfectly every single time.&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%2Fa07dm2cbmsql6s20qzmg.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%2Fa07dm2cbmsql6s20qzmg.png" alt=" A picture of your multi-stage Dockerfile " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Fixing the "Refresh" Bug:&lt;/strong&gt;&lt;br&gt;
When I first tested the box on my laptop, the site loaded beautifully. But I noticed a bug: if I clicked on a different page and hit "refresh," the whole site crashed and gave me a &lt;code&gt;404 Not Found&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;The web server was getting confused and looking for files that didn't exist. I had to write a custom rule basically giving the server a map so it knew how to route lost visitors back to the right place. Bug squashed!&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%2Fztqunkrl5ce9latr35gb.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%2Fztqunkrl5ce9latr35gb.png" alt=" A picture of your custom Nginx configuration or the initial 404 error " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Automating the Boring Stuff:&lt;/strong&gt;&lt;br&gt;
I didn't want to manually repackage and upload the website every single time my brother wanted to change a button color. So, I wrote an automation script using GitHub Actions.&lt;/p&gt;

&lt;p&gt;Now, I have a "robot" working for me in the cloud. The second my brother saves new code, this robot wakes up, tests the code to make sure it isn't broken, packages it into a new Docker box, and securely gets it ready for the internet.&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%2Fdpp1krseflbt3xvxz8nq.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%2Fdpp1krseflbt3xvxz8nq.png" alt=" A picture of your GitHub Actions pipeline with the green checkmark " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The Trap of Over-Engineering:&lt;/strong&gt;&lt;br&gt;
This is where I almost made a huge mistake. When it was time to put the app on the internet, the idea of using Kubernetes came up. Kubernetes is a massive, highly complex system used by giant tech companies to keep thousands of servers running.&lt;/p&gt;

&lt;p&gt;I took a step back and asked: &lt;em&gt;"Isn't this total overkill for a simple web app?"&lt;/em&gt; It absolutely was. Using Kubernetes for this would be like buying a commercial airplane just to go to the grocery store. We scrapped that idea and went with a simple, free cloud hosting platform called Render instead.&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%2Fuzv1z4nbj6u3yw5ql112.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%2Fuzv1z4nbj6u3yw5ql112.png" alt="A picture of your Render dashboard showing the green " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Big Takeaway&lt;/strong&gt;&lt;br&gt;
This project taught me one of the most important lessons in tech: a good engineer doesn't just use the fanciest, most complex buzzwords. They use the right, simple tool for the exact job in front of them.&lt;/p&gt;

&lt;p&gt;If you are just starting your tech career, stop watching videos. Grab a project, break it, figure out how to fix it and put it on the internet. There is no substitute for building!&lt;/p&gt;

&lt;h2&gt;
  
  
  DevOps #TechJourney #LearnInPublic #CloudComputing
&lt;/h2&gt;

</description>
      <category>devops</category>
      <category>aiops</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <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>
  </channel>
</rss>
