<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Hasan Ashab</title>
    <description>The latest articles on Forem by Hasan Ashab (@hasan_ashab).</description>
    <link>https://forem.com/hasan_ashab</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3475962%2F23b27e85-15f8-4487-b721-f4b6f8bd3145.png</url>
      <title>Forem: Hasan Ashab</title>
      <link>https://forem.com/hasan_ashab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hasan_ashab"/>
    <language>en</language>
    <item>
      <title>Don’t Learn DevOps Before Understanding Web Development</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Thu, 16 Oct 2025 01:29:43 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/dont-learn-devops-before-understanding-web-development-g67</link>
      <guid>https://forem.com/hasan_ashab/dont-learn-devops-before-understanding-web-development-g67</guid>
      <description>&lt;p&gt;When people ask me how to start learning DevOps or Cloud, I always say the same thing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“First, build a simple three-tier web app — even if it’s just a Hello World.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And they usually look confused.&lt;br&gt;
&lt;em&gt;“Why? I want to be a DevOps engineer, not a web developer.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I get it — I said the same thing once.&lt;/p&gt;




&lt;h3&gt;
  
  
  How I Started
&lt;/h3&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%2F9oin2oq9akg0tnr861es.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%2F9oin2oq9akg0tnr861es.png" alt="DevOps and Cloud Engineer" width="800" height="457"&gt;&lt;/a&gt;&lt;br&gt;
I began my career as a &lt;strong&gt;backend-focused web developer&lt;/strong&gt;.&lt;br&gt;
I spent my days writing APIs, debugging weird server errors, and figuring out why that one endpoint suddenly returned 500 after deploying.&lt;/p&gt;

&lt;p&gt;Eventually, I moved into DevOps and Cloud Engineering — and that’s when I realized how much my development background saved me.&lt;/p&gt;

&lt;p&gt;Because here’s the truth:&lt;br&gt;
You &lt;em&gt;can&lt;/em&gt; become a DevOps engineer without learning web development — but you’ll never be an &lt;strong&gt;impactful&lt;/strong&gt; one until you understand &lt;em&gt;why&lt;/em&gt; things are built the way they are.&lt;/p&gt;

&lt;h3&gt;
  
  
  The “Why” Behind the Tools
&lt;/h3&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%2Fjp5xs3x5t9f0vdo4uppt.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%2Fjp5xs3x5t9f0vdo4uppt.png" alt="DevOps tools" width="800" height="457"&gt;&lt;/a&gt;&lt;br&gt;
A lot of new DevOps learners jump straight into Kubernetes, Docker, Terraform, or AWS.&lt;br&gt;
They follow the roadmaps, memorize commands, spin up clusters — and then hit a wall.&lt;/p&gt;

&lt;p&gt;Because when something breaks, they have no clue whether it’s an app problem, a config issue, or an architectural flaw.&lt;br&gt;
They know &lt;em&gt;how&lt;/em&gt; to deploy, but not &lt;em&gt;what&lt;/em&gt; they’re actually deploying.&lt;/p&gt;

&lt;p&gt;That’s what I call the &lt;strong&gt;“tooler trap”&lt;/strong&gt; — knowing tools, but not the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple 3-Tier App Can Teach You Everything
&lt;/h3&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%2Flsi0nv3vdhy5iv76qb9s.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%2Flsi0nv3vdhy5iv76qb9s.png" alt="three tier website architecture" width="800" height="457"&gt;&lt;/a&gt;&lt;br&gt;
You don’t need to build a full SaaS to understand the ecosystem.&lt;br&gt;
Just develop a &lt;strong&gt;3-tier Hello World&lt;/strong&gt; app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; even a static page with a button&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; a small API that returns “Hello, world”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; maybe a single table or record&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then deploy it.&lt;br&gt;
Host the backend somewhere, connect it to the DB, expose an endpoint, and open it in a browser.&lt;/p&gt;

&lt;p&gt;That process alone will teach you &lt;strong&gt;how data flows&lt;/strong&gt;, &lt;strong&gt;where bottlenecks form&lt;/strong&gt;, and &lt;strong&gt;why infrastructure matters&lt;/strong&gt;.&lt;br&gt;
Once you see that flow end-to-end, every DevOps concept — CI/CD, load balancing, monitoring, scaling — starts making &lt;em&gt;sense&lt;/em&gt;, not just &lt;em&gt;noise&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real DevOps Is About Solving Problems, Not Running Pipelines
&lt;/h3&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%2Fi12umbdhbhleloh2m5wr.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%2Fi12umbdhbhleloh2m5wr.png" alt="devops troubleshooting" width="800" height="457"&gt;&lt;/a&gt;&lt;br&gt;
A “tooler” can build a Jenkins pipeline or deploy to AWS.&lt;br&gt;
A real DevOps engineer can look at a system and ask,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Why is this deployment taking 8 minutes?”&lt;br&gt;
“Why is our API timing out?”&lt;br&gt;
“Why does our architecture break under load?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those questions can’t be answered without understanding the &lt;strong&gt;application layer&lt;/strong&gt;.&lt;br&gt;
If you’ve never built or debugged a web app, you’ll miss the reasoning behind every DevOps decision.&lt;/p&gt;

&lt;p&gt;DevOps is not about using tools — it’s about &lt;strong&gt;solving the pain developers face every day&lt;/strong&gt;.&lt;br&gt;
And you can’t fix pain you’ve never felt.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Advice
&lt;/h3&gt;

&lt;p&gt;If you’re new and aiming for DevOps or Cloud:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yes&lt;/strong&gt;, learn the tools. But first, build something that &lt;em&gt;uses&lt;/em&gt; them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yes&lt;/strong&gt;, you can skip web dev — but then you’ll miss the “why” that makes DevOps meaningful.&lt;/li&gt;
&lt;li&gt;Start small. A simple 3-tier Hello World will teach you more than any crash course on Terraform or Kubernetes ever could.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because once you understand &lt;em&gt;why&lt;/em&gt; developers struggle, every automation you create will have &lt;strong&gt;purpose&lt;/strong&gt; — not just pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&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%2Frl3t8h2wm0am5fejmne6.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%2Frl3t8h2wm0am5fejmne6.png" alt="learn devops and cloud" width="800" height="457"&gt;&lt;/a&gt;&lt;br&gt;
DevOps isn’t just a stack of tools; it’s a mindset built on understanding how software works, from code to customer.&lt;br&gt;
You don’t have to become a full-stack developer — but you should at least experience the stack once.&lt;/p&gt;

&lt;p&gt;Otherwise, you’ll always stay a &lt;strong&gt;tooler&lt;/strong&gt;, not an &lt;strong&gt;engineer&lt;/strong&gt; — busy setting up systems that solve no real problems.&lt;/p&gt;

&lt;p&gt;And that’s not DevOps. That’s just decoration.&lt;/p&gt;




&lt;h2&gt;
  
  
  📬 Contact
&lt;/h2&gt;

&lt;p&gt;If you’d like to connect, collaborate, or discuss DevOps, feel free to reach out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://hasan-ashab.vercel.app/" rel="noopener noreferrer"&gt;hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://linkedin.com/in/hasan-ashab/" rel="noopener noreferrer"&gt;linkedin.com/in/hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>cloud</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Creating a CLI Tool with Node.js</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Mon, 06 Oct 2025 01:07:03 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/creating-a-cli-tool-with-nodejs-5b1k</link>
      <guid>https://forem.com/hasan_ashab/creating-a-cli-tool-with-nodejs-5b1k</guid>
      <description>&lt;p&gt;Command-line tools might not have fancy interfaces, but they’re some of the most powerful and time-saving tools a developer can build. From simple automation scripts to complex developer utilities, CLIs (Command Line Interfaces) are at the heart of how modern developers work.&lt;/p&gt;

&lt;p&gt;In this post, we’ll walk through &lt;strong&gt;how to build a robust CLI with Node.js&lt;/strong&gt;, and we’ll take a close look at &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/samer-artisan" rel="noopener noreferrer"&gt;SamerArtisan&lt;/a&gt;&lt;/strong&gt; — a Laravel Artisan–inspired framework that brings structure, elegance, and TypeScript support to CLI development.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Build a CLI Tool?
&lt;/h2&gt;

&lt;p&gt;Think about how often you type &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;npm&lt;/code&gt;, or &lt;code&gt;docker&lt;/code&gt; in your terminal. CLI tools let us control complex systems quickly, script them easily, and automate repetitive tasks.&lt;/p&gt;

&lt;p&gt;Here’s why developers love CLIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚙️ &lt;strong&gt;Automation-friendly&lt;/strong&gt; – Easily scriptable and perfect for CI/CD.&lt;/li&gt;
&lt;li&gt;💡 &lt;strong&gt;Lightweight&lt;/strong&gt; – No need for a GUI, ideal for servers and terminals.&lt;/li&gt;
&lt;li&gt;🧩 &lt;strong&gt;Composable&lt;/strong&gt; – Can be chained with other tools using pipes and redirects.&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;Fast&lt;/strong&gt; – Interacts directly with the system, no browser overhead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: CLIs make developers faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Node.js CLI Ecosystem
&lt;/h2&gt;

&lt;p&gt;Node.js has become a favorite for building CLI tools, thanks to its simplicity and cross-platform nature. The most common library for this is &lt;strong&gt;Commander.js&lt;/strong&gt;, which handles command parsing, flags, and options well.&lt;/p&gt;

&lt;p&gt;But as your tool grows, Commander.js can start to feel repetitive — lots of boilerplate, little structure. That’s where &lt;strong&gt;SamerArtisan&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;Inspired by Laravel’s “Artisan” console, &lt;strong&gt;SamerArtisan&lt;/strong&gt; gives Node.js CLIs a framework-like experience — complete with classes, structure, interactivity, and first-class TypeScript support.&lt;/p&gt;




&lt;h2&gt;
  
  
  Commander.js vs SamerArtisan — Which Should You Use?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🧱 Commander.js: The Classic Choice
&lt;/h3&gt;

&lt;p&gt;Commander.js is perfect when you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;simple CLI&lt;/strong&gt; with a few commands.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;quick prototype&lt;/strong&gt; or internal tool.&lt;/li&gt;
&lt;li&gt;Minimal dependencies and maximum control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&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;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Command&lt;/span&gt; &lt;span class="p"&gt;}&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;commander&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;program&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-cli&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;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CLI for simple greetings&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;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.8.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;program&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;greet&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;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Greet someone&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;span class="nf"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;person to greet&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;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-u, --uppercase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uppercase the greeting&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;span class="nf"&gt;action&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;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Hello &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;!`&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="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uppercase&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Straightforward — but not very scalable.&lt;/p&gt;




&lt;h3&gt;
  
  
  💎 SamerArtisan: Structure with Style
&lt;/h3&gt;

&lt;p&gt;SamerArtisan shines when your CLI needs more than a few commands. It gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organized &lt;strong&gt;command classes&lt;/strong&gt; and inheritance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive prompts&lt;/strong&gt;, progress bars, and tables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript support&lt;/strong&gt; for safety and autocompletion.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Laravel-like experience&lt;/strong&gt; for developers.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;scalable foundation&lt;/strong&gt; for large tools.&lt;/li&gt;
&lt;/ul&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%2Fytep816iuwpd0vne10pl.jpg" 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%2Fytep816iuwpd0vne10pl.jpg" alt="Nodejs ClI" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SamerArtisan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Command&lt;/span&gt; &lt;span class="p"&gt;}&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="s2"&gt;samer-artisan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GreetCommand&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;greet {name} {--uppercase}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Greet someone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argument&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uppercase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uppercase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Hello &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;!`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uppercase&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;greeting&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;SamerArtisan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GreetCommand&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;SamerArtisan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cleaner, more readable, and ready to scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started with SamerArtisan
&lt;/h2&gt;

&lt;p&gt;Let’s build a simple project management CLI that initializes projects and manages tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;samer-artisan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Set Up Your CLI Entry File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cli.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SamerArtisan&lt;/span&gt; &lt;span class="p"&gt;}&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="s2"&gt;samer-artisan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;SamerArtisan&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ProjectManager&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;span class="nf"&gt;root&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;commands&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;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Create a Command
&lt;/h3&gt;

&lt;p&gt;You can generate a command class automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node cli.js make:command InitProject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a clean template:&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="c1"&gt;// commands/InitProject.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Command&lt;/span&gt; &lt;span class="p"&gt;}&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="s2"&gt;samer-artisan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InitProject&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="cm"&gt;/**
  * The name and signature of the console command.
  */&lt;/span&gt;
  &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`project:init 
        { a: First arg }
        { b }
        { c*: Third arg }
        { --dog : First opt }`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="cm"&gt;/**
  * The console command description.
  */&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;command description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="cm"&gt;/**
  * Execute the command
  */&lt;/span&gt;
  &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;InitProject command works!&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;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;InitProject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Features That Make SamerArtisan Special
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Interactive Prompts
&lt;/h3&gt;

&lt;p&gt;SamerArtisan makes user input effortless:&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;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Project name?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter your API key:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;framework&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Choose framework:&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;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;React&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Beautiful Output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ All systems operational&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⚠️  Some warnings detected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❌ Critical issues found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Uptime&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;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ Running&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;99.9%&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;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;API&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⚠️ Slow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;98.1%&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;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❌ Down&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0%&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;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. TypeScript Support
&lt;/h3&gt;

&lt;p&gt;Type-safe arguments and options right out of the box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TypedCommand&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;run {name} {--force}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argument&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;force&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;force&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Easy Organization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;SamerArtisan&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;commands/project&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;commands/database&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;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Best Practices for CLI Design
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ Make It Scriptable &lt;em&gt;and&lt;/em&gt; Interactive
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yes&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;confirm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Proceed with deployment?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ⚠️ Handle Errors Gracefully
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🧭 Document Everything
&lt;/h3&gt;

&lt;p&gt;SamerArtisan can generate help text directly from your command signatures — no extra effort needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Example: Database Migrations
&lt;/h2&gt;

&lt;p&gt;Here’s what a realistic command might look like:&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;class&lt;/span&gt; &lt;span class="nc"&gt;MigrateCommand&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`migrate 
    {--rollback : Rollback last migration}
    {--steps=1 : Steps to rollback}
    {--seed : Run seeders after migration}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Run database migrations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rollback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;steps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;seed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runRollback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runMigrations&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runSeeders&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;Readable, structured, and clean — exactly how a CLI should feel.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Choose SamerArtisan
&lt;/h2&gt;

&lt;p&gt;Use &lt;strong&gt;SamerArtisan&lt;/strong&gt; if you’re building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;complex CLI&lt;/strong&gt; with multiple commands or subcommands.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;team tool&lt;/strong&gt; that benefits from consistent structure.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;TypeScript project&lt;/strong&gt; where safety matters.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;long-term project&lt;/strong&gt; that will evolve over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stick with &lt;strong&gt;Commander.js&lt;/strong&gt; for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quick scripts&lt;/strong&gt; or small utilities.&lt;/li&gt;
&lt;li&gt;Projects with &lt;strong&gt;strict dependency policies&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-off tools&lt;/strong&gt; where setup speed matters.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Building a CLI tool is about more than just parsing commands — it’s about creating a smooth experience for both humans and scripts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Commander.js&lt;/strong&gt; is ideal for small tools and prototypes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SamerArtisan&lt;/strong&gt; shines in structured, scalable, developer-focused projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end of the day, the “best” CLI framework is the one that fits your use case. Start simple, grow smart, and build tools that make developers’ lives easier — because the best CLI is the one people &lt;em&gt;actually use&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>cli</category>
      <category>laravel</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Why You Should Use AWS Backup Instead of Custom Lambda Solutions</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Sun, 05 Oct 2025 05:42:50 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/why-you-should-use-aws-backup-instead-of-custom-lambda-solutions-3n02</link>
      <guid>https://forem.com/hasan_ashab/why-you-should-use-aws-backup-instead-of-custom-lambda-solutions-3n02</guid>
      <description>&lt;p&gt;&lt;em&gt;Stop reinventing the wheel.&lt;/em&gt;&lt;br&gt;
Here's why &lt;strong&gt;AWS Backup&lt;/strong&gt; is the superior choice over custom Lambda functions for EC2 backups — and how to migrate your existing solution in minutes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Jump To:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Problem with Custom Solutions&lt;/li&gt;
&lt;li&gt;Why AWS Backup Wins&lt;/li&gt;
&lt;li&gt;Migration Guide&lt;/li&gt;
&lt;li&gt;The New Architecture&lt;/li&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;li&gt;Benefits Comparison&lt;/li&gt;
&lt;li&gt;Cost Analysis&lt;/li&gt;
&lt;li&gt;Contact&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Problem with Custom Solutions
&lt;/h2&gt;

&lt;p&gt;I used to think building custom backup solutions was the way to go. Lambda functions, EventBridge rules, custom IAM policies, S3 logging buckets — it felt like I was in control of every aspect of my backup strategy.&lt;/p&gt;

&lt;p&gt;But here's what I learned the hard way: &lt;strong&gt;you're reinventing a wheel that AWS has already perfected.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After migrating from a custom Lambda-based backup solution to AWS Backup, I realized I had been:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing hundreds of lines of code that AWS already maintains&lt;/li&gt;
&lt;li&gt;Managing complex IAM policies when service-linked roles exist&lt;/li&gt;
&lt;li&gt;Building monitoring and alerting that's built into AWS Backup&lt;/li&gt;
&lt;li&gt;Debugging custom logic instead of focusing on business value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today, I'll show you why AWS Backup is superior and how to migrate your existing custom solution in under 30 minutes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why AWS Backup Wins
&lt;/h2&gt;

&lt;p&gt;Let me break down why AWS Backup is objectively better than custom Lambda solutions:&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Zero Code Maintenance&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custom Lambda&lt;/strong&gt;: 200+ lines of Python code to maintain, debug, and update&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Backup&lt;/strong&gt;: Zero lines of code. It's a managed service.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Enterprise Features Out of the Box&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cross-region replication&lt;/strong&gt;: Built-in, no custom logic needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Point-in-time recovery&lt;/strong&gt;: Supported natively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance reporting&lt;/strong&gt;: Built into the console&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup verification&lt;/strong&gt;: Automatic integrity checks&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Better Security&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Service-linked roles&lt;/strong&gt;: AWS manages permissions automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption&lt;/strong&gt;: KMS integration with no custom key management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit trails&lt;/strong&gt;: CloudTrail integration by default&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Superior Monitoring&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Built-in dashboards&lt;/strong&gt;: No custom CloudWatch setup needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native alerting&lt;/strong&gt;: SNS integration without custom Lambda triggers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job status tracking&lt;/strong&gt;: Real-time backup job monitoring&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Migration Guide
&lt;/h2&gt;

&lt;p&gt;If you're currently using a custom Lambda solution, here's how to migrate to AWS Backup:&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Before: Custom Lambda Architecture&lt;/strong&gt;
&lt;/h3&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%2Fg946g535wfob6vkjn6kf.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%2Fg946g535wfob6vkjn6kf.png" alt="Lambda" width="676" height="380"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EventBridge → Lambda Function → EC2 API calls → S3 Logging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;200+ lines of Python code&lt;/li&gt;
&lt;li&gt;Custom IAM policies&lt;/li&gt;
&lt;li&gt;Manual error handling&lt;/li&gt;
&lt;li&gt;Custom monitoring setup&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;After: AWS Backup Architecture&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS Backup Plan → Backup Vault → Encrypted Recovery Points
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Zero lines of code&lt;/li&gt;
&lt;li&gt;Service-linked roles&lt;/li&gt;
&lt;li&gt;Built-in error handling&lt;/li&gt;
&lt;li&gt;Native monitoring&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;AWS Backup simplifies everything:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Backup Plan&lt;/strong&gt;: Defines schedule and retention rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup Vault&lt;/strong&gt;: Secure, encrypted storage for recovery points&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup Selection&lt;/strong&gt;: Tag-based resource targeting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Role&lt;/strong&gt;: AWS-managed permissions&lt;/li&gt;
&lt;/ol&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%2Fxaywb8tqt6yrytvkxtt8.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%2Fxaywb8tqt6yrytvkxtt8.png" alt="AWS Backup" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. Four components instead of a dozen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Here's the complete Terraform configuration for AWS Backup. Compare this to the 300+ lines needed for a custom Lambda solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# IAM role for AWS Backup&lt;/span&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;"backup_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;"aws-backup-service-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;"backup.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;span class="c1"&gt;# Attach AWS managed policies&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"backup_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backup_role&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="s2"&gt;"arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Backup vault with encryption&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_backup_vault"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"primary-backup-vault"&lt;/span&gt;
  &lt;span class="nx"&gt;kms_key_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_kms_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Backup plan with lifecycle rules&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_backup_plan"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"daily-backup-plan"&lt;/span&gt;

  &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;rule_name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"daily_backup_rule"&lt;/span&gt;
    &lt;span class="nx"&gt;target_vault_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_backup_vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="nx"&gt;schedule&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cron(0 2 * * ? *)"&lt;/span&gt;  &lt;span class="c1"&gt;# 2 AM daily&lt;/span&gt;

    &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;delete_after&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;  &lt;span class="c1"&gt;# 7 day retention&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="c1"&gt;# Backup selection - which resources to backup&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_backup_selection"&lt;/span&gt; &lt;span class="s2"&gt;"ec2_backup"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;iam_role_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backup_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&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;"ec2-backup-selection"&lt;/span&gt;
  &lt;span class="nx"&gt;plan_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_backup_plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;selection_tag&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"STRINGEQUALS"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"BackupPlan"&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;"daily-backup"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;strong&gt;50 lines of Terraform vs 300+ lines for a custom solution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check the source code &lt;a href="https://github.com/hasanashab/aws-backup-ec2-terraform" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits Comparison
&lt;/h2&gt;

&lt;p&gt;Let me show you the concrete benefits of switching:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Custom Lambda&lt;/th&gt;
&lt;th&gt;AWS Backup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code Maintenance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;200+ lines of Python&lt;/td&gt;
&lt;td&gt;0 lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IAM Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom policies, 50+ lines&lt;/td&gt;
&lt;td&gt;Service-linked roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual try/catch blocks&lt;/td&gt;
&lt;td&gt;Built-in retry logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monitoring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom CloudWatch setup&lt;/td&gt;
&lt;td&gt;Native dashboards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cross-region&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complex custom logic&lt;/td&gt;
&lt;td&gt;One checkbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual reporting&lt;/td&gt;
&lt;td&gt;Built-in compliance reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Point-in-time Recovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Native support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backup Verification&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual testing&lt;/td&gt;
&lt;td&gt;Automatic integrity checks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Cost Analysis
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Custom Lambda Solution Monthly Costs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda execution: ~$2-5&lt;/li&gt;
&lt;li&gt;EventBridge: ~$1&lt;/li&gt;
&lt;li&gt;S3 storage for logs: ~$1-3&lt;/li&gt;
&lt;li&gt;CloudWatch logs: ~$1-2&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total: $5-11/month&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AWS Backup Solution Monthly Costs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backup storage: Same as EBS snapshots&lt;/li&gt;
&lt;li&gt;AWS Backup service: $0.50 per backup job&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total: ~$2-4/month&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AWS Backup is actually cheaper&lt;/strong&gt; because it eliminates Lambda execution costs and reduces operational overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration Steps
&lt;/h2&gt;

&lt;p&gt;Ready to migrate? Here's your step-by-step guide:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Update Your Tags
&lt;/h3&gt;

&lt;p&gt;Change your EC2 instance tags from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Backup = "true"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Deploy AWS Backup Resources
&lt;/h3&gt;

&lt;p&gt;Use the Terraform configuration above to create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backup vault with KMS encryption&lt;/li&gt;
&lt;li&gt;Backup plan with your schedule&lt;/li&gt;
&lt;li&gt;Backup selection targeting your tagged resources&lt;/li&gt;
&lt;li&gt;Service-linked IAM role&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Test the Migration
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Deploy the AWS Backup resources&lt;/li&gt;
&lt;li&gt;Wait for the first scheduled backup&lt;/li&gt;
&lt;li&gt;Verify recovery points in the AWS Backup console&lt;/li&gt;
&lt;li&gt;Test a restore operation&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Real-World Results
&lt;/h2&gt;

&lt;p&gt;After migrating to AWS Backup, here's what I experienced:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operational Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Zero code maintenance (was spending 2-3 hours/month debugging Lambda issues)&lt;/li&gt;
&lt;li&gt;✅ Built-in monitoring eliminated custom CloudWatch setup&lt;/li&gt;
&lt;li&gt;✅ Automatic retry logic reduced backup failures by 90%&lt;/li&gt;
&lt;li&gt;✅ Cross-region replication setup took 5 minutes vs. days of custom development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost Savings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ 40% reduction in monthly backup costs&lt;/li&gt;
&lt;li&gt;✅ Eliminated Lambda execution charges&lt;/li&gt;
&lt;li&gt;✅ Reduced CloudWatch log storage costs&lt;/li&gt;
&lt;li&gt;✅ No more S3 storage for custom backup logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Security Improvements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Service-linked roles eliminated custom IAM policy maintenance&lt;/li&gt;
&lt;li&gt;✅ Built-in encryption with customer-managed KMS keys&lt;/li&gt;
&lt;li&gt;✅ Automatic compliance reporting for audits&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Contact
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Have questions about this setup? Found a bug in the code? Drop a comment below&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;or reach out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://hasan-ashab.vercel.app/" rel="noopener noreferrer"&gt;hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://linkedin.com/in/hasan-ashab/" rel="noopener noreferrer"&gt;linkedin.com/in/hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>backup</category>
      <category>terraform</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Never Miss a Downtime: AWS Website Uptime Monitor with Terraform</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Sat, 04 Oct 2025 04:18:13 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/never-miss-a-downtime-aws-website-uptime-monitor-with-terraform-5f2o</link>
      <guid>https://forem.com/hasan_ashab/never-miss-a-downtime-aws-website-uptime-monitor-with-terraform-5f2o</guid>
      <description>&lt;p&gt;&lt;em&gt;Stop wondering if your website is down.&lt;/em&gt;&lt;br&gt;
Here's how to build a &lt;strong&gt;complete uptime monitoring system&lt;/strong&gt; with real-time dashboard using AWS &lt;em&gt;Lambda&lt;/em&gt;, &lt;em&gt;DynamoDB&lt;/em&gt;, &lt;em&gt;React&lt;/em&gt;, and &lt;em&gt;Terraform&lt;/em&gt; — get instant alerts when your site goes down.&lt;/p&gt;
&lt;h2&gt;
  
  
  Jump To:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Picture This&lt;/li&gt;
&lt;li&gt;Why This Solution Works&lt;/li&gt;
&lt;li&gt;The Architecture&lt;/li&gt;
&lt;li&gt;Requirements&lt;/li&gt;
&lt;li&gt;Step 1: Project Structure&lt;/li&gt;
&lt;li&gt;Step 2: Monitoring Lambda&lt;/li&gt;
&lt;li&gt;Step 3: Dashboard API&lt;/li&gt;
&lt;li&gt;Step 4: React Dashboard&lt;/li&gt;
&lt;li&gt;Step 5: Infrastructure&lt;/li&gt;
&lt;li&gt;Step 6: Deployment&lt;/li&gt;
&lt;li&gt;Step 7: Testing&lt;/li&gt;
&lt;li&gt;Monitoring and Alerts&lt;/li&gt;
&lt;li&gt;Troubleshooting&lt;/li&gt;
&lt;li&gt;Contact&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Picture this:
&lt;/h2&gt;

&lt;p&gt;Your website goes down at 2 AM. Your customers can't access your service, but you're peacefully sleeping, completely unaware. By morning, you've lost customers, revenue, and trust. Sound familiar?&lt;/p&gt;

&lt;p&gt;Today, I'll show you how to build a comprehensive website monitoring system that never sleeps. By the end of this guide, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time uptime monitoring with customizable checks&lt;/li&gt;
&lt;li&gt;Beautiful dashboard showing response times and availability&lt;/li&gt;
&lt;li&gt;Instant email alerts when your site goes down&lt;/li&gt;
&lt;li&gt;Historical data and monthly uptime reports&lt;/li&gt;
&lt;li&gt;All running serverlessly for pennies per month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part? It takes about 30 minutes to set up and monitors your site 24/7.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Solution Works
&lt;/h2&gt;

&lt;p&gt;Before diving into the code, let's understand why this serverless approach beats traditional monitoring services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost-Effective&lt;/strong&gt;: Monitor multiple websites for under $5/month. No expensive SaaS subscriptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customizable&lt;/strong&gt;: Full control over what gets monitored and how alerts are sent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time&lt;/strong&gt;: Dashboard updates every minute with live data and beautiful charts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable&lt;/strong&gt;: Monitor 1 website or 100 - the system scales automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliable&lt;/strong&gt;: Built on AWS managed services with 99.99% uptime SLA.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Architecture (Simple Yet Powerful)
&lt;/h2&gt;

&lt;p&gt;Our monitoring system has six main components working together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge&lt;/strong&gt;: Triggers uptime checks every 5 minutes (or your preferred interval)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring Lambda&lt;/strong&gt;: Performs HTTP checks and validates responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt;: Stores all monitoring data and metrics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Lambda&lt;/strong&gt;: Serves dashboard data through REST endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Dashboard&lt;/strong&gt;: Beautiful real-time UI with charts and metrics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SNS&lt;/strong&gt;: Sends email alerts for downtime events&lt;/li&gt;
&lt;/ol&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%2F7upoguhb2btropdet5ai.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%2F7upoguhb2btropdet5ai.png" alt="Website Uptime Monitor Architecture" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EventBridge (Every 5min) → Monitor Lambda → Check Website → Store Results → DynamoDB
                                                                              ↓
React Dashboard ← API Lambda ← DynamoDB ← SNS Alerts (if check fails)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, simple, and bulletproof.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Need
&lt;/h2&gt;

&lt;p&gt;Before we start, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS CLI configured with appropriate permissions&lt;/li&gt;
&lt;li&gt;Terraform installed (version 1.0 or later)&lt;/li&gt;
&lt;li&gt;Node.js 22+ and npm&lt;/li&gt;
&lt;li&gt;Basic familiarity with React and AWS services&lt;/li&gt;
&lt;li&gt;About 30 minutes of your time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't worry if you're new to some of these - I'll guide you through everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Project Structure
&lt;/h2&gt;

&lt;p&gt;Let's organize our project for maximum clarity and reusability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;website-uptime-monitor/
├── infra/                          # Terraform infrastructure
│   ├── main.tf                     # Main configuration
│   ├── variables.tf                # Input variables
│   ├── envs/
│   │   └── prod.tfvars            # Production config
│   └── modules/
│       ├── uptime_monitor/         # Monitoring infrastructure
│       └── dashboard/              # Dashboard infrastructure
├── dashboard/
│   ├── backend/                    # Lambda API functions
│   │   └── functions/
│   │       ├── get.mjs            # Metrics endpoint
│   │       ├── list.mjs           # All data endpoint
│   │       └── create.mjs         # Recent pings endpoint
│   └── frontend/                   # React dashboard
│       ├── src/
│       │   ├── App.js             # Main dashboard component
│       │   └── index.css          # Styling
│       └── package.json
└── .github/workflows/              # CI/CD pipelines
    ├── backend-cicd.yaml
    └── frontend-cicd.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can either clone the complete project &lt;a href="https://github.com/HasanAshab/aws-website-uptime-monitor" rel="noopener noreferrer"&gt;from GitHub&lt;/a&gt; or create the structure manually:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Clone the complete project (recommended):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/HasanAshab/aws-website-uptime-monitor.git
&lt;span class="nb"&gt;cd &lt;/span&gt;aws-website-uptime-monitor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 2: Create the structure manually:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;website-uptime-monitor
&lt;span class="nb"&gt;cd &lt;/span&gt;website-uptime-monitor
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; infra/&lt;span class="o"&gt;{&lt;/span&gt;modules/&lt;span class="o"&gt;{&lt;/span&gt;uptime_monitor,dashboard&lt;span class="o"&gt;}&lt;/span&gt;,envs&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; dashboard/&lt;span class="o"&gt;{&lt;/span&gt;backend/functions,frontend/src&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .github/workflows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: The Monitoring Lambda (The Watchdog)
&lt;/h2&gt;

&lt;p&gt;The monitoring Lambda is the heart of our system. It performs HTTP checks, validates responses, and stores results.&lt;/p&gt;

&lt;p&gt;Here's what our monitoring function does:&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Fetch the target website URL from environment&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. Perform HTTP request with timeout&lt;/span&gt;
  &lt;span class="c1"&gt;// 3. Validate status code and response body&lt;/span&gt;
  &lt;span class="c1"&gt;// 4. Measure response time&lt;/span&gt;
  &lt;span class="c1"&gt;// 5. Store results in DynamoDB&lt;/span&gt;
  &lt;span class="c1"&gt;// 6. Send SNS alert if any check fails&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function checks for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Status Code&lt;/strong&gt;: Ensures it matches expected value (usually 200)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response Body&lt;/strong&gt;: Validates that specific text is present&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response Time&lt;/strong&gt;: Measures and stores response time in milliseconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection Issues&lt;/strong&gt;: Catches timeouts and network errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Full monitoring Lambda code available in the repository&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Dashboard API (Serving the Data)
&lt;/h2&gt;

&lt;p&gt;Our dashboard needs three API endpoints to display comprehensive monitoring data:&lt;/p&gt;

&lt;h3&gt;
  
  
  Metrics Endpoint (&lt;code&gt;get.mjs&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GET /metrics - Returns monthly aggregated data&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;uptime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;99.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;// Percentage uptime this month&lt;/span&gt;
    &lt;span class="na"&gt;invalidStatusCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Number of failed status checks&lt;/span&gt;
    &lt;span class="na"&gt;avgResponseTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;245&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// Average response time in ms&lt;/span&gt;
    &lt;span class="na"&gt;totalChecks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1440&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// Total checks performed&lt;/span&gt;
    &lt;span class="na"&gt;successfulChecks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1438&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// Successful checks&lt;/span&gt;
    &lt;span class="na"&gt;failedChecks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;// Failed checks&lt;/span&gt;
    &lt;span class="na"&gt;lastUpdated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;corsHeaders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metrics&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;h3&gt;
  
  
  Recent Pings Endpoint (&lt;code&gt;create.mjs&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GET /recent-pings - Returns last 30 minutes of data&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Filter DynamoDB data for last 30 minutes&lt;/span&gt;
  &lt;span class="c1"&gt;// Return detailed ping results with timestamps&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  All Data Endpoint (&lt;code&gt;list.mjs&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GET /uptime-data - Returns all monitoring data&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Scan entire DynamoDB table&lt;/span&gt;
  &lt;span class="c1"&gt;// Return all historical data&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These endpoints power our dashboard with real-time data and historical trends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: React Dashboard (Beautiful Real-Time UI)
&lt;/h2&gt;

&lt;p&gt;Our React dashboard provides a beautiful, responsive interface for monitoring your website:&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%2Fq7sbg4h7hy6r504f2yf0.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%2Fq7sbg4h7hy6r504f2yf0.png" alt="AWS S3, Cloudfront" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monthly Metrics Cards&lt;/strong&gt;: Uptime percentage, failed checks, average response time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Chart&lt;/strong&gt;: Response time visualization for the last 30 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recent Pings Table&lt;/strong&gt;: Detailed list of recent monitoring attempts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-Refresh&lt;/strong&gt;: Updates every 60 seconds automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive Design&lt;/strong&gt;: Works perfectly on desktop, tablet, and mobile&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Main Dashboard Component (&lt;code&gt;App.js&lt;/code&gt;):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMetrics&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;recentPings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setRecentPings&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;chartData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setChartData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Auto-refresh every 60 seconds&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fetch from our API endpoints&lt;/span&gt;
    &lt;span class="c1"&gt;// Update state with fresh data&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MetricsCards&lt;/span&gt; &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ResponseTimeChart&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;chartData&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RecentPingsTable&lt;/span&gt; &lt;span class="nx"&gt;pings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;recentPings&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;The dashboard uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recharts&lt;/strong&gt; for beautiful, responsive charts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framer Motion&lt;/strong&gt; for smooth animations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Icons&lt;/strong&gt; for consistent iconography&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom CSS&lt;/strong&gt; with modern gradients and glassmorphism effects&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: Terraform Infrastructure
&lt;/h2&gt;

&lt;p&gt;Now let's tie everything together with Terraform. Our infrastructure is modular and reusable:&lt;/p&gt;

&lt;h3&gt;
  
  
  Main Configuration (&lt;code&gt;infra/main.tf&lt;/code&gt;):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# DynamoDB table for storing monitoring data
&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dynamodb_table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;source&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;terraform-aws-modules/dynamodb-table/aws&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5.1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

  &lt;span class="n"&gt;name&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${local.project_name}-dynamodb-${var.environment}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;hash_key&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;billing_mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db_billing_mode&lt;/span&gt;

  &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
      &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&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="c1"&gt;# Uptime monitoring system
&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uptime_monitor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./modules/uptime_monitor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

  &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;
  &lt;span class="n"&gt;name_prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project_name&lt;/span&gt;

  &lt;span class="n"&gt;website_url&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_website_url&lt;/span&gt;
  &lt;span class="n"&gt;ping_schedule&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uptime_ping_schedule&lt;/span&gt;
  &lt;span class="n"&gt;assertions&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uptime_assertions&lt;/span&gt;
  &lt;span class="n"&gt;subscriber_email&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uptime_alert_subscriber_email&lt;/span&gt;
  &lt;span class="n"&gt;dynamodb_table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dynamodb_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dynamodb_table_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Dashboard (API + Frontend)
&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dashboard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./modules/dashboard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

  &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;
  &lt;span class="n"&gt;name_prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project_name&lt;/span&gt;

  &lt;span class="n"&gt;backend_src_root&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${path.root}/../dashboard/backend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;dynamodb_table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dynamodb_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dynamodb_table_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration Variables (&lt;code&gt;infra/variables.tf&lt;/code&gt;):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;variable&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target_website_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;URL of the website to monitor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;type&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
  &lt;span class="n"&gt;default&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;variable&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uptime_ping_schedule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EventBridge cron expression for monitoring frequency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;type&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
  &lt;span class="n"&gt;default&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cron(*/5 * * * ? *)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Every 5 minutes
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;variable&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uptime_assertions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Monitoring assertions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;status_code&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="n"&gt;body_includes&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;max_response_time_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;variable&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uptime_alert_subscriber_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Email address for downtime alerts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;type&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment Configuration (&lt;code&gt;infra/envs/prod.tfvars&lt;/code&gt;):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;environment&lt;/span&gt;                   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;aws_region&lt;/span&gt;                   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-west-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;target_website_url&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-website.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;uptime_alert_subscriber_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alerts@your-domain.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;uptime_ping_schedule&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cron(*/5 * * * ? *)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;uptime_assertions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;status_code&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
  &lt;span class="n"&gt;body_includes&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your Site Title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;max_response_time_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Deployment
&lt;/h2&gt;

&lt;p&gt;Now let's deploy our complete monitoring system:&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Infrastructure:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;infra
terraform init
terraform workspace new prod
terraform plan &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;envs/prod.tfvars
terraform apply &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;envs/prod.tfvars
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy Frontend:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;dashboard/frontend
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build

&lt;span class="c"&gt;# Upload to S3 bucket (created by Terraform)&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;sync &lt;/span&gt;build/ s3://your-dashboard-bucket &lt;span class="nt"&gt;--delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform will create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ DynamoDB table for monitoring data&lt;/li&gt;
&lt;li&gt;✅ Lambda functions for monitoring and API&lt;/li&gt;
&lt;li&gt;✅ EventBridge rule for scheduled checks&lt;/li&gt;
&lt;li&gt;✅ SNS topic for email alerts&lt;/li&gt;
&lt;li&gt;✅ S3 bucket and CloudFront for dashboard hosting&lt;/li&gt;
&lt;li&gt;✅ API Gateway for dashboard endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 7: Test Your Monitoring System
&lt;/h2&gt;

&lt;p&gt;Don't wait for your site to go down to test the system! Here's how to verify everything works:&lt;/p&gt;

&lt;h3&gt;
  
  
  Trigger Manual Check:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--function-name&lt;/span&gt; your-monitor-function &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  response.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check Monitoring Logs:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to CloudWatch → Log groups&lt;/li&gt;
&lt;li&gt;Find &lt;code&gt;/aws/lambda/your-monitor-function&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check the latest log stream for monitoring results&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Verify Data Storage:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to DynamoDB → Tables → your-monitoring-table&lt;/li&gt;
&lt;li&gt;Click "Explore table items"&lt;/li&gt;
&lt;li&gt;Verify monitoring records are being created&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Test Dashboard:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Get your dashboard URL from Terraform output:
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Open the URL in your browser&lt;/li&gt;
&lt;li&gt;Verify you see:

&lt;ul&gt;
&lt;li&gt;Monthly metrics cards&lt;/li&gt;
&lt;li&gt;Response time chart&lt;/li&gt;
&lt;li&gt;Recent pings table&lt;/li&gt;
&lt;li&gt;Auto-refresh functionality&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Test Alerts:
&lt;/h3&gt;

&lt;p&gt;Temporarily change your website URL to a non-existent domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply &lt;span class="nt"&gt;-var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"target_website_url=https://non-existent-site.com"&lt;/span&gt; &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;envs/prod.tfvars
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should receive an email alert within 5 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring and Alerts
&lt;/h2&gt;

&lt;p&gt;Your monitoring system is now active! Here's how to keep it running smoothly:&lt;/p&gt;

&lt;h3&gt;
  
  
  Email Alert Configuration
&lt;/h3&gt;

&lt;p&gt;The system automatically sends alerts for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website Down&lt;/strong&gt;: HTTP errors, timeouts, connection failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow Response&lt;/strong&gt;: Response time exceeds your threshold&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content Changes&lt;/strong&gt;: Expected text not found in response body&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status Code Issues&lt;/strong&gt;: Unexpected HTTP status codes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dashboard Features
&lt;/h3&gt;

&lt;p&gt;Your dashboard provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Metrics&lt;/strong&gt;: Current month uptime percentage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response Time Trends&lt;/strong&gt;: Visual chart of performance over time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure Analysis&lt;/strong&gt;: Count and details of failed checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical Data&lt;/strong&gt;: Complete monitoring history&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cost Monitoring
&lt;/h3&gt;

&lt;p&gt;Monitor your AWS costs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check monthly costs for monitoring services&lt;/span&gt;
aws ce get-cost-and-usage &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--time-period&lt;/span&gt; &lt;span class="nv"&gt;Start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2025-01-01,End&lt;span class="o"&gt;=&lt;/span&gt;2025-02-01 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--granularity&lt;/span&gt; MONTHLY &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metrics&lt;/span&gt; BlendedCost &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-by&lt;/span&gt; &lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;DIMENSION,Key&lt;span class="o"&gt;=&lt;/span&gt;SERVICE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected monthly costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt;: $0.20 (for ~8,640 invocations/month)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt;: $1.25 (for ~250,000 read/write units)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront&lt;/strong&gt;: $0.50 (for dashboard hosting)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SNS&lt;/strong&gt;: $0.50 (for email alerts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total&lt;/strong&gt;: ~$2.45/month&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lambda Timeout Errors:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Increase timeout in Terraform configuration&lt;/li&gt;
&lt;li&gt;Check if target website is responding slowly&lt;/li&gt;
&lt;li&gt;Verify network connectivity from Lambda&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dashboard Not Loading:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check CloudFront distribution status&lt;/li&gt;
&lt;li&gt;Verify S3 bucket has correct files&lt;/li&gt;
&lt;li&gt;Check API Gateway endpoints are working&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  No Email Alerts:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Confirm SNS subscription in AWS console&lt;/li&gt;
&lt;li&gt;Check spam folder for confirmation email&lt;/li&gt;
&lt;li&gt;Verify email address in Terraform variables&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Missing Data in Dashboard:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check DynamoDB table has monitoring records&lt;/li&gt;
&lt;li&gt;Verify Lambda function is being triggered by EventBridge&lt;/li&gt;
&lt;li&gt;Check API Lambda functions have correct DynamoDB permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  High Response Times:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check if your website is actually slow&lt;/li&gt;
&lt;li&gt;Verify Lambda is in same region as your website&lt;/li&gt;
&lt;li&gt;Consider adjusting response time thresholds&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Advanced Features
&lt;/h2&gt;

&lt;p&gt;Once your basic monitoring is working, consider these enhancements:&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Website Monitoring:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Monitor multiple websites
&lt;/span&gt;&lt;span class="n"&gt;variable&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;websites&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main-site&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-main-site.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
      &lt;span class="n"&gt;expected_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api-endpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.your-site.com/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
      &lt;span class="n"&gt;expected_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK&lt;/span&gt;&lt;span class="sh"&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;h3&gt;
  
  
  Slack Integration:
&lt;/h3&gt;

&lt;p&gt;Replace SNS email with Slack webhooks for team notifications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Metrics:
&lt;/h3&gt;

&lt;p&gt;Add CloudWatch custom metrics for advanced monitoring and alerting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Region Monitoring:
&lt;/h3&gt;

&lt;p&gt;Deploy monitoring Lambda in multiple regions for global perspective.&lt;/p&gt;

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

&lt;p&gt;You now have a production-ready website monitoring system! Here are some ideas for further enhancement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mobile App&lt;/strong&gt;: Build a React Native app for monitoring on-the-go&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Analytics&lt;/strong&gt;: Add trend analysis and predictive alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration APIs&lt;/strong&gt;: Connect with PagerDuty, Slack, or Microsoft Teams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SLA Reporting&lt;/strong&gt;: Generate monthly uptime reports for stakeholders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Budgets&lt;/strong&gt;: Set and track performance goals over time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Building a comprehensive website monitoring system doesn't have to break the bank or require complex infrastructure. With AWS serverless services, React, and Terraform, you can create a robust monitoring solution that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monitors 24/7&lt;/strong&gt;: Never miss downtime again&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Costs Under $5/month&lt;/strong&gt;: Fraction of commercial monitoring services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provides Beautiful Dashboard&lt;/strong&gt;: Real-time insights with professional UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scales Automatically&lt;/strong&gt;: Handle traffic spikes without configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sends Instant Alerts&lt;/strong&gt;: Know about issues before your customers do&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up once, monitor forever&lt;/li&gt;
&lt;li&gt;Complete visibility into website performance&lt;/li&gt;
&lt;li&gt;Professional-grade monitoring at startup prices&lt;/li&gt;
&lt;li&gt;Full control over monitoring logic and alerts&lt;/li&gt;
&lt;li&gt;Beautiful dashboard your team will actually use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Remember&lt;/strong&gt;: You can't fix what you can't see.&lt;/p&gt;

&lt;p&gt;Your website is your business's front door. Make sure you know when it's closed, how fast it opens, and whether visitors can find what they're looking for.&lt;/p&gt;

&lt;p&gt;Now go forth and monitor with confidence!&lt;/p&gt;




&lt;h2&gt;
  
  
  Contact
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Questions about this monitoring setup? Found an issue with the implementation? Want to share your monitoring success story?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Reach out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://hasan-ashab.vercel.app/" rel="noopener noreferrer"&gt;hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://linkedin.com/in/hasan-ashab/" rel="noopener noreferrer"&gt;linkedin.com/in/hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Happy monitoring! 🚀&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>lambda</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Automate EC2 Backups on AWS with Lambda, EventBridge, and Terraform</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Thu, 02 Oct 2025 00:32:07 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/automate-ec2-backups-on-aws-with-lambda-eventbridge-and-terraform-k4n</link>
      <guid>https://forem.com/hasan_ashab/automate-ec2-backups-on-aws-with-lambda-eventbridge-and-terraform-k4n</guid>
      <description>&lt;p&gt;&lt;em&gt;Stop losing sleep over manual backups.&lt;/em&gt;&lt;br&gt;
Here is how to set up &lt;strong&gt;fully automated&lt;/strong&gt;, &lt;strong&gt;cost-effective&lt;/strong&gt; EC2 backups using AWS &lt;em&gt;Lambda&lt;/em&gt;, &lt;em&gt;EventBridge&lt;/em&gt;, and &lt;em&gt;Terraform&lt;/em&gt; — no manual snapshots required.&lt;/p&gt;
&lt;h2&gt;
  
  
  Jump To:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Picture This&lt;/li&gt;
&lt;li&gt;Why This Approach&lt;/li&gt;
&lt;li&gt;The Architecture&lt;/li&gt;
&lt;li&gt;Requirements&lt;/li&gt;
&lt;li&gt;Step 1: Project Structure&lt;/li&gt;
&lt;li&gt;Step 2: Lambda Function&lt;/li&gt;
&lt;li&gt;Step 3: IAM Permissions&lt;/li&gt;
&lt;li&gt;Step 4: Terraform&lt;/li&gt;
&lt;li&gt;Step 5: Deployment&lt;/li&gt;
&lt;li&gt;Step 6: Testing&lt;/li&gt;
&lt;li&gt;Monitoring and Maintenance&lt;/li&gt;
&lt;li&gt;Troubleshooting&lt;/li&gt;
&lt;li&gt;Contact&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Picture this:
&lt;/h2&gt;

&lt;p&gt;It's 3 AM, your production server crashes, and you realize your last backup was... when exactly? We've all been there. Manual backups are like flossing – everyone knows they should do it, but somehow it never happens consistently.&lt;/p&gt;

&lt;p&gt;Today, I'll show you how to build a completely automated EC2 backup system. By the end of this guide, you'll have a system that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backs up your EC2 instances every night automatically&lt;/li&gt;
&lt;li&gt;Cleans up old snapshots to save costs&lt;/li&gt;
&lt;li&gt;Logs everything for audit trails&lt;/li&gt;
&lt;li&gt;Requires zero maintenance once deployed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part? It costs pennies to run and takes about 15 minutes to set up.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Approach Works
&lt;/h2&gt;

&lt;p&gt;Before we dive in, let's talk about why this serverless approach beats traditional backup solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost-Effective&lt;/strong&gt;: You only pay for what you use. No expensive backup software licenses or dedicated servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliable&lt;/strong&gt;: AWS manages the infrastructure. No more "backup server is down" emergencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable&lt;/strong&gt;: Works whether you have 5 instances or 500. The system scales automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditable&lt;/strong&gt;: Every backup operation is logged and tracked.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Architecture (Keep It Simple)
&lt;/h2&gt;

&lt;p&gt;Our backup system has four main components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge&lt;/strong&gt;: Acts like a cron job, triggering backups daily&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda Function&lt;/strong&gt;: The workhorse that creates and manages snapshots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2 Tags&lt;/strong&gt;: Simple way to mark which instances need backing up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Bucket&lt;/strong&gt;: Stores backup logs for auditing&lt;/li&gt;
&lt;/ol&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%2Fpkbsks25hsk6pnp2n20n.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%2Fpkbsks25hsk6pnp2n20n.png" alt="AWS EC2 Automated Backup" width="676" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's how they work together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EventBridge (Daily) → Lambda → Find Tagged EC2s → Create Snapshots → Log to S3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No complex orchestration, no fragile dependencies. Just simple, reliable automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Need
&lt;/h2&gt;

&lt;p&gt;Before we start, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS CLI configured&lt;/li&gt;
&lt;li&gt;Terraform installed (version 1.0 or later)&lt;/li&gt;
&lt;li&gt;Basic familiarity with AWS services&lt;/li&gt;
&lt;li&gt;About 15 minutes of your time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't worry if you're new to some of these tools – I'll walk you through everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Project Structure
&lt;/h2&gt;

&lt;p&gt;Let's start by creating our project structure. This keeps everything organized and makes the code reusable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ec2-backup-automation/
├── main.tf                    # Main Terraform configuration
├── variables.tf               # Input variables
├── terraform.tfvars          # Your specific values
├── lambda/
│   └── lambda_function.py    # Backup logic
└── templates/
    └── lambda_policy.json    # IAM permissions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can either &lt;a href="https://github.com/HasanAshab/aws-ec2-backup-lambda" rel="noopener noreferrer"&gt;clone&lt;/a&gt; the complete project or create the structure manually:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Clone the complete project (recommended):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/HasanAshab/aws-ec2-backup-lambda.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ec2-backup-automation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 2: Create the structure manually:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;ec2-backup-automation
&lt;span class="nb"&gt;cd &lt;/span&gt;ec2-backup-automation
&lt;span class="nb"&gt;mkdir &lt;/span&gt;lambda templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: The Lambda Function (The Heart of the System)
&lt;/h2&gt;

&lt;p&gt;The Lambda function is where the magic happens. It finds EC2 instances tagged for backup, creates snapshots, and cleans up old ones.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;lambda/lambda_function.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Main Lambda handler
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;create_backups&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;cleanup_old_snapshots&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;save_logs_to_s3&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Full code available &lt;a href="https://github.com/HasanAshab/aws-ec2-backup-lambda/blob/main/main.tf" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: IAM Permissions (Security Done Right)
&lt;/h2&gt;

&lt;p&gt;Our Lambda needs specific permissions to do its job. Create &lt;code&gt;templates/lambda_policy.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2008-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PolicyForEC2AutoBackupLambda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowLogging"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"logs:CreateLogGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"logs:PutLogEvents"&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="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:logs:*:*:*"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowEC2Actions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"ec2:DescribeInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeVolumes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ec2:CreateSnapshot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeSnapshots"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DeleteSnapshot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ec2:CreateTags"&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="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowPutS3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"s3:PutObject"&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="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${log_bucket_arn}/*"&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="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;This follows the principle of least privilege – the Lambda can only do what it needs to do, nothing more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Terraform Configuration
&lt;/h2&gt;

&lt;p&gt;Now let's tie everything together with Terraform. Create &lt;code&gt;variables.tf&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="c1"&gt;# ...&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"backup_schedule"&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;"EventBridge cron expression for backup schedule"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cron(0 2 * * ? *)"&lt;/span&gt;  &lt;span class="c1"&gt;# 2 AM UTC daily&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"retention_days"&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;"Number of days to retain snapshots"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create &lt;code&gt;main.tf&lt;/code&gt;. I'll break this down into logical sections so it's easier to understand:&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 Bucket for Logs
&lt;/h3&gt;

&lt;p&gt;Next, we'll create an S3 bucket to store our backup logs:&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;module&lt;/span&gt; &lt;span class="s2"&gt;"log_bucket"&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;"terraform-aws-modules/s3-bucket/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;"5.5.0"&lt;/span&gt;

  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.project_name}-log-${var.environment}"&lt;/span&gt;
  &lt;span class="nx"&gt;force_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a simple S3 bucket where our Lambda function will store detailed backup reports. The &lt;code&gt;force_destroy = true&lt;/code&gt; allows Terraform to delete the bucket even if it contains files (useful for testing).&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda Function
&lt;/h3&gt;

&lt;p&gt;Now for the heart of our system - the Lambda function:&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;module&lt;/span&gt; &lt;span class="s2"&gt;"lambda_function"&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;"terraform-aws-modules/lambda/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;"8.1.0"&lt;/span&gt;

  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.project_name}-backup-${var.environment}"&lt;/span&gt;
  &lt;span class="nx"&gt;source_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/lambda/lambda_function.py"&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda_function.lambda_handler"&lt;/span&gt;
  &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"python3.12"&lt;/span&gt;

  &lt;span class="nx"&gt;attach_policy_json&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;policy_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/templates/lambda_policy.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;log_bucket_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_bucket_arn&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;allowed_triggers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;eventbridge&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;"events"&lt;/span&gt;
      &lt;span class="nx"&gt;source_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventbridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventbridge_rule_arns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"crons"&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;create_current_version_allowed_triggers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;artifacts_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.root}/.terraform/lambda-builds/"&lt;/span&gt;

  &lt;span class="nx"&gt;environment_variables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;LOG_BUCKET&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_bucket_id&lt;/span&gt;
    &lt;span class="nx"&gt;RETENTION_DAYS&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;retention_days&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 creates our Lambda function with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source code&lt;/strong&gt;: Points to our Python file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM permissions&lt;/strong&gt;: Uses the policy template we created&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge trigger&lt;/strong&gt;: Allows EventBridge to invoke the function&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment variables&lt;/strong&gt;: Passes configuration to our Python code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  EventBridge Scheduler
&lt;/h3&gt;

&lt;p&gt;Finally, let's set up the scheduler that triggers our backups:&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;module&lt;/span&gt; &lt;span class="s2"&gt;"eventbridge"&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;"terraform-aws-modules/eventbridge/aws"&lt;/span&gt;

  &lt;span class="nx"&gt;create_bus&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;rules&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;crons&lt;/span&gt; &lt;span class="p"&gt;=&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;"Daily EC2 backup"&lt;/span&gt;
      &lt;span class="nx"&gt;schedule_expression&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;backup_schedule&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;crons&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;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ec2-backup-lambda"&lt;/span&gt;
        &lt;span class="nx"&gt;arn&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function_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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates an EventBridge rule that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runs on schedule&lt;/strong&gt;: Uses the cron expression from our variables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Targets our Lambda&lt;/strong&gt;: Automatically invokes our backup function&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uses default event bus&lt;/strong&gt;: No need for a custom event bus&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example EC2 Instances (Optional)
&lt;/h3&gt;

&lt;p&gt;For testing purposes, let's also create some sample EC2 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="c1"&gt;# Get default VPC and subnets for our test instances&lt;/span&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;"default"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnets"&lt;/span&gt; &lt;span class="s2"&gt;"default"&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;"vpc-id"&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="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;default&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;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create test instances - some will be backed up, some won't&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"ec2_instance"&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;"terraform-aws-modules/ec2-instance/aws"&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="s2"&gt;"1"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;   &lt;span class="c1"&gt;# This instance will be backed up&lt;/span&gt;
    &lt;span class="s2"&gt;"2"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;   &lt;span class="c1"&gt;# This instance will be backed up&lt;/span&gt;
    &lt;span class="s2"&gt;"3"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"false"&lt;/span&gt;  &lt;span class="c1"&gt;# This instance will NOT be backed up&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;"instance-${each.key}"&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;"t3.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_subnets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;monitoring&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="nx"&gt;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;Backup&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="c1"&gt;# This is the magic tag!&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 creates three test instances in your default VPC. Two are tagged for backup (&lt;code&gt;Backup=true&lt;/code&gt;) and one isn't (&lt;code&gt;Backup=false&lt;/code&gt;). This lets you see the system in action without affecting your existing instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Deployment
&lt;/h2&gt;

&lt;p&gt;Now deploy everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Initialize Terraform&lt;/span&gt;
terraform init

&lt;span class="c"&gt;# Review what will be created&lt;/span&gt;
terraform plan

&lt;span class="c"&gt;# Deploy the infrastructure&lt;/span&gt;
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform will show you exactly what it's going to create. Type &lt;code&gt;yes&lt;/code&gt; when you're ready to proceed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Test Your Backup System
&lt;/h2&gt;

&lt;p&gt;Don't wait until disaster strikes to test your backups! Here's how to verify everything works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trigger a manual backup:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="nt"&gt;--function-name&lt;/span&gt; my-backup-fn response.json
&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%2Ff9jze4bjcofet8th6vq1.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%2Ff9jze4bjcofet8th6vq1.png" alt="Invoke AWS Lambda" width="608" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check the logs:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to CloudWatch → Log groups&lt;/li&gt;
&lt;li&gt;Find &lt;code&gt;/aws/lambda/my-backup-fn&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check the latest log stream
&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%2F4qk8rpjvqr9oa4ke678s.png" alt="Logs" width="800" height="213"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Verify snapshots were created:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to EC2 → Snapshots&lt;/li&gt;
&lt;li&gt;Look for snapshots tagged with &lt;code&gt;CreatedBy=automated-backup&lt;/code&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%2Frsiti5kwmf9m088kgf4f.png" alt="AWS EC2 Snapshots" width="800" height="167"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Check S3 logs:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to S3 → your backup logs bucket&lt;/li&gt;
&lt;li&gt;Look in the &lt;code&gt;backup-logs/&lt;/code&gt; folder for detailed reports
&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%2F0wbtas14un6w7qm7jpf7.png" alt="AWS S3 Bucket" width="800" height="315"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Monitoring and Maintenance
&lt;/h2&gt;

&lt;p&gt;Your backup system is now running, but here are some tips to keep it healthy:&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Up Alerts
&lt;/h3&gt;

&lt;p&gt;Create a CloudWatch alarm to notify you if backups fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudwatch_metric_alarm"&lt;/span&gt; &lt;span class="s2"&gt;"backup_failures"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;alarm_name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.project_name}-backup-failures"&lt;/span&gt;
  &lt;span class="nx"&gt;comparison_operator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GreaterThanThreshold"&lt;/span&gt;
  &lt;span class="nx"&gt;evaluation_periods&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
  &lt;span class="nx"&gt;metric_name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Errors"&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/Lambda"&lt;/span&gt;
  &lt;span class="nx"&gt;period&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"300"&lt;/span&gt;
  &lt;span class="nx"&gt;statistic&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Sum"&lt;/span&gt;
  &lt;span class="nx"&gt;threshold&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;
  &lt;span class="nx"&gt;alarm_description&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This metric monitors lambda errors"&lt;/span&gt;
  &lt;span class="nx"&gt;alarm_actions&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_sns_topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;dimensions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;FunctionName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function_name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cost Optimization
&lt;/h3&gt;

&lt;p&gt;Monitor your snapshot costs and adjust retention as needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check snapshot costs&lt;/span&gt;
aws ce get-cost-and-usage &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--time-period&lt;/span&gt; &lt;span class="nv"&gt;Start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2025-01-01,End&lt;span class="o"&gt;=&lt;/span&gt;2026-01-31 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--granularity&lt;/span&gt; MONTHLY &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metrics&lt;/span&gt; BlendedCost &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-by&lt;/span&gt; &lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;DIMENSION,Key&lt;span class="o"&gt;=&lt;/span&gt;SERVICE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Regular Testing
&lt;/h3&gt;

&lt;p&gt;Schedule monthly restore tests to ensure your backups actually work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a test instance from a snapshot&lt;/li&gt;
&lt;li&gt;Verify the instance boots and data is intact&lt;/li&gt;
&lt;li&gt;Document the restore process&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lambda timeout errors:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increase the timeout in your Terraform configuration&lt;/li&gt;
&lt;li&gt;Consider splitting large backup jobs across multiple Lambda invocations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Permission denied errors:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check the IAM policy in &lt;code&gt;templates/lambda_policy.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ensure your AWS credentials have sufficient permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Snapshots not being created:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify instances are tagged correctly (&lt;code&gt;Backup=true&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Check CloudWatch logs for specific error messages&lt;/li&gt;
&lt;li&gt;Ensure instances are in the same region as your Lambda&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;EventBridge not triggering:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify the cron expression is correct&lt;/li&gt;
&lt;li&gt;Check that the Lambda permission allows EventBridge to invoke it&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;You now have a production-ready EC2 backup system! Here are some enhancements you might consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-region backups:&lt;/strong&gt; Copy snapshots to another region for disaster recovery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack notifications:&lt;/strong&gt; Get notified when backups complete or fail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup verification:&lt;/strong&gt; Automatically test that snapshots are restorable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Building automated backups doesn't have to be complicated. With Lambda, EventBridge, and Terraform, you can create a robust, cost-effective backup system in under an hour.&lt;/p&gt;

&lt;p&gt;The key benefits of this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set it and forget it&lt;/strong&gt;: Once deployed, it runs automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost-effective&lt;/strong&gt;: Pay only for what you use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable&lt;/strong&gt;: Works for 5 instances or 500&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditable&lt;/strong&gt;: Complete logs of every backup operation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliable&lt;/strong&gt;: Built on AWS managed services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Remember&lt;/strong&gt;: Manual backups fail; automated backups succeed.&lt;/p&gt;

&lt;p&gt;Your future self (and your boss) will thank you when that inevitable "we need to restore from backup" moment arrives, and you can confidently say: "No problem, we have automated backups running every night."&lt;/p&gt;

&lt;p&gt;Now go tag those instances and sleep better knowing your data is protected!&lt;/p&gt;




&lt;h2&gt;
  
  
  Contact
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Have questions about this setup? Found a bug in the code? Drop a comment below&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;or reach out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://hasan-ashab.vercel.app/" rel="noopener noreferrer"&gt;hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://linkedin.com/in/hasan-ashab/" rel="noopener noreferrer"&gt;linkedin.com/in/hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>s3</category>
      <category>terraform</category>
    </item>
    <item>
      <title>2048 in the Cloud: DevOps with AWS &amp; ArgoCD</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Sun, 28 Sep 2025 05:27:27 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/2048-in-the-cloud-devops-with-aws-argocd-1gpe</link>
      <guid>https://forem.com/hasan_ashab/2048-in-the-cloud-devops-with-aws-argocd-1gpe</guid>
      <description>&lt;p&gt;&lt;em&gt;Overengineering a simple game to practice real-world DevOps skills&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Yes, I know — nobody needs Kubernetes, Terraform, and ArgoCD to run 2048. It's overkill. But that's exactly why I did it. By deploying a simple static game with enterprise-grade tools, I could focus purely on &lt;strong&gt;DevOps patterns&lt;/strong&gt; without being distracted by app complexity.&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%2Fc1dczz3fsw8t1frye3wn.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%2Fc1dczz3fsw8t1frye3wn.png" alt="2048 game in the cloud" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This project turned the classic 2048 game into a cloud-native demo running on AWS with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker + ECR for containerization&lt;/li&gt;
&lt;li&gt;Kubernetes on EKS for orchestration&lt;/li&gt;
&lt;li&gt;GitHub Actions for CI/CD&lt;/li&gt;
&lt;li&gt;ArgoCD for GitOps deployments&lt;/li&gt;
&lt;li&gt;Terraform for infrastructure as code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🔗 &lt;a href="https://github.com/HasanAshab/2048-game-devops" rel="noopener noreferrer"&gt;Source code on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 2048?
&lt;/h2&gt;

&lt;p&gt;The game itself is trivial — just static HTML/JS. But that's the point. By stripping away complicated backend logic, You will be free to practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building a full pipeline from scratch&lt;/li&gt;
&lt;li&gt;Managing cloud infra with Terraform&lt;/li&gt;
&lt;li&gt;Deploying to Kubernetes&lt;/li&gt;
&lt;li&gt;Setting up GitOps with ArgoCD&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not about the game. It's about showing that if you can deploy 2048 this way, you can deploy &lt;em&gt;any app&lt;/em&gt; this way.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  1. Containerized the Game
&lt;/h3&gt;

&lt;p&gt;I packaged the 2048 codebase into a Docker image using &lt;code&gt;nginx:alpine&lt;/code&gt; as a lightweight web server:&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;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /usr/share/nginx/html/&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was pushed to &lt;strong&gt;Amazon ECR&lt;/strong&gt;, ready for Kubernetes.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Kubernetes Deployment on EKS
&lt;/h3&gt;

&lt;p&gt;I wrote manifests for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: 3 replicas for availability, with resource limits and probes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service&lt;/strong&gt;: Type &lt;code&gt;LoadBalancer&lt;/code&gt; to expose via AWS Load Balancer.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;game-2048&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;game-2048&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;game-2048&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;game-2048&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;ECR_REPO&amp;gt;/game-2048:latest&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. CI/CD with GitHub Actions
&lt;/h3&gt;

&lt;p&gt;A workflow handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linting and basic tests&lt;/li&gt;
&lt;li&gt;Building + pushing Docker image to ECR&lt;/li&gt;
&lt;li&gt;Updating manifests&lt;/li&gt;
&lt;/ul&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%2Fh2sph5bdvkw3890kk1ol.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%2Fh2sph5bdvkw3890kk1ol.png" alt="GitHub Actions Workflow" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This keeps the pipeline automated from commit → container → deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. GitOps with ArgoCD
&lt;/h3&gt;

&lt;p&gt;Instead of manually applying manifests, I set up &lt;strong&gt;ArgoCD&lt;/strong&gt; on EKS. It watches the Git repo and automatically syncs changes, giving me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated deployments&lt;/li&gt;
&lt;li&gt;Rollback capabilities&lt;/li&gt;
&lt;li&gt;A nice UI to visualize app state&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Infrastructure with Terraform
&lt;/h3&gt;

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

&lt;ul&gt;
&lt;li&gt;VPC + subnets&lt;/li&gt;
&lt;li&gt;EKS cluster + node groups&lt;/li&gt;
&lt;li&gt;ECR repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the whole setup reproducible in any AWS account.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitOps feels powerful&lt;/strong&gt;: Letting ArgoCD drive deployments keeps clusters in sync with Git.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IaC saves time&lt;/strong&gt;: Rebuilding infra with Terraform is way easier than clicking through AWS console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overkill is okay for learning&lt;/strong&gt;: Practicing with simple apps helps when stakes are low.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker + EKS + ArgoCD&lt;/strong&gt; is a solid baseline stack for real-world apps.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;If I revisit this project, I'd like to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add monitoring (CloudWatch or Prometheus)&lt;/li&gt;
&lt;li&gt;Use Fargate for serverless pods&lt;/li&gt;
&lt;li&gt;Try spot instances for cost savings&lt;/li&gt;
&lt;li&gt;Add Route53 + SSL for a proper domain&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Running 2048 on AWS with Terraform, Kubernetes, and ArgoCD is complete overkill — and that's exactly the point. This project let me practice the same workflows companies use in production, but in a low-stakes, fun environment.&lt;/p&gt;

&lt;p&gt;If you can take a tiny game and ship it with a full DevOps stack, you're already building the habits needed for larger, real-world systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  📬 Contact
&lt;/h2&gt;

&lt;p&gt;If you have any doubts, feel free to reach out and ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://hasan-ashab.vercel.app/" rel="noopener noreferrer"&gt;hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://linkedin.com/in/hasan-ashab/" rel="noopener noreferrer"&gt;linkedin.com/in/hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>kubernetes</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>From $820 to $92: How A Startup Cut Cloud Costs by 88%</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Thu, 25 Sep 2025 00:58:23 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/from-820-to-92-how-a-startup-cut-cloud-costs-by-88-2mne</link>
      <guid>https://forem.com/hasan_ashab/from-820-to-92-how-a-startup-cut-cloud-costs-by-88-2mne</guid>
      <description>&lt;p&gt;Last night my foreign cousin was sharing his startup story with me.&lt;/p&gt;

&lt;p&gt;Things were going well — their product was gaining users, the team was excited… but then he mentioned the AWS cloud bill. What began as a few hundred dollars a month had suddenly ballooned into the thousands. For a small team, that’s brutal.&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%2Froj1q75j4qaxulegt5uo.webp" 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%2Froj1q75j4qaxulegt5uo.webp" alt="cloud bills exploding" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This isn’t rare. Startups (and small teams) often scale the product faster than they manage costs. The good news: most cloud-cost problems are straightforward to fix. Here’s a simple, non-overwhelming checklist that actually works.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Don’t Over-Engineer for Small Teams
&lt;/h2&gt;

&lt;p&gt;A classic money trap is spinning up production-grade infrastructure for non-critical tasks. I’ve seen early-stage teams launch a full-blown EKS cluster just to run CI/CD pipelines. That’s like renting an office tower just to store sticky notes.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Fix:&lt;/strong&gt; For testing and pipelines, use &lt;strong&gt;Kind&lt;/strong&gt;, &lt;strong&gt;Minikube&lt;/strong&gt;, or even lightweight managed CI/CD tools. Keep Kubernetes clusters for production workloads — not for experimenting.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Right-Size Your Instances
&lt;/h2&gt;

&lt;p&gt;My cousin had several servers idling at ~20% CPU — like renting a conference hall for one person.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Fix:&lt;/strong&gt; Move to smaller instance types, enable auto-scaling, and review sizing monthly. Don’t pay for unused headroom.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Implement Scheduling Tools
&lt;/h2&gt;

&lt;p&gt;Think of this as flipping off the lights when you leave the office. Non-critical resources (like test databases or staging VMs) don’t need to run during nights and weekends.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Fix:&lt;/strong&gt; Use scheduling tools (like AWS Instance Scheduler, Azure Automation, or GCP Scheduler) to shut things down during off-hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Automate Non-Prod Shutdowns
&lt;/h2&gt;

&lt;p&gt;Dev and staging environments don’t need to run 24/7. Yet many teams forget to turn them off.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Fix:&lt;/strong&gt; Schedule automatic stop/start (nights, weekends) or use ephemeral environments spun up by CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Use Discounted Options Wisely
&lt;/h2&gt;

&lt;p&gt;Paying list price for everything is expensive. There are cheaper options if you plan a bit.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reserved instances&lt;/strong&gt; for predictable, long-running workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot/preemptible instances&lt;/strong&gt; for batch jobs, CI runs, or anything that can tolerate interruptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Cut Data Transfer (Egress) Costs
&lt;/h2&gt;

&lt;p&gt;Cross-region traffic and uncontrolled log shipping can surprise you. My cousin’s service was shuttling logs between regions — huge egress fees.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Fix:&lt;/strong&gt; Co-locate services in the same region, use a CDN for static assets, compress or batch logs, and avoid unnecessary cross-region calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Cache, Cache, Cache
&lt;/h2&gt;

&lt;p&gt;Serving the same data repeatedly from compute or the origin costs more than a cache.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Fix:&lt;/strong&gt; Add caching for frequently requested items (CDN, Redis, in-memory caches). It reduces compute and bandwidth.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Leverage Infrastructure as Code (IaC)
&lt;/h2&gt;

&lt;p&gt;Manual provisioning often leads to forgotten, duplicate, or oversized resources. IaC keeps things clean and repeatable.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Fix:&lt;/strong&gt; Use &lt;strong&gt;Terraform&lt;/strong&gt;, &lt;strong&gt;CloudFormation&lt;/strong&gt;, or &lt;strong&gt;Pulumi&lt;/strong&gt; to define infrastructure. This ensures resources are consistently right-sized and prevents accidental overspending.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Spot the “Zombie” Resources
&lt;/h2&gt;

&lt;p&gt;Old test servers, forgotten storage buckets, and unused IPs quietly drain money.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Fix:&lt;/strong&gt; Do a weekly cleanup. If you can’t explain a resource in 60 seconds, stop it or tag it for deletion.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Set Budgets and Alerts (and Enforce Them)
&lt;/h2&gt;

&lt;p&gt;The easiest prevention is a simple alert before things spiral. Too many teams treat budget alerts as optional.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Fix:&lt;/strong&gt; Configure cost alerts that notify the team and enforce escalation (Slack + email + owner). Treat alerts like production incidents.&lt;/p&gt;




&lt;h3&gt;
  
  
  🚀 Quick Results You Can Expect
&lt;/h3&gt;

&lt;p&gt;Apply these fixes and you’ll usually see &lt;strong&gt;20–50% savings&lt;/strong&gt; within a month. My cousin implemented these and cut their monthly bill by nearly half — money that went straight into hiring and product improvements.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Learn from others mistakes, not yours.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fd587z5u2cgrv0jqy1onk.jpg" 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%2Fd587z5u2cgrv0jqy1onk.jpg" alt="reduce cloud bills" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cloud cost control isn’t magic. It’s discipline. Do those and your cloud will stop eating your runway.&lt;/p&gt;




&lt;h2&gt;
  
  
  📬 Contact
&lt;/h2&gt;

&lt;p&gt;If you have any doubts, feel free to reach out and ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://hasan-ashab.vercel.app/" rel="noopener noreferrer"&gt;hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://linkedin.com/in/hasan-ashab/" rel="noopener noreferrer"&gt;linkedin.com/in/hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>finops</category>
      <category>cloud</category>
      <category>aws</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Deploying Containers on AWS: The One Guide You Need</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Mon, 22 Sep 2025 09:36:12 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/deploying-containers-on-aws-the-one-guide-you-need-18mc</link>
      <guid>https://forem.com/hasan_ashab/deploying-containers-on-aws-the-one-guide-you-need-18mc</guid>
      <description>&lt;p&gt;When I first started working with containers on AWS, I was overwhelmed. There were too many services—ECS, EKS, Lambda, App Runner, Lightsail—and each one came with its own pros and cons. The real struggle wasn’t running containers; it was &lt;strong&gt;deciding where&lt;/strong&gt; to run them.&lt;/p&gt;

&lt;p&gt;If you’ve ever felt the same, you’re not alone. That’s why I put together this guide. Think of it as a friendly map—helping you pick the right AWS service without drowning in documentation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Ask Yourself — Do You Need Kubernetes?
&lt;/h2&gt;

&lt;p&gt;Kubernetes is powerful, but also complex. If you’re already using it or plan to, &lt;strong&gt;Amazon Elastic Kubernetes Service (EKS)&lt;/strong&gt; is the natural choice.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros&lt;/strong&gt;: Fully managed, highly scalable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons&lt;/strong&gt;: Setup isn’t beginner-friendly, and pricing is resource-based.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Kubernetes isn’t part of your journey, no worries. AWS has plenty of other roads to explore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Serverless or Provisioned?
&lt;/h2&gt;

&lt;p&gt;This is the big fork in the road.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you want AWS to handle servers for you → go &lt;strong&gt;Serverless&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If you want full control over compute resources → go &lt;strong&gt;Provisioned&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Serverless Path 🚀
&lt;/h3&gt;

&lt;p&gt;Not sure if serverless is the right direction?&lt;br&gt;
I wrote a separate guide on &lt;a href="https://dev.to/hasan_ashab/serverless-the-hype-is-real-but-is-it-for-you-17pi"&gt;When to Go Serverless&lt;/a&gt;. that might help you decide.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Provisioned Path ⚙️
&lt;/h3&gt;

&lt;p&gt;If you prefer setting things up yourself:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lightsail&lt;/strong&gt;: Simplest UI, great for small projects.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Catch&lt;/strong&gt;: You’re locked into AWS’s ecosystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ECS (Elastic Container Service)&lt;/strong&gt;: Managed orchestration without Kubernetes.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best for&lt;/strong&gt;: Teams that need scaling + orchestration but don’t want Kubernetes overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downside&lt;/strong&gt;: Steep learning curve.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;EC2&lt;/strong&gt;: Old-school way—install Docker and manage everything yourself.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best for&lt;/strong&gt;: Absolute control freaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downside&lt;/strong&gt;: Frustrating setup and heavy maintenance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Visual Shortcut
&lt;/h2&gt;

&lt;p&gt;Here’s a handy flowchart to guide your decision:&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%2Fh78j5xa5ho6990a7ahjx.jpg" 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%2Fh78j5xa5ho6990a7ahjx.jpg" alt="AWS service to deploy containers" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This chart sums up everything we just walked through—so you can quickly see which AWS service matches your needs.&lt;/p&gt;




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

&lt;p&gt;There isn’t a single “best” way to run containers on AWS. The right choice depends on your priorities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ease of use?&lt;/strong&gt; → Lightsail or App Runner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless-first mindset?&lt;/strong&gt; → Lambda or Fargate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already love Kubernetes?&lt;/strong&gt; → EKS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want to tinker with everything?&lt;/strong&gt; → EC2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The good news is, no matter which road you take, AWS has you covered. Start small, experiment, and you’ll soon find the perfect fit for your workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  📬 Contact
&lt;/h2&gt;

&lt;p&gt;If you have any doubts (Cloud or DevOps), feel free to reach out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://hasan-ashab.vercel.app/" rel="noopener noreferrer"&gt;hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://linkedin.com/in/hasan-ashab/" rel="noopener noreferrer"&gt;linkedin.com/in/hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>containers</category>
      <category>cloud</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>A Serverless Todo App on AWS with Terraform and GitHub Actions</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Sun, 21 Sep 2025 05:12:32 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/a-serverless-todo-app-on-aws-with-terraform-and-github-actions-mo9</link>
      <guid>https://forem.com/hasan_ashab/a-serverless-todo-app-on-aws-with-terraform-and-github-actions-mo9</guid>
      <description>&lt;p&gt;When I started this project, I didn’t want to just build &lt;em&gt;another&lt;/em&gt; todo app. We’ve all seen those before. My goal was different: &lt;strong&gt;build something simple on the outside, but production-ready on the inside&lt;/strong&gt; — something that shows what a DevOps engineer actually does.&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%2Fop45v5dxbtciaws58dkm.jpg" 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%2Fop45v5dxbtciaws58dkm.jpg" alt="Serverless Todo App" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s how this &lt;strong&gt;Serverless Todo App&lt;/strong&gt; came to life. A lightweight frontend for users, but behind it sits a complete AWS setup: serverless compute, Infrastructure as Code, and automated CI/CD pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  🌱 Why Serverless?
&lt;/h2&gt;

&lt;p&gt;The choice was clear. For small apps (and honestly, for many startups too), serverless hits the sweet spot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Perfect for apps with unpredictable traffic (like side projects, MVPs, or apps with spiky usage)&lt;/li&gt;
&lt;li&gt;Scales up (and down to zero) automatically&lt;/li&gt;
&lt;li&gt;Pay only for what you use (almost zero cost when idle)&lt;/li&gt;
&lt;li&gt;High availability by default&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically: if your customer base is uncertain or your workload isn’t steady, serverless is the go-to choice.&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%2Fbxf4rk8io1xrwl7ohx8z.jpg" 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%2Fbxf4rk8io1xrwl7ohx8z.jpg" alt="Why Serverless" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I followed a classic three-tier approach — but all serverless:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React app hosted in S3&lt;/li&gt;
&lt;li&gt;CloudFront for fast, global delivery&lt;/li&gt;
&lt;li&gt;OAC so no one can hit S3 directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Gateway to handle requests&lt;/li&gt;
&lt;li&gt;Lambda functions for each CRUD operation&lt;/li&gt;
&lt;li&gt;IAM roles with the bare minimum permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DynamoDB table with &lt;code&gt;id&lt;/code&gt; as hash key&lt;/li&gt;
&lt;li&gt;Pay-per-request billing — no wasted capacity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a sketch of it all working together:&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%2Fwdlid5a3jrp0zmjaynt5.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%2Fwdlid5a3jrp0zmjaynt5.png" alt="Serverless Architecture" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Infrastructure as Code with Terraform
&lt;/h2&gt;

&lt;p&gt;Instead of clicking around the AWS console, everything is defined in Terraform. I broke it into &lt;strong&gt;modules&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;frontend/&lt;/code&gt; → S3 + CloudFront setup&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;backend/&lt;/code&gt; → API Gateway + Lambda functions + IAM&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dynamodb/&lt;/code&gt; → Key-value table&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This modular approach made it easy to spin up dev, staging, and prod environments. Just change a &lt;code&gt;tfvars&lt;/code&gt; file, and Terraform does the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ CI/CD Pipelines
&lt;/h2&gt;

&lt;p&gt;The real DevOps flavor comes here. I used &lt;strong&gt;GitHub Actions&lt;/strong&gt; to automate deployments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend pipeline&lt;/strong&gt; → Build React app, push to S3, invalidate CloudFront
&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%2Fzcku9g1cdrdn8aygucgn.png" alt="Frontend CI/CD" width="800" height="248"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend pipeline&lt;/strong&gt; → Package &amp;amp; deploy Lambda functions
&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%2F3ndf6dkn7o51kqr9mqeb.png" alt="Backend CI/CD" width="800" height="308"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infra pipeline&lt;/strong&gt; → Terraform plan/apply with drift detection
&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%2Fxljny82wg2qosrnjcai0.png" alt="Terraform CI/CD" width="800" height="169"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production deployments always require &lt;strong&gt;manual approval&lt;/strong&gt; — I wanted it to feel realistic, not reckless.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔍 Monitoring &amp;amp; Security
&lt;/h2&gt;

&lt;p&gt;A few non-negotiables I baked in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudWatch metrics and alerts for errors and latency&lt;/li&gt;
&lt;li&gt;Terraform drift detection (to catch sneaky manual console changes)&lt;/li&gt;
&lt;li&gt;IAM least privilege for every Lambda&lt;/li&gt;
&lt;li&gt;CloudFront enforcing HTTPS and hiding the S3 bucket&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing fancy, just the basics done right.&lt;/p&gt;

&lt;h2&gt;
  
  
  💸 Cost-Friendly by Default
&lt;/h2&gt;

&lt;p&gt;Since it’s serverless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda only bills on execution&lt;/li&gt;
&lt;li&gt;DynamoDB scales automatically&lt;/li&gt;
&lt;li&gt;CloudFront caching reduces backend hits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In idle periods, the whole system costs almost nothing.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terraform modules&lt;/strong&gt; are a lifesaver for reusability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate pipelines&lt;/strong&gt; keep infra, backend, and frontend independent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless debugging&lt;/strong&gt; is trickier than traditional apps (logs are your best friend)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold starts&lt;/strong&gt; exist, but caching and design can reduce the pain&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;At first glance, it’s “just a todo app.” But under the hood, it’s a real-world &lt;strong&gt;DevOps case study&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure as Code&lt;/li&gt;
&lt;li&gt;Automated CI/CD&lt;/li&gt;
&lt;li&gt;Secure, scalable, cost-efficient design&lt;/li&gt;
&lt;li&gt;Production-ready practices like monitoring, drift detection, and multi-env setups&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📂 Code &amp;amp; Repo
&lt;/h2&gt;

&lt;p&gt;You can check out the full implementation here:&lt;br&gt;
👉 &lt;a href="https://github.com/HasanAshab/serverless-todo-app" rel="noopener noreferrer"&gt;Serverless Todo App on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thought
&lt;/h3&gt;

&lt;p&gt;I built this project to show that even something simple can demonstrate &lt;strong&gt;serious DevOps skills&lt;/strong&gt; when designed with the right mindset.&lt;/p&gt;




&lt;h2&gt;
  
  
  📬 Contact
&lt;/h2&gt;

&lt;p&gt;If you’d like to connect, collaborate, or discuss DevOps, feel free to reach out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://hasan-ashab.vercel.app/" rel="noopener noreferrer"&gt;hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/HasanAshab/" rel="noopener noreferrer"&gt;github.com/HasanAshab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://linkedin.com/in/hasan-ashab/" rel="noopener noreferrer"&gt;linkedin.com/in/hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>terraform</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Serverless: The Hype is Real. But Is It For You?</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Thu, 18 Sep 2025 09:26:12 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/serverless-the-hype-is-real-but-is-it-for-you-17pi</link>
      <guid>https://forem.com/hasan_ashab/serverless-the-hype-is-real-but-is-it-for-you-17pi</guid>
      <description>&lt;p&gt;A couple of months ago, A friend of mine told me how their team started a side project at a café. They were ambitious but strapped for time and resources. Someone suggested: &lt;strong&gt;“Why don’t we just go serverless?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The room lit up. Serverless sounded magical — no servers to manage, instant scaling, and a pay-as-you-go model. It was like being promised a sports car with no gas bills. They jumped in.&lt;/p&gt;

&lt;p&gt;And yes, the hype felt very real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Allure of Serverless
&lt;/h2&gt;

&lt;p&gt;At first, it felt like cheating. With just a few lines of code and some clicks on AWS, they had a function running in the cloud. Requests came in, it responded instantly, and no one had to touch a single server.&lt;/p&gt;

&lt;p&gt;That’s the beauty of serverless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-scaling&lt;/strong&gt; — it grows with you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower upfront cost&lt;/strong&gt; — you pay only for execution time, not idle servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt; — perfect for shipping features fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event-driven magic&lt;/strong&gt; — upload an image, and boom, a function resizes it automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small teams and startups, this is a dream. You can get an MVP live in days, not weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reality Check: The Caveats
&lt;/h2&gt;

&lt;p&gt;But dreams have their fine print. As the project grew, cracks started to show.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cold Starts&lt;/strong&gt;: The first request after a function sat idle? Slow. Painfully slow. Imagine users waiting an extra second or two just because the function needed to “wake up.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vendor Lock-In&lt;/strong&gt;: They built tightly around AWS Lambda. When they thought about moving parts of the system elsewhere, the cost of rewriting was overwhelming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging and Monitoring&lt;/strong&gt;: Traditional logs and metrics didn’t tell the full story. Tracking errors across dozens of tiny functions was like chasing shadows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Pitfalls&lt;/strong&gt;: While serverless seemed cheap in the beginning, their bill spiked once traffic became steady. The per-execution cost wasn’t built for heavy, consistent workloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The magic wasn’t gone — but it came with strings attached.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who Really Wins With Serverless?
&lt;/h2&gt;

&lt;p&gt;That journey taught them (and me, by hearing it) something simple: serverless isn’t bad. It’s just &lt;strong&gt;situational&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Great fit for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Startups testing an idea without big infrastructure spend.&lt;/li&gt;
&lt;li&gt;Apps with spiky traffic (events, notifications, seasonal demand).&lt;/li&gt;
&lt;li&gt;Background tasks like image resizing, PDF generation, or chatbots.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not ideal for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heavy, long-running jobs like video rendering.&lt;/li&gt;
&lt;li&gt;Applications that demand ultra-low latency at all times.&lt;/li&gt;
&lt;li&gt;Teams that want full control over networking, scaling policies, or cost optimization.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Future: Not Just a Fad
&lt;/h2&gt;

&lt;p&gt;Despite the struggles, serverless isn’t a passing trend. It’s evolving fast.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloudflare Workers and Vercel Edge Functions push compute even closer to users.&lt;/li&gt;
&lt;li&gt;Serverless databases are emerging to solve the data bottleneck.&lt;/li&gt;
&lt;li&gt;Hybrid approaches let you mix traditional servers with serverless functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many companies, the future isn’t “all-in serverless” — it’s about &lt;strong&gt;using it where it shines, and avoiding it where it doesn’t&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Hearing their story made me rethink the “serverless hype.” It’s not just buzz — the power is real. But it also made me realize it’s not a one-size-fits-all solution.&lt;/p&gt;

&lt;p&gt;So before you jump in, ask yourself: &lt;em&gt;Is this the hammer I need, or am I just chasing the shine of something new?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📬 Contact
&lt;/h2&gt;

&lt;p&gt;If you’d like to connect, collaborate, or discuss DevOps, feel free to reach out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://hasan-ashab.vercel.app/" rel="noopener noreferrer"&gt;hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/HasanAshab/" rel="noopener noreferrer"&gt;github.com/HasanAshab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://linkedin.com/in/hasan-ashab/" rel="noopener noreferrer"&gt;linkedin.com/in/hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloud</category>
      <category>serverless</category>
      <category>architecture</category>
    </item>
    <item>
      <title>From 1.2GB to 54MB: My Docker Image Went on a Diet</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Wed, 17 Sep 2025 01:28:02 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/from-12gb-to-54mb-my-docker-image-went-on-a-diet-apj</link>
      <guid>https://forem.com/hasan_ashab/from-12gb-to-54mb-my-docker-image-went-on-a-diet-apj</guid>
      <description>&lt;p&gt;When I first containerized my Node.js app, I felt pretty good about myself. I had a Dockerfile, I built it, and it worked.&lt;/p&gt;

&lt;p&gt;Then I checked the size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.2GB. For a single Node.js service.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s when reality hit me. My image wasn’t lean—it was obese. It slowed down builds, bloated my CI/CD pipeline, took forever to push to the registry, and ate storage like there was no tomorrow.&lt;/p&gt;

&lt;p&gt;So, I put my Docker image on a strict diet. After a few rounds of optimizations, it went from &lt;strong&gt;1.2GB → 250MB → 54MB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s the story of how I cut the fat—and how you can too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: The Heavyweight Start
&lt;/h2&gt;

&lt;p&gt;Here’s what my original Dockerfile looked like:&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;FROM&lt;/span&gt;&lt;span class="s"&gt; node:16&lt;/span&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;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks innocent, right? But it had several problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;node:16&lt;/code&gt; is Debian-based and heavy (~350MB).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm install&lt;/code&gt; installed &lt;strong&gt;everything&lt;/strong&gt;—dev and production dependencies.&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;.dockerignore&lt;/code&gt;, so logs, git history, and &lt;code&gt;node_modules&lt;/code&gt; sneaked into the image.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? A &lt;strong&gt;1.2GB monster&lt;/strong&gt; that slowed everything down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Choosing a Leaner Base
&lt;/h2&gt;

&lt;p&gt;The first fix was swapping &lt;code&gt;node:16&lt;/code&gt; for &lt;code&gt;node:16-alpine&lt;/code&gt;.&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;FROM&lt;/span&gt;&lt;span class="s"&gt; node:16-alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one-line change &lt;strong&gt;cut my image down to ~250MB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Lesson: &lt;strong&gt;Your base image choice can make or break your build.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;⚠️ Caveat: Alpine uses &lt;strong&gt;musl&lt;/strong&gt; instead of glibc. If your app has native modules (&lt;code&gt;sharp&lt;/code&gt;, &lt;code&gt;bcrypt&lt;/code&gt;, &lt;code&gt;canvas&lt;/code&gt;), you may need extra packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Multi-Stage Builds
&lt;/h2&gt;

&lt;p&gt;My app uses TypeScript, so I had build tools sitting inside the final image. Big mistake. They added hundreds of MBs I didn’t need in production.&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;multi-stage builds&lt;/strong&gt;:&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="c"&gt;# Stage 1: Builder&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:16-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&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 ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Stage 2: Runtime&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:16-alpine&lt;/span&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; --from=builder /app/dist ./dist&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 ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the final image contains only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compiled JavaScript (&lt;code&gt;dist/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Production dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No dev dependencies. No build cache. No clutter.&lt;/p&gt;

&lt;p&gt;This dropped my image to ~120MB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Prune and Ignore Junk
&lt;/h2&gt;

&lt;p&gt;Another culprit: files that had no business being in production.&lt;/p&gt;

&lt;p&gt;I added a &lt;code&gt;.dockerignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
*.md
tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I cleaned up caches in the Dockerfile:&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;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /tmp/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;End result: no accidental junk, no wasted MBs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Minimize Layers
&lt;/h2&gt;

&lt;p&gt;At first, I had a Dockerfile with multiple &lt;code&gt;RUN&lt;/code&gt; statements:&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;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; python3
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;RUN &lt;/span&gt;npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;RUN&lt;/code&gt; adds a layer. I combined them into one:&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;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; python3 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This small tweak shaved off ~15MB. Not huge, but every MB counts when you’re pulling images in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Measuring and Iterating
&lt;/h2&gt;

&lt;p&gt;The key to trimming images is &lt;strong&gt;measuring&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker images
docker &lt;span class="nb"&gt;history&lt;/span&gt; &amp;lt;image&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;docker history&lt;/code&gt;, I saw exactly which layer was eating space and optimized from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Weight Check
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Original:&lt;/strong&gt; 1.2GB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After switching to Alpine:&lt;/strong&gt; ~250MB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After multi-stage + pruning:&lt;/strong&gt; 120MB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After &lt;code&gt;.dockerignore&lt;/code&gt; + cleanup:&lt;/strong&gt; 54MB 🎉&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s a &lt;strong&gt;~95% reduction&lt;/strong&gt;. Pulls went from minutes to seconds, and CI/CD pipelines stopped crawling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick the right base image&lt;/strong&gt; – Defaults are rarely optimal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-stage builds are gold&lt;/strong&gt; – Keep dev tools out of production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;.dockerignore&lt;/code&gt; religiously&lt;/strong&gt; – Don’t ship junk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prune aggressively&lt;/strong&gt; – Caches, logs, temp files… delete them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure constantly&lt;/strong&gt; – Know what’s eating space before fixing it.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Cutting Docker image size isn’t just about bragging rights—it’s about faster deploys, lower registry costs, and fewer headaches.&lt;/p&gt;

&lt;p&gt;My Node.js image went on a diet and lost &lt;strong&gt;1.1GB&lt;/strong&gt;, and I’ll never go back to lazy Dockerfiles again.&lt;/p&gt;

&lt;p&gt;If your containers are bloated, trust me: a few tweaks can make them featherweight.&lt;/p&gt;

&lt;p&gt;So… is your Docker image on a healthy diet?&lt;/p&gt;




&lt;h2&gt;
  
  
  📬 Contact
&lt;/h2&gt;

&lt;p&gt;If you’d like to connect, collaborate, or discuss DevOps, feel free to reach out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://hasan-ashab.vercel.app/" rel="noopener noreferrer"&gt;hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/HasanAshab/" rel="noopener noreferrer"&gt;github.com/HasanAshab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://linkedin.com/in/hasan-ashab/" rel="noopener noreferrer"&gt;linkedin.com/in/hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>performance</category>
      <category>containers</category>
    </item>
    <item>
      <title>Productionizing AWS’s Retail Sample App with GitOps on EKS</title>
      <dc:creator>Hasan Ashab</dc:creator>
      <pubDate>Tue, 16 Sep 2025 04:54:56 +0000</pubDate>
      <link>https://forem.com/hasan_ashab/productionizing-awss-retail-sample-app-with-gitops-on-eks-22f2</link>
      <guid>https://forem.com/hasan_ashab/productionizing-awss-retail-sample-app-with-gitops-on-eks-22f2</guid>
      <description>&lt;p&gt;&lt;em&gt;How I transformed AWS’s demo retail microservices application into a production-ready, cost-optimized platform using Terraform, GitHub Actions, ArgoCD, and EKS Auto Mode.&lt;/em&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%2Fncee0vquua712l935lzv.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%2Fncee0vquua712l935lzv.png" alt="Banner" width="800" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/aws-containers/retail-store-sample-app" rel="noopener noreferrer"&gt;AWS Retail Sample App&lt;/a&gt; is a microservices-based demo designed to showcase cloud-native workloads. While it demonstrates architecture concepts, it’s not “production-ready” out of the box.&lt;/p&gt;

&lt;p&gt;The objective of this project was to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Take the AWS Retail Sample App (5 microservices)&lt;/li&gt;
&lt;li&gt;Deploy it on &lt;strong&gt;Amazon EKS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Implement &lt;strong&gt;infrastructure as code, CI/CD, and GitOps&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Ensure the setup was &lt;strong&gt;secure, observable, and cost-efficient&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This case study documents the approach, challenges, and outcomes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&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%2Ftf6qkjy0p8bab06q8tl8.gif" 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%2Ftf6qkjy0p8bab06q8tl8.gif" alt="EKS" width="600" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final platform integrated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terraform&lt;/strong&gt; for infrastructure as code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon EKS Auto Mode&lt;/strong&gt; to simplify compute management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/CD with intelligent change detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ArgoCD&lt;/strong&gt; for GitOps-driven continuous delivery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECR&lt;/strong&gt; as the private container registry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ingress + Cert Manager&lt;/strong&gt; for HTTPS traffic&lt;/li&gt;
&lt;li&gt;Optional &lt;strong&gt;Prometheus/Grafana&lt;/strong&gt; for observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;High-level workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer → GitHub → GitHub Actions → ECR → ArgoCD → EKS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Infrastructure as Code with Terraform
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Deployed EKS with &lt;strong&gt;Auto Mode&lt;/strong&gt;, eliminating the need to manage node groups.&lt;/li&gt;
&lt;li&gt;Added &lt;strong&gt;random suffix strategy&lt;/strong&gt; for unique cluster naming.&lt;/li&gt;
&lt;li&gt;Provisioned add-ons (Ingress, Cert Manager, optional monitoring stack).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. CI/CD with GitHub Actions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Implemented &lt;strong&gt;change detection&lt;/strong&gt; to rebuild only modified services.&lt;/li&gt;
&lt;li&gt;Built and pushed Docker images to &lt;strong&gt;private ECR&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Updated Helm values with new image tags.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. GitOps with ArgoCD
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Configured &lt;strong&gt;one ArgoCD application per service&lt;/strong&gt;, avoiding a single umbrella chart.&lt;/li&gt;
&lt;li&gt;Enabled automatic sync with Git as the source of truth.&lt;/li&gt;
&lt;li&gt;Supported granular rollouts, service-level visibility, and independent rollbacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Security &amp;amp; Cost Controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Enforced &lt;strong&gt;private ECR with scanning + encryption&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Applied &lt;strong&gt;IAM least-privilege policies&lt;/strong&gt; for CI/CD and services.&lt;/li&gt;
&lt;li&gt;Used &lt;strong&gt;single NAT gateway&lt;/strong&gt; in dev and spot instance support for cost optimization.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Challenges and Solutions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node Group Complexity&lt;/strong&gt; → Solved with &lt;strong&gt;EKS Auto Mode&lt;/strong&gt; for hands-free compute scaling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helm Chart Updates Overwriting Infra Images&lt;/strong&gt; → Implemented targeted AWK script to update only app images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitOps Workflow Discipline&lt;/strong&gt; → Enforced branch protections and structured Git flows.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deployment Speed&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure: 15–20 mins&lt;/li&gt;
&lt;li&gt;Application changes: 3–5 mins&lt;/li&gt;
&lt;li&gt;Single service update: 1–2 mins&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Cost Efficiency&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto Mode prevented over-provisioning&lt;/li&gt;
&lt;li&gt;Change detection reduced unnecessary builds&lt;/li&gt;
&lt;li&gt;Shared NAT + optional spot instances lowered non-prod expenses&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Production Readiness&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTPS ingress with auto certificates&lt;/li&gt;
&lt;li&gt;RBAC/IAM security enforcement&lt;/li&gt;
&lt;li&gt;Optional monitoring and alerting enabled&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;EKS Auto Mode&lt;/strong&gt; dramatically reduces operational overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change detection in CI/CD&lt;/strong&gt; is a simple but powerful optimization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitOps&lt;/strong&gt; adds discipline and traceability but requires strict Git hygiene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security and observability&lt;/strong&gt; should be built in from day one, not after incidents.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  At a Glance
&lt;/h2&gt;

&lt;p&gt;By productionizing the &lt;strong&gt;AWS Retail Sample App&lt;/strong&gt;, this project demonstrates how a demo workload can be elevated into a &lt;strong&gt;secure, cost-optimized, and fully automated cloud-native platform&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The integration of Terraform, GitHub Actions, ArgoCD, and EKS Auto Mode provided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated infrastructure provisioning&lt;/li&gt;
&lt;li&gt;Intelligent CI/CD pipelines&lt;/li&gt;
&lt;li&gt;GitOps-driven deployments&lt;/li&gt;
&lt;li&gt;Security, monitoring, and cost controls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach can be adapted to any microservices-based workload seeking &lt;strong&gt;production readiness with efficient DevOps practices&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/HasanAshab/retail-store-devops" rel="noopener noreferrer"&gt;source code&lt;/a&gt; at GitHub for more details.&lt;/p&gt;




&lt;h2&gt;
  
  
  📬 Contact
&lt;/h2&gt;

&lt;p&gt;If you’d like to connect, collaborate, or discuss DevOps, feel free to reach out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://hasan-ashab.vercel.app/" rel="noopener noreferrer"&gt;hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/HasanAshab/" rel="noopener noreferrer"&gt;github.com/HasanAshab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://linkedin.com/in/hasan-ashab/" rel="noopener noreferrer"&gt;linkedin.com/in/hasan-ashab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
