<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: POTHURAJU JAYAKRISHNA YADAV</title>
    <description>The latest articles on DEV Community by POTHURAJU JAYAKRISHNA YADAV (@jayakrishnayadav24).</description>
    <link>https://dev.to/jayakrishnayadav24</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1212088%2Fda53393e-d025-4778-8689-039bac40c8cd.png</url>
      <title>DEV Community: POTHURAJU JAYAKRISHNA YADAV</title>
      <link>https://dev.to/jayakrishnayadav24</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jayakrishnayadav24"/>
    <language>en</language>
    <item>
      <title>How a Linux Kernel Upgrade Broke MongoDB on an Azure VM — And How We Fixed It</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Wed, 21 Oct 2026 23:55:04 +0000</pubDate>
      <link>https://dev.to/jayakrishnayadav24/how-a-linux-kernel-upgrade-broke-mongodb-on-an-azure-vm-and-how-we-fixed-it-28i8</link>
      <guid>https://dev.to/jayakrishnayadav24/how-a-linux-kernel-upgrade-broke-mongodb-on-an-azure-vm-and-how-we-fixed-it-28i8</guid>
      <description>&lt;p&gt;It was a normal morning. I woke up, grabbed my coffee, and before I'd even properly sat down my phone was already lighting up — alerts, messages, the whole thing. The app was down. Users couldn't connect, services were throwing errors, and nobody had the faintest idea why.&lt;/p&gt;

&lt;p&gt;The confusing part? Nobody had touched anything. No deploy the night before, no config change, no code push. I'd left everything green and healthy when I logged off, and I woke up to a dead server. That's the kind of outage that really gets under your skin, because there's nothing obvious to point at.&lt;/p&gt;

&lt;p&gt;So I SSH'd into the VM, half expecting the whole box to be gone, and instead found MongoDB sitting there flat-out refusing to start. That was the beginning of a morning I hadn't planned for — and the culprit turned out to be something none of us had on our radar: Azure had quietly upgraded the kernel overnight while we were all asleep.&lt;/p&gt;

&lt;p&gt;This one caught us completely off guard, which is exactly why I wanted to write it down. Here's the whole story, including the stuff we tried that didn't work, because honestly the dead ends are half the lesson.&lt;/p&gt;

&lt;h2&gt;
  
  
  The incident
&lt;/h2&gt;

&lt;p&gt;It started like most outages do — out of nowhere. One of our apps running on an Azure VM just stopped responding. Users couldn't connect, services were down, and the first thing we did was SSH in and check whether anything was even alive.&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;sudo &lt;/span&gt;systemctl status mongod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MongoDB was in a failed state — &lt;code&gt;Result: exit-code&lt;/code&gt;. And the weird part was &lt;em&gt;how fast&lt;/em&gt; it was dying. The service would try to start and then fall over within milliseconds, somewhere around 35-48ms. That's not a database struggling to load data. That's something rejecting it before it even gets going.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hunting for the cause
&lt;/h2&gt;

&lt;p&gt;Logs first, obviously.&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;sudo cat&lt;/span&gt; /var/log/mongodb/mongod.log | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useless. The log only showed a clean shutdown from two days ago. Nothing about why it was refusing to start now. So we went to the system journal instead and filtered for fatal messages:&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;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; mongod &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s2"&gt;"today"&lt;/span&gt; &lt;span class="nt"&gt;--no-pager&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'"s":"F"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there it was, the line that made the whole thing click:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MongoDB cannot start: Linux kernel versions 6.19 and newer has a known 
incompatibility with this version of MongoDB. 
See https://jira.mongodb.org/browse/SERVER-121912 for more information.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A kernel incompatibility. Not corruption, not permissions, not a config mistake. The kernel.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;Turns out Azure had auto-upgraded the Linux kernel on the VM overnight during a maintenance window. The box jumped to &lt;code&gt;7.0.0-1014-azure&lt;/code&gt;, which under the hood is based on Linux kernel 6.19+.&lt;/p&gt;

&lt;p&gt;MongoDB has a known problem with kernel 6.19 and up — something to do with the &lt;code&gt;rseq&lt;/code&gt; system call. And rather than risk running in a broken state, MongoDB just refuses to start at all when it detects that kernel. A hard safety block.&lt;/p&gt;

&lt;p&gt;We confirmed what we were dealing with:&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;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;span class="c"&gt;# Output: 7.0.0-1014-azure&lt;/span&gt;

&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="c"&gt;# Linux 7.0.0-1014-azure ... x86_64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the MongoDB version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mongod &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# db version v8.0.29&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So: MongoDB 8.0.29 sitting on a kernel it explicitly refuses to run on. That's the entire bug in one sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stuff we tried that didn't work
&lt;/h2&gt;

&lt;p&gt;Before we figured out the kernel was the issue, we burned time on the usual suspects. Leaving these in because this is the part where you learn what the problem &lt;em&gt;isn't&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First we blamed permissions.&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;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; mongodb:mongodb /var/lib/mongodb
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; mongodb:mongodb /var/log/mongodb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then we figured it was a stale lock file.&lt;/strong&gt; Classic MongoDB-won't-start cause.&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;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/lib/mongodb/mongod.lock
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /tmp/mongodb-27017.sock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then we thought maybe upgrading MongoDB would fix it.&lt;/strong&gt; If it's a known issue, surely a newer version handles it, right?&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;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; mongodb-org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MongoDB upgraded fine, straight to 8.0.32 — and failed with the exact same error. The kernel check is in 8.0.32 too. So no, upgrading doesn't help; the block is intentional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then we tried forcing it with &lt;code&gt;--fork&lt;/code&gt;.&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;sudo &lt;/span&gt;mongod &lt;span class="nt"&gt;--config&lt;/span&gt; /etc/mongod.conf &lt;span class="nt"&gt;--fork&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one actually worked for a bit, but only with the &lt;em&gt;old&lt;/em&gt; binary. Once we'd upgraded MongoDB, even &lt;code&gt;--fork&lt;/code&gt; ran into the kernel check and died. So that was a dead end too, just a slower one.&lt;/p&gt;

&lt;p&gt;At this point it was clear the problem wasn't anything on the MongoDB side at all. It was the kernel, full stop. Which meant the fix had to be at the kernel level.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real fix: boot into the older kernel
&lt;/h2&gt;

&lt;p&gt;Here's the thing that saved us — Azure doesn't throw away the old kernel when it upgrades. It leaves the previous ones installed on disk. The compatible kernel was still right there, just not the one being booted.&lt;/p&gt;

&lt;p&gt;We listed what was actually installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dpkg &lt;span class="nt"&gt;--list&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;linux-image-.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;-azure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rc  linux-image-6.17.0-1021-azure   # rc = removed, can't use
ii  linux-image-6.17.0-1022-azure   # ii = installed ✅
ii  linux-image-7.0.0-1014-azure    # ii = installed, broken ❌
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;6.17.0-1022-azure&lt;/code&gt; was fully installed (&lt;code&gt;ii&lt;/code&gt;), and since 6.17 is below 6.19, it was compatible with MongoDB. We just needed to tell the server to boot into it instead of the new broken one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick word on GRUB
&lt;/h2&gt;

&lt;p&gt;If you've never had a reason to touch this, GRUB (the Grand Unified Bootloader) is the thing that runs the instant your server powers on, before the OS loads. It picks which kernel to boot. Think of it as the startup selector for your Linux box.&lt;/p&gt;

&lt;p&gt;By default it grabs the newest kernel — which was exactly our problem, because the newest one was the broken &lt;code&gt;7.0.0-1014-azure&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The config lives at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;/&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;default&lt;/span&gt;/&lt;span class="n"&gt;grub&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pointing GRUB at the good kernel
&lt;/h2&gt;

&lt;p&gt;We edited the GRUB config to default to the older compatible kernel, and gave ourselves a boot menu timeout while we were at it:&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;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/GRUB_DEFAULT=0/GRUB_DEFAULT="Advanced options for Ubuntu&amp;gt;Ubuntu, with Linux 6.17.0-1022-azure"/'&lt;/span&gt; /etc/default/grub
&lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/GRUB_TIMEOUT=0/GRUB_TIMEOUT=5/'&lt;/span&gt; /etc/default/grub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What those two changes actually do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GRUB_DEFAULT=0&lt;/code&gt; just boots whatever the first (newest) kernel is. Changing it to the full &lt;code&gt;"Advanced options for Ubuntu&amp;gt;Ubuntu, with Linux 6.17.0-1022-azure"&lt;/code&gt; string tells GRUB to boot that specific kernel by name instead.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GRUB_TIMEOUT=0&lt;/code&gt; means GRUB shows no menu and boots instantly. Bumping it to &lt;code&gt;5&lt;/code&gt; gives you a five-second window to manually pick a kernel if you ever need to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then — and this part is important — you have to regenerate the actual boot config and reboot:&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;sudo &lt;/span&gt;update-grub
&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't skip &lt;code&gt;update-grub&lt;/code&gt;. Editing &lt;code&gt;/etc/default/grub&lt;/code&gt; on its own does nothing. &lt;code&gt;update-grub&lt;/code&gt; reads that file and regenerates the real boot config at &lt;code&gt;/boot/grub/grub.cfg&lt;/code&gt;. If you forget it, you'll reboot straight back into the broken kernel and wonder why nothing changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  After the reboot
&lt;/h2&gt;

&lt;p&gt;Server came back up, first thing we checked was the kernel:&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;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;span class="c"&gt;# Output: 6.17.0-1022-azure ✅&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we cleaned up the leftover junk from all our failed attempts:&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;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/run/mongodb/mongod.pid
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /tmp/mongodb-27017.sock
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; mongodb:mongodb /var/lib/mongodb
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; mongodb:mongodb /var/log/mongodb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And started MongoDB:&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;sudo &lt;/span&gt;systemctl start mongod
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status mongod
&lt;span class="c"&gt;# Active: active (running) ✅&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running. The app was back online immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making sure it doesn't happen again
&lt;/h2&gt;

&lt;p&gt;Getting it running was great, but we weren't out of the woods. Azure could just upgrade the kernel again on the next maintenance window and put us right back where we started. So we pinned the kernel packages to stop that:&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;sudo &lt;/span&gt;apt-mark hold linux-azure
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-mark hold linux-image-azure
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-mark hold linux-image-7.0.0-1014-azure
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-mark hold linux-image-6.17.0-1022-azure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What &lt;code&gt;apt-mark hold&lt;/code&gt; does is tell the package manager to never automatically upgrade or remove those packages. The important ones are &lt;code&gt;linux-azure&lt;/code&gt; and &lt;code&gt;linux-image-azure&lt;/code&gt; — those are meta-packages that always point at the latest kernel, so holding them stops any new kernel from sneaking in automatically. This keeps us parked on &lt;code&gt;6.17.0-1022-azure&lt;/code&gt; until MongoDB ships an official fix.&lt;/p&gt;

&lt;p&gt;Double-check what's held with:&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;sudo &lt;/span&gt;apt-mark showhold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I took away from this
&lt;/h2&gt;

&lt;p&gt;A few things worth remembering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud providers — Azure here, but it's not just Azure — will upgrade your kernel during maintenance windows without asking. Something can break overnight with zero changes on your end.&lt;/li&gt;
&lt;li&gt;MongoDB 8.x has a hard block against Linux kernel 6.19+ because of the &lt;code&gt;rseq&lt;/code&gt; incompatibility (SERVER-121912). It won't start, and that's on purpose.&lt;/li&gt;
&lt;li&gt;A Linux box can have several kernels installed at once. You just have to tell GRUB which one to use.&lt;/li&gt;
&lt;li&gt;GRUB is the bootloader. Edit &lt;code&gt;/etc/default/grub&lt;/code&gt;, run &lt;code&gt;update-grub&lt;/code&gt;, reboot — that's how you control the default kernel.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apt-mark hold&lt;/code&gt; is genuinely useful when you need to freeze a package and keep auto-upgrades from undoing your fix.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick reference for next time
&lt;/h2&gt;

&lt;p&gt;If this bites us (or you) again on any VM, here's the short version:&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;# 1. Confirm what's broken&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; mongod &lt;span class="nt"&gt;--no-pager&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'"s":"F"'&lt;/span&gt;

&lt;span class="c"&gt;# 2. List installed kernels&lt;/span&gt;
dpkg &lt;span class="nt"&gt;--list&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;linux-image-.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;-azure&lt;/span&gt;

&lt;span class="c"&gt;# 3. Pick the highest kernel below 6.19 that shows "ii"&lt;/span&gt;

&lt;span class="c"&gt;# 4. Set it as the default and reboot&lt;/span&gt;
&lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/GRUB_DEFAULT=.*/GRUB_DEFAULT="Advanced options for Ubuntu&amp;gt;Ubuntu, with Linux &amp;lt;your-kernel&amp;gt;"/'&lt;/span&gt; /etc/default/grub
&lt;span class="nb"&gt;sudo &lt;/span&gt;update-grub
&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot

&lt;span class="c"&gt;# 5. After reboot, start Mongo&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start mongod
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status mongod

&lt;span class="c"&gt;# 6. Freeze the kernels&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-mark hold linux-azure linux-image-azure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;What looked like a mysterious outage was really just a kernel compatibility issue triggered by an automatic cloud update. No code changes, no reinstall, no data loss — the whole thing came down to understanding how Linux kernel management and GRUB fit together.&lt;/p&gt;

&lt;p&gt;The real lesson for me: know what kernels are installed on your VMs, and remember your cloud provider can swap them out from under you whenever it feels like it. A quick &lt;code&gt;dpkg --list | grep linux-image&lt;/code&gt; and a basic grasp of GRUB would've saved us a good chunk of the time we spent chasing permissions and lock files.&lt;/p&gt;

&lt;p&gt;If you've been burned by an auto-upgrade like this, I'd like to hear how it showed up for you. These silent infrastructure changes are sneaky, and the more stories floating around, the faster the next person figures it out.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>azure</category>
      <category>mongodb</category>
      <category>linux</category>
    </item>
    <item>
      <title>Stop Googling Command Syntax — Your Terminal Already Knows the Answer</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Sun, 18 Oct 2026 08:55:52 +0000</pubDate>
      <link>https://dev.to/jayakrishnayadav24/stop-googling-command-syntax-your-terminal-already-knows-the-answer-k7n</link>
      <guid>https://dev.to/jayakrishnayadav24/stop-googling-command-syntax-your-terminal-already-knows-the-answer-k7n</guid>
      <description>&lt;p&gt;I'll admit something a little embarrassing. For years, every time I forgot the exact flags for &lt;code&gt;tar&lt;/code&gt;, or couldn't remember whether it was &lt;code&gt;-C&lt;/code&gt; or &lt;code&gt;-c&lt;/code&gt; to change directory, my reflex was to alt-tab to a browser and search for it. Same with Kubernetes — I'd go hunting through the docs website every time I needed to remember what fields go into a Pod spec.&lt;/p&gt;

&lt;p&gt;At some point it clicked that this was ridiculous. The machine right in front of me already knows all of this. It ships with the answers built in. I just wasn't asking it.&lt;/p&gt;

&lt;p&gt;So this post is about the handful of tools that killed my habit of Googling command syntax: &lt;code&gt;tldr&lt;/code&gt;, &lt;code&gt;apropos&lt;/code&gt;, and &lt;code&gt;info&lt;/code&gt; for everyday Linux commands, and &lt;code&gt;kubectl explain&lt;/code&gt; for writing Kubernetes manifests without guessing. None of these are new or fancy. They're just underused, and once they're part of your muscle memory you stop breaking your flow to go look things up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with &lt;code&gt;man&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Everyone knows &lt;code&gt;man&lt;/code&gt;. You type &lt;code&gt;man tar&lt;/code&gt; and you get... the entire manual. Every flag, every edge case, every historical note, all in one dense scroll. It's thorough, and that's exactly the problem. When I've forgotten &lt;em&gt;how to create a gzipped archive&lt;/em&gt;, I don't want a 600-line reference. I want the one line that does the thing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;man&lt;/code&gt; is a reference manual. Most of the time what you actually need is an example or a reminder. That's where the other tools come in.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;tldr&lt;/code&gt; — examples, not essays
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tldr&lt;/code&gt; is the one I reach for most. The name is literally "too long; didn't read," and that's the whole philosophy. Instead of the full manual, it gives you a short list of the most common real-world uses of a command, with copy-pasteable examples.&lt;/p&gt;

&lt;p&gt;Here's &lt;code&gt;tldr tar&lt;/code&gt; on my machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tar
Archiving utility. Often combined with a compression method, such as gzip or bzip2.

 - [c]reate an archive and write it to a [f]ile:
   tar cf path/to/target.tar path/to/file1 path/to/file2 ...

 - [c]reate a g[z]ipped archive and write it to a [f]ile:
   tar czf path/to/target.tar.gz path/to/file1 path/to/file2 ...

 - E[x]tract a (compressed) archive [f]ile into the current directory [v]erbosely:
   tar xvf path/to/source.tar.ext

 - E[x]tract a (compressed) archive [f]ile into the target directory:
   tar xf path/to/source.tar.ext -C path/to/directory

 - Lis[t] the contents of a tar [f]ile [v]erbosely:
   tar tvf path/to/source.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at how readable that is. See the little brackets — &lt;code&gt;[c]reate&lt;/code&gt;, &lt;code&gt;g[z]ipped&lt;/code&gt;, &lt;code&gt;[f]ile&lt;/code&gt;? That's a genuinely clever touch. The letters in brackets map directly to the flags, so &lt;code&gt;czf&lt;/code&gt; suddenly makes sense: &lt;strong&gt;c&lt;/strong&gt;reate, g*&lt;em&gt;z&lt;/em&gt;&lt;em&gt;ipped, **f&lt;/em&gt;*ile. You stop memorizing flags and start understanding them.&lt;/p&gt;

&lt;p&gt;That tar example alone covers maybe 95% of what anyone ever does with tar. No scrolling, no essay, just the thing you came for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting it
&lt;/h3&gt;

&lt;p&gt;It's not always installed by default, but it's one package away:&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;# Debian / Ubuntu&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;tldr

&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;tldr

&lt;span class="c"&gt;# Via npm (the original client)&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; tldr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run might fetch the pages, and you can refresh the local cache whenever you want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tldr &lt;span class="nt"&gt;--update&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it for literally anything you half-remember — &lt;code&gt;tldr ssh&lt;/code&gt;, &lt;code&gt;tldr find&lt;/code&gt;, &lt;code&gt;tldr git rebase&lt;/code&gt;, &lt;code&gt;tldr ffmpeg&lt;/code&gt;. The ffmpeg one in particular has saved me more times than I can count, because nobody on earth remembers ffmpeg syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;apropos&lt;/code&gt; — when you don't even know the command's name
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tldr&lt;/code&gt; is great when you know &lt;em&gt;which&lt;/em&gt; command you want. But sometimes you don't. You know &lt;em&gt;what you want to do&lt;/em&gt;, you just can't remember what it's called.&lt;/p&gt;

&lt;p&gt;That's &lt;code&gt;apropos&lt;/code&gt;. It searches the names and descriptions of all the man pages on your system for a keyword and shows you everything that matches. It's basically "search the manual by topic."&lt;/p&gt;

&lt;h3&gt;
  
  
  First, a gotcha that got me
&lt;/h3&gt;

&lt;p&gt;The very first time I tried this, I got nothing but errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ apropos compress
compress: nothing appropriate.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that happens to you, don't panic — &lt;code&gt;apropos&lt;/code&gt; isn't broken and the commands aren't missing. It relies on a prebuilt search index, and on a fresh or minimal install (this bites people on WSL and slim container images especially) that index just doesn't exist yet. Build it once:&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;sudo &lt;/span&gt;mandb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see it churn through your man pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Processing manual pages under /usr/local/man...
7612 manual pages were added.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;apropos&lt;/code&gt; has something to search, and it works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Now it actually does something
&lt;/h3&gt;

&lt;p&gt;With the index built, &lt;code&gt;apropos compress&lt;/code&gt; returns a wall of genuinely useful results. Here's a trimmed slice of what came back on my machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bunzip2 (1)     - a block-sorting file compressor, v1.0.8
bzip2 (1)       - a block-sorting file compressor, v1.0.8
gunzip (1)      - compress or expand files
gzip (1)        - compress or expand files
lz4 (1)         - lz4, unlz4, lz4cat - Compress or decompress .lz4 files
lzma (1)        - Compress or decompress .xz and .lzma files
pigz (1)        - compress or expand files
unzip (1)       - list, test and extract compressed files in a ZIP archive
xz (1)          - Compress or decompress .xz and .lzma files
zcat (1)        - compress or expand files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You went in knowing only the word "compress" and came out with every compression tool on the box. From there, &lt;code&gt;tldr gzip&lt;/code&gt; or &lt;code&gt;man xz&lt;/code&gt; gets you the actual usage.&lt;/p&gt;

&lt;p&gt;The real output is actually much longer than that — &lt;code&gt;apropos&lt;/code&gt; matches the keyword anywhere in the description, so you also get things like &lt;code&gt;zgrep&lt;/code&gt;, &lt;code&gt;zless&lt;/code&gt;, library functions, and perl modules. Which brings us to the useful trick for cutting the noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Narrowing it down
&lt;/h3&gt;

&lt;p&gt;Man pages are split into numbered sections, and section 1 is user commands. So if you only care about actual commands you can run, scope the search to section 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apropos &lt;span class="nt"&gt;-s&lt;/span&gt; 1 copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp (1)                  - copy files and directories
cpio (1)                - copy files to and from archives
dd (1)                  - convert and copy a file
docker-cp (1)           - Copy files/folders between a container and the local filesystem
install (1)             - copy files and set attributes
objcopy (1)             - copy and translate object files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's much cleaner than the unfiltered version, which also drags in library calls and config file formats you don't care about when you're just trying to copy a file.&lt;/p&gt;

&lt;p&gt;Think of the pairing like this: &lt;code&gt;apropos&lt;/code&gt; finds the &lt;em&gt;name&lt;/em&gt; of the command, &lt;code&gt;tldr&lt;/code&gt; tells you &lt;em&gt;how to use it&lt;/em&gt;. Together they cover the full "I forgot" spectrum.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;info&lt;/code&gt; — the deep dive when you actually need it
&lt;/h2&gt;

&lt;p&gt;Sometimes you &lt;em&gt;do&lt;/em&gt; need the full story — not an example, not a one-liner, but the real, complete documentation with all the context. That's where &lt;code&gt;info&lt;/code&gt; comes in.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;info&lt;/code&gt; reads GNU Info documents, which are often far more detailed and better structured than the equivalent man page. For GNU tools especially (&lt;code&gt;coreutils&lt;/code&gt;, &lt;code&gt;gcc&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;tar&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;), the info manual is the authoritative, book-length version with chapters, sections, and cross-links you can navigate like a hyperlinked document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;info coreutils
info &lt;span class="nb"&gt;tar
&lt;/span&gt;info &lt;span class="nb"&gt;grep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few navigation basics, because &lt;code&gt;info&lt;/code&gt; trips people up the first time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Arrow keys scroll.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;n&lt;/code&gt; goes to the next node (section), &lt;code&gt;p&lt;/code&gt; to the previous one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;u&lt;/code&gt; goes up a level.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Enter&lt;/code&gt; on a highlighted link follows it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;q&lt;/code&gt; quits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honestly I don't live in &lt;code&gt;info&lt;/code&gt; the way I live in &lt;code&gt;tldr&lt;/code&gt;. But when I genuinely need to understand &lt;em&gt;why&lt;/em&gt; a tool behaves a certain way, or I'm chasing some obscure option, the info manual is usually where the real explanation lives.&lt;/p&gt;

&lt;p&gt;So the mental model for the three is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tldr&lt;/code&gt;&lt;/strong&gt; — "just remind me how to do the common thing."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;apropos&lt;/code&gt;&lt;/strong&gt; — "I forgot what the command is even called."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;info&lt;/code&gt;&lt;/strong&gt; — "I need to actually understand this properly."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Now the Kubernetes version of the same problem: &lt;code&gt;kubectl explain&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Here's where this gets really useful, because the exact same frustration shows up when writing Kubernetes manifests. You're staring at a blank YAML file trying to remember: is it &lt;code&gt;containers&lt;/code&gt; or &lt;code&gt;container&lt;/code&gt;? Does &lt;code&gt;image&lt;/code&gt; go under the pod spec or inside the container? What fields does a probe even take?&lt;/p&gt;

&lt;p&gt;Most people's instinct is to go to the Kubernetes docs website. But &lt;code&gt;kubectl&lt;/code&gt; has the entire API schema baked in, and &lt;code&gt;kubectl explain&lt;/code&gt; is the self-documentation tool that mirrors exactly what &lt;code&gt;tldr&lt;/code&gt;/&lt;code&gt;man&lt;/code&gt; do for shell commands — except here it's generating the structure of your manifest for you.&lt;/p&gt;

&lt;p&gt;The best part: it reads the schema straight from your cluster, so it matches &lt;em&gt;your&lt;/em&gt; Kubernetes version. No guessing whether a field exists in your version or not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start at the top
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl explain pod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;KIND&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;v1&lt;/span&gt;

&lt;span class="na"&gt;DESCRIPTION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;Pod is a collection of containers that can run on a host.&lt;/span&gt;

&lt;span class="na"&gt;FIELDS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;apiVersion    &amp;lt;string&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;kind  &amp;lt;string&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;metadata  &amp;lt;ObjectMeta&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;spec  &amp;lt;PodSpec&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;status    &amp;lt;PodStatus&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right away this tells you the four top-level keys of any Pod manifest: &lt;code&gt;apiVersion&lt;/code&gt;, &lt;code&gt;kind&lt;/code&gt;, &lt;code&gt;metadata&lt;/code&gt;, and &lt;code&gt;spec&lt;/code&gt;. That's your skeleton:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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;Pod&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;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Drill down with dot notation
&lt;/h3&gt;

&lt;p&gt;Now, what goes inside &lt;code&gt;spec&lt;/code&gt;? Just ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl explain pod.spec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;KIND&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;v1&lt;/span&gt;

&lt;span class="na"&gt;FIELD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spec &amp;lt;PodSpec&amp;gt;&lt;/span&gt;

&lt;span class="na"&gt;DESCRIPTION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;Specification of the desired behavior of the pod.&lt;/span&gt;

&lt;span class="na"&gt;FIELDS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;activeDeadlineSeconds &amp;lt;integer&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;affinity  &amp;lt;Affinity&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;automountServiceAccountToken  &amp;lt;boolean&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;containers    &amp;lt;[]Container&amp;gt; -required-&lt;/span&gt;
    &lt;span class="s"&gt;List of containers belonging to the pod. There must be at least one&lt;/span&gt;
    &lt;span class="s"&gt;container in a Pod.&lt;/span&gt;
  &lt;span class="s"&gt;dnsPolicy &amp;lt;string&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;enum&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterFirst, ClusterFirstWithHostNet, Default, None&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This output is doing a lot of work for you. Look at the details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;containers  &amp;lt;[]Container&amp;gt; -required-&lt;/code&gt; — the &lt;code&gt;[]&lt;/code&gt; means it's a &lt;strong&gt;list&lt;/strong&gt;, and &lt;code&gt;-required-&lt;/code&gt; means you can't skip it. So you know &lt;code&gt;containers&lt;/code&gt; is a YAML list and it's mandatory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dnsPolicy &amp;lt;string&amp;gt;&lt;/code&gt; even shows you the valid &lt;code&gt;enum&lt;/code&gt; values, so you don't have to guess what strings are allowed.&lt;/li&gt;
&lt;li&gt;Each field shows its &lt;strong&gt;type&lt;/strong&gt;, which tells you how to shape the YAML — a &lt;code&gt;&amp;lt;string&amp;gt;&lt;/code&gt; is a plain value, an &lt;code&gt;&amp;lt;Affinity&amp;gt;&lt;/code&gt; is a nested object, a &lt;code&gt;&amp;lt;[]Container&amp;gt;&lt;/code&gt; is a list of objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Keep going until you hit plain values
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;containers&lt;/code&gt; is a &lt;code&gt;[]Container&lt;/code&gt;, so let's see what a container looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl explain pod.spec.containers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;KIND&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;v1&lt;/span&gt;

&lt;span class="na"&gt;FIELD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers &amp;lt;[]Container&amp;gt;&lt;/span&gt;

&lt;span class="na"&gt;DESCRIPTION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;A single application container that you want to run within a pod.&lt;/span&gt;

&lt;span class="na"&gt;FIELDS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;args  &amp;lt;[]string&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;command   &amp;lt;[]string&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;env   &amp;lt;[]EnvVar&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;image &amp;lt;string&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;imagePullPolicy   &amp;lt;string&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;name  &amp;lt;string&amp;gt; -required-&lt;/span&gt;
  &lt;span class="s"&gt;ports &amp;lt;[]ContainerPort&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;resources &amp;lt;ResourceRequirements&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;volumeMounts  &amp;lt;[]VolumeMount&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can see that a container needs a &lt;code&gt;name&lt;/code&gt; (required), takes an &lt;code&gt;image&lt;/code&gt;, and that &lt;code&gt;ports&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt;, and &lt;code&gt;volumeMounts&lt;/code&gt; are all lists of their own object types. If you want to know what goes in a port, you just keep drilling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl explain pod.spec.containers.ports
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You follow the types down the tree until everything bottoms out in &lt;code&gt;&amp;lt;string&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;integer&amp;gt;&lt;/code&gt;, or &lt;code&gt;&amp;lt;boolean&amp;gt;&lt;/code&gt; — plain scalar values you can actually type. That's your signal that you've reached the leaves and the manifest is complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting it together into a real manifest
&lt;/h3&gt;

&lt;p&gt;Walking the schema with &lt;code&gt;kubectl explain&lt;/code&gt; — &lt;code&gt;pod&lt;/code&gt; → &lt;code&gt;pod.spec&lt;/code&gt; → &lt;code&gt;pod.spec.containers&lt;/code&gt; → &lt;code&gt;pod.spec.containers.ports&lt;/code&gt; — gives you everything you need to assemble this by hand, confidently, without the docs website:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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;Pod&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;nginx-pod&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="c1"&gt;# []Container, required&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;nginx&lt;/span&gt;             &lt;span class="c1"&gt;# string, required&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;nginx:1.27&lt;/span&gt;       &lt;span class="c1"&gt;# string&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                  &lt;span class="c1"&gt;# []ContainerPort&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;span class="c1"&gt;# integer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every single field in that file maps back to something &lt;code&gt;kubectl explain&lt;/code&gt; told us: &lt;code&gt;containers&lt;/code&gt; is a required list, each item needs a &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt; is a string, &lt;code&gt;ports&lt;/code&gt; is a list, and &lt;code&gt;containerPort&lt;/code&gt; is an integer. Nothing memorized, nothing guessed.&lt;/p&gt;

&lt;h3&gt;
  
  
  A couple of handy variations
&lt;/h3&gt;

&lt;p&gt;Dump the &lt;em&gt;entire&lt;/em&gt; nested tree in one shot instead of drilling level by level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl explain pod &lt;span class="nt"&gt;--recursive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a firehose, but it's great when you want to see the full shape of a resource at a glance.&lt;/p&gt;

&lt;p&gt;And this works for &lt;em&gt;every&lt;/em&gt; resource type, not just pods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl explain deployment.spec.template.spec.containers
kubectl explain service.spec
kubectl explain ingress.spec.rules
kubectl explain configmap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;deployment.spec.template.spec&lt;/code&gt; path is the one I use constantly, because that's where the pod template lives inside a Deployment and it's easy to lose track of how deep it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than it looks
&lt;/h2&gt;

&lt;p&gt;None of these tools are impressive on their own. But together they change a habit, and the habit is what matters.&lt;/p&gt;

&lt;p&gt;When you Google command syntax, three things happen: you break your focus by leaving the terminal, you risk landing on a Stack Overflow answer from eight years ago that's subtly wrong for your version, and you never actually &lt;em&gt;learn&lt;/em&gt; the tool — you just copy and move on. When you ask your own machine instead, the answer is version-accurate (especially with &lt;code&gt;kubectl explain&lt;/code&gt;, which reads your real cluster schema), it's instant, and you stay in flow.&lt;/p&gt;

&lt;p&gt;The bracket notation in &lt;code&gt;tldr&lt;/code&gt; and the type annotations in &lt;code&gt;kubectl explain&lt;/code&gt; also quietly teach you &lt;em&gt;why&lt;/em&gt; the syntax is the way it is, so over time you need them less. That's the real win. The goal isn't to depend on these forever — it's to stop context-switching to a browser for things your terminal can answer in half a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Forgot how to use a command you know the name of&lt;/span&gt;
tldr &amp;lt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;# Forgot the command's name, know what it does&lt;/span&gt;
apropos &amp;lt;keyword&amp;gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mandb              &lt;span class="c"&gt;# run once if apropos finds nothing&lt;/span&gt;

&lt;span class="c"&gt;# Need the full, detailed GNU manual&lt;/span&gt;
info &amp;lt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;# Building a Kubernetes manifest, forgot the fields&lt;/span&gt;
kubectl explain &amp;lt;resource&amp;gt;
kubectl explain &amp;lt;resource&amp;gt;.spec
kubectl explain &amp;lt;resource&amp;gt;.spec.&amp;lt;field&amp;gt;
kubectl explain &amp;lt;resource&amp;gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I stopped Googling &lt;code&gt;tar&lt;/code&gt; flags and Pod spec fields the day I realized the answers were already sitting on my machine, offline, version-correct, one command away. &lt;code&gt;tldr&lt;/code&gt; for quick examples, &lt;code&gt;apropos&lt;/code&gt; to find a command you've forgotten the name of, &lt;code&gt;info&lt;/code&gt; for the deep dive, and &lt;code&gt;kubectl explain&lt;/code&gt; to build manifests field by field straight from the API schema.&lt;/p&gt;

&lt;p&gt;Next time you catch yourself reaching for the browser to look up syntax, try asking your terminal first. It knows. It's been waiting the whole time.&lt;/p&gt;

&lt;p&gt;If you've got a self-documentation trick I didn't mention here, drop it in the comments — I'm always happy to retire another browser tab.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>kubernetes</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a SIP Calling Lab on AWS: Talking Between My Phone and Laptop Through the Cloud</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Thu, 15 Oct 2026 04:38:24 +0000</pubDate>
      <link>https://dev.to/jayakrishnayadav24/building-a-sip-calling-lab-on-aws-talking-between-my-phone-and-laptop-through-the-cloud-ojn</link>
      <guid>https://dev.to/jayakrishnayadav24/building-a-sip-calling-lab-on-aws-talking-between-my-phone-and-laptop-through-the-cloud-ojn</guid>
      <description>&lt;p&gt;I wanted to actually understand SIP, not just read about it. So I set myself a hands-on goal: run my own SIP server in the cloud and make a real voice call between my phone and my laptop, with the audio flowing through that server. This post is the full story, including the dead ends I hit, because the mistakes taught me more than the happy path did.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What you'll have at the end:&lt;/strong&gt; one small AWS EC2 instance running Kamailio (the SIP server) and rtpengine (the audio relay), two softphones registered to it, and a working two-way call.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What SIP actually is (the mental model I needed)
&lt;/h2&gt;

&lt;p&gt;Before touching anything, here's the model that made it click for me.&lt;/p&gt;

&lt;p&gt;SIP (Session Initiation Protocol) is just the "who's calling whom and are they picking up" part of a call. It's signaling. It does &lt;strong&gt;not&lt;/strong&gt; carry your voice. Think of it like the ringing and the "hello?" negotiation.&lt;/p&gt;

&lt;p&gt;The actual audio travels over a separate protocol called &lt;strong&gt;RTP&lt;/strong&gt; on a different set of ports. This split is the single most important thing to understand, because it's the source of 90% of the problems people hit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SIP&lt;/strong&gt; → port 5060, sets up and tears down the call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RTP&lt;/strong&gt; → a range of ports (I used 10000–20000), carries the voice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are two roles in the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;server&lt;/strong&gt; (the registrar/proxy) that knows where everyone is and routes calls. I used &lt;strong&gt;Kamailio&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clients&lt;/strong&gt; (softphones) that have a mic and speaker. These live on devices with audio hardware — my phone and my laptop. &lt;em&gt;Not&lt;/em&gt; on the server. A cloud server has no microphone, so a softphone running on it couldn't capture my voice. That realization saved me from a wrong turn early on.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   My phone                   EC2 (one instance)              My laptop
 [softphone 1001] &amp;lt;--SIP--&amp;gt; [Kamailio + rtpengine] &amp;lt;--SIP--&amp;gt; [softphone 1002]
      mic/spk     &amp;lt;------ RTP audio relayed through EC2 ------&amp;gt;   mic/spk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What is what: every piece in plain English
&lt;/h2&gt;

&lt;p&gt;Before the step-by-step, let me explain each piece the way I wish someone had explained it to me. The big picture first: &lt;strong&gt;I built a tiny phone company in the cloud.&lt;/strong&gt; When you make a normal call, a phone carrier's servers connect you to the other person. This lab is my own miniature version of that carrier, running on a single AWS server, connecting my phone to my laptop.&lt;/p&gt;

&lt;h3&gt;
  
  
  The two halves of every call
&lt;/h3&gt;

&lt;p&gt;This is the one concept everything else hangs on. A phone call is really &lt;strong&gt;two separate conversations happening at the same time&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Signaling — "the arranging."&lt;/strong&gt; Who's calling, who's being called, is it ringing, did they answer, did they hang up. It's the paperwork of the call. The protocol is &lt;strong&gt;SIP&lt;/strong&gt;, and it rides on port &lt;strong&gt;5060&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media — "the actual voice."&lt;/strong&gt; The real sound of your voice streaming to the other person. The protocol is &lt;strong&gt;RTP&lt;/strong&gt;, and it rides on a range of ports (I used &lt;strong&gt;10000–20000&lt;/strong&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why split them? Because they're different jobs. Signaling is a few small text messages ("ring this person"). Media is a continuous stream of audio. Keep this one line in your head and most problems make sense: &lt;strong&gt;SIP sets up the call, RTP carries the voice.&lt;/strong&gt; The infamous "it rings but I hear nothing" bug is almost always RTP ports not being open — because people forget the voice travels separately from the signaling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kamailio — the switchboard operator
&lt;/h3&gt;

&lt;p&gt;Kamailio is the &lt;strong&gt;SIP server&lt;/strong&gt;, and its whole job is signaling. It does two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Registrar:&lt;/strong&gt; it keeps a live list of who's online and where. When my phone says "Hi, I'm 1001, here's my address," Kamailio writes it down. That's &lt;em&gt;registration&lt;/em&gt; — the green checkmark on the phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy:&lt;/strong&gt; when 1001 calls 1002, Kamailio looks up where 1002 is and forwards the call. It's the operator patching two lines together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Crucially, Kamailio never touches your voice. It only handles the "who's calling whom" messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  rtpengine — the audio relay in the middle
&lt;/h3&gt;

&lt;p&gt;rtpengine handles the &lt;strong&gt;media (RTP)&lt;/strong&gt; — the actual voice. Why do you need it on a cloud server? Because my phone and laptop are on different networks, both behind NAT (home router, mobile carrier). They can't send audio straight to each other because neither can see the other's real address. So rtpengine sits in the middle on the public server and &lt;strong&gt;relays&lt;/strong&gt; the audio: phone → rtpengine → laptop, and back. Both sides only ever have to reach the server, which has a public address everyone can see.&lt;/p&gt;

&lt;h3&gt;
  
  
  The database (DBTEXT) — the user list
&lt;/h3&gt;

&lt;p&gt;This stores the accounts: &lt;code&gt;1001&lt;/code&gt; and its password, &lt;code&gt;1002&lt;/code&gt; and its password. When a phone tries to register, Kamailio checks this list to confirm the password. I used &lt;strong&gt;DBTEXT&lt;/strong&gt;, which is just plain text files on disk — the simplest option, no separate database server to run.&lt;/p&gt;

&lt;h3&gt;
  
  
  The softphones — the actual phones
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;softphone&lt;/strong&gt; is a phone app with a mic and speaker. I used &lt;strong&gt;Zoiper&lt;/strong&gt; on my phone (extension &lt;strong&gt;1001&lt;/strong&gt;) and &lt;strong&gt;MicroSIP&lt;/strong&gt; on my laptop (extension &lt;strong&gt;1002&lt;/strong&gt;). They register to Kamailio and place the calls. This is exactly why they live on my devices and not on the server — the server has no microphone.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AWS pieces
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EC2 instance&lt;/strong&gt; — just a rented Linux computer in Amazon's datacenter. It runs Kamailio + rtpengine. Nothing magic, a normal Ubuntu box I reach over SSH.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public IP vs Private IP&lt;/strong&gt; — the instance has two addresses: a &lt;em&gt;private&lt;/em&gt; one (&lt;code&gt;172.31.x&lt;/code&gt;) it knows itself by inside AWS, and a &lt;em&gt;public&lt;/em&gt; one (&lt;code&gt;52.91.x&lt;/code&gt;) the outside world uses to reach it. This split is the root of the NAT problem: the server thinks it's &lt;code&gt;172.31.x&lt;/code&gt;, but phones must talk to &lt;code&gt;52.91.x&lt;/code&gt;. If the server naively tells a phone "send audio to 172.31.x," the phone fails because that address is meaningless on the internet. That's why I had to explicitly tell both Kamailio and rtpengine to &lt;em&gt;advertise the public IP&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Group&lt;/strong&gt; — AWS blocks all incoming traffic by default. The Security Group is where I poked holes for the ports I needed: SIP (5060) and the RTP range. Forget the RTP ports and calls connect but have no sound — the classic trap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How one call actually flows
&lt;/h3&gt;

&lt;p&gt;Putting it together, here's what happens when I dial 1002 from 1001:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Both phones register.&lt;/strong&gt; Zoiper (1001) and MicroSIP (1002) each tell Kamailio "I'm online, here's my address." Kamailio checks their passwords against the user list and records where they are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I dial.&lt;/strong&gt; Phone 1001 sends an &lt;strong&gt;INVITE&lt;/strong&gt; ("I want to call 1002") to Kamailio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kamailio routes it.&lt;/strong&gt; It looks up where 1002 is, forwards the INVITE, and tells rtpengine to set up an audio relay for this call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The laptop rings&lt;/strong&gt; (&lt;strong&gt;180 Ringing&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I answer.&lt;/strong&gt; MicroSIP sends &lt;strong&gt;200 OK&lt;/strong&gt; — the call is connected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice flows.&lt;/strong&gt; RTP audio streams phone → rtpengine → laptop and back. We're talking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I hang up.&lt;/strong&gt; One side sends &lt;strong&gt;BYE&lt;/strong&gt;, the other replies &lt;strong&gt;200 OK&lt;/strong&gt;, and rtpengine tears down the relay.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That exact sequence is what shows up in the Kamailio log later — the &lt;code&gt;INVITE ... 200 OK&lt;/code&gt; and &lt;code&gt;BYE ... 200 OK&lt;/code&gt; lines are steps 2–7 happening for real.&lt;/p&gt;

&lt;h3&gt;
  
  
  One-line cheat sheet
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;Plain-English job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SIP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the language for arranging calls (ring, answer, hang up)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RTP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the stream that carries your actual voice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kamailio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the switchboard — tracks who's online, connects callers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;rtpengine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the audio relay in the middle; fixes the NAT problem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DBTEXT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the file holding users and passwords&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Softphone (Zoiper / MicroSIP)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the phone app with a mic and speaker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EC2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the rented cloud computer running it all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Public / Private IP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;two addresses for one server; the NAT headache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security Group&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the firewall; I opened the SIP + RTP ports&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why I moved from WSL to AWS
&lt;/h2&gt;

&lt;p&gt;My first attempt was on my Windows laptop using WSL (Ubuntu). It technically works, but WSL2 puts Linux behind a NAT'd virtual network with a &lt;code&gt;172.x&lt;/code&gt; address, so reaching the SIP server from another device meant fighting with mirrored networking and port forwarding. On top of that, Ubuntu 20.04's Kamailio control tool had an old Python 2 vs 3 bug that broke user creation.&lt;/p&gt;

&lt;p&gt;A disposable EC2 instance sidestepped all of it: a clean public IP, no WSL networking quirks, and a newer Ubuntu with a working toolchain. And when I'm done, I just terminate it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson learned:&lt;/strong&gt; for anything involving inbound network traffic, a cloud VM with a real public IP is far less fiddly than WSL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Launch the EC2 instance
&lt;/h2&gt;

&lt;p&gt;I launched a small Ubuntu 24.04 instance (a &lt;code&gt;t3a.medium&lt;/code&gt; is plenty for a lab).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public IP&lt;/strong&gt; (what the softphones connect to): &lt;code&gt;52.91.109.231&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private IP&lt;/strong&gt; (what the instance sees on itself): &lt;code&gt;172.31.42.238&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That public/private split is the NAT situation I'd have to handle later. Keep both numbers handy; everything references them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here's my instance in the EC2 console. Note the public IPv4 address — that's the one every softphone will connect to.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftko70gwtaux1k1t4eskw.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftko70gwtaux1k1t4eskw.png" alt="EC2 console showing the running Ubuntu instance and its public IP" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Open the ports (AWS Security Group)
&lt;/h2&gt;

&lt;p&gt;This is where cloud SIP most often "silently fails." The firewall is the Security Group, and SIP needs more than one port family open. My inbound rules:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Port range&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All UDP&lt;/td&gt;
&lt;td&gt;UDP&lt;/td&gt;
&lt;td&gt;0–65535&lt;/td&gt;
&lt;td&gt;0.0.0.0/0&lt;/td&gt;
&lt;td&gt;covers SIP 5060 &lt;strong&gt;and&lt;/strong&gt; the RTP media range in one rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom TCP&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;5060&lt;/td&gt;
&lt;td&gt;0.0.0.0/0&lt;/td&gt;
&lt;td&gt;SIP fallback if a network blocks UDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;my IP&lt;/td&gt;
&lt;td&gt;so I can log in&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The gotcha:&lt;/strong&gt; if you only open 5060 and forget the RTP range, calls will connect and ring, but you'll hear &lt;strong&gt;silence&lt;/strong&gt;. Opening All UDP is the lazy-but-reliable lab choice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; All UDP open to the whole internet means SIP scanners &lt;em&gt;will&lt;/em&gt; start probing within minutes. Fine for a short-lived lab — just terminate the instance when done. Don't leave it running for days.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;My inbound rules. The All-UDP rule is the important one — it covers both SIP signaling (5060) and the RTP media ports in a single line, which is what lets the audio through.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo4kh87y2mei5d27c8l4f.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo4kh87y2mei5d27c8l4f.png" alt="AWS Security Group inbound rules opening UDP, TCP 5060, and SSH" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Install Kamailio and rtpengine
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; kamailio kamailio-extra-modules sqlite3
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; rtpengine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ubuntu 24.04 gave me &lt;strong&gt;Kamailio 5.7.4&lt;/strong&gt; and &lt;strong&gt;rtpengine 11.5.1&lt;/strong&gt; straight from the repos — no building from source. rtpengine is the piece that makes audio work across NAT: it relays the RTP stream and rewrites the media addresses so each side sends voice to a reachable IP.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Confirming both packages installed cleanly. Ubuntu 24.04 ships recent-enough versions that I didn't have to compile anything.&lt;/em&gt;&lt;/p&gt;

&lt;h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnbmyv9hx6icqunmgf29k.png" alt="Terminal showing Kamailio 5.7.4 and rtpengine 11.5.1 version output" width="800" height="272"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Step 4 — Configure rtpengine (the NAT fix)
&lt;/h2&gt;

&lt;p&gt;The whole audio problem comes down to one line in &lt;code&gt;/etc/rtpengine/rtpengine.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;172.31.42.238!52.91.109.231&lt;/span&gt;
&lt;span class="py"&gt;port-min&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10000&lt;/span&gt;
&lt;span class="py"&gt;port-max&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;20000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;interface = PRIVATE!PUBLIC&lt;/code&gt; syntax is the magic. rtpengine &lt;strong&gt;binds&lt;/strong&gt; on the private IP the OS actually has (&lt;code&gt;172.31.x&lt;/code&gt;), but &lt;strong&gt;advertises&lt;/strong&gt; the public IP (&lt;code&gt;52.91.x&lt;/code&gt;) in the call setup, so remote phones know where to send audio. Without this, you get one-way or no audio every time.&lt;/p&gt;

&lt;p&gt;Then:&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;sudo &lt;/span&gt;systemctl restart rtpengine-daemon
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status rtpengine-daemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;rtpengine up and running. "active (running)" is what you want to see here — if it's not, the audio relay is dead and calls will be silent.&lt;/em&gt;&lt;/p&gt;

&lt;h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3lei5swwyqphk4bwrftg.png" alt="systemctl status showing rtpengine-daemon active and running" width="800" height="297"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Step 5 — Point the control tool at the database
&lt;/h2&gt;

&lt;p&gt;I used &lt;strong&gt;DBTEXT&lt;/strong&gt;, a flat-file database. Zero extra services to run, which is perfect for a lab. In &lt;code&gt;/etc/kamailio/kamctlrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;SIP_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;52.91.109.231&lt;/span&gt;
&lt;span class="py"&gt;DBENGINE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;DBTEXT&lt;/span&gt;
&lt;span class="py"&gt;DB_PATH&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/etc/kamailio/dbtext"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used the &lt;strong&gt;public IP as the SIP domain&lt;/strong&gt; so the softphones don't need any DNS — they just point at the IP.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6 — Turn on the features in kamailio.cfg
&lt;/h2&gt;

&lt;p&gt;Kamailio's default config is modular; you switch features on with &lt;code&gt;#!define&lt;/code&gt; flags at the very top of &lt;code&gt;/etc/kamailio/kamailio.cfg&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;#!define WITH_AUTH
#!define WITH_USRLOCDB
#!define WITH_NAT
#!define WITH_RTPENGINE
#!define DBURL "text:///etc/kamailio/dbtext"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WITH_AUTH&lt;/code&gt; — require a password to register&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WITH_USRLOCDB&lt;/code&gt; — remember where users registered&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WITH_NAT&lt;/code&gt; + &lt;code&gt;WITH_RTPENGINE&lt;/code&gt; — enable NAT handling and route media through rtpengine&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DBURL&lt;/code&gt; — use the flat-file DB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also added one listen line so Kamailio advertises its &lt;strong&gt;public&lt;/strong&gt; IP in SIP headers (otherwise phones try to reply to the unreachable private IP):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;listen&lt;/span&gt;=&lt;span class="n"&gt;udp&lt;/span&gt;:&lt;span class="m"&gt;172&lt;/span&gt;.&lt;span class="m"&gt;31&lt;/span&gt;.&lt;span class="m"&gt;42&lt;/span&gt;.&lt;span class="m"&gt;238&lt;/span&gt;:&lt;span class="m"&gt;5060&lt;/span&gt; &lt;span class="n"&gt;advertise&lt;/span&gt; &lt;span class="m"&gt;52&lt;/span&gt;.&lt;span class="m"&gt;91&lt;/span&gt;.&lt;span class="m"&gt;109&lt;/span&gt;.&lt;span class="m"&gt;231&lt;/span&gt;:&lt;span class="m"&gt;5060&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The very top of my kamailio.cfg. These five define lines are the switches that turn on auth, NAT handling, and the rtpengine integration. They must appear exactly once — more on that mistake below.&lt;/em&gt;&lt;/p&gt;

&lt;h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1lfco6tct4qobxy8tyq6.png" alt="Top of kamailio.cfg showing the WITH_AUTH, WITH_NAT, WITH_RTPENGINE defines" width="799" height="181"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Step 7 — Create the two users
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;kamdbctl create          &lt;span class="c"&gt;# y to presence tables, n to extra tables&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;kamctl add 1001 test1001
&lt;span class="nb"&gt;sudo &lt;/span&gt;kamctl add 1002 test1002
&lt;span class="nb"&gt;sudo &lt;/span&gt;kamctl db show subscriber
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;1001&lt;/code&gt; is my phone, &lt;code&gt;1002&lt;/code&gt; is my laptop. The &lt;code&gt;db show&lt;/code&gt; listed both with hashed passwords, so I knew they were stored.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Both users stored in the database. You can see 1001 and 1002, each with a hashed password — Kamailio never keeps the plaintext.&lt;/em&gt;&lt;/p&gt;

&lt;h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ojrmtpjtkphwvkcacmx.png" alt="kamctl db show subscriber output listing users 1001 and 1002 with hashed passwords" width="800" height="54"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  The three mistakes I had to fix (this is where I learned the most)
&lt;/h2&gt;

&lt;p&gt;Here's the honest part. Kamailio refused to start three separate times, and each failure taught me something.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 1 — Duplicate defines
&lt;/h3&gt;

&lt;p&gt;I'd added the &lt;code&gt;#!define&lt;/code&gt; lines in the editor, then re-added them with a command. Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRITICAL: pp_define(): already defined: WITH_AUTH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kamailio rejects a flag defined twice. I deleted the duplicate block and kept exactly one set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; &lt;code&gt;kamailio -c -f /etc/kamailio/kamailio.cfg&lt;/code&gt; checks syntax before you start the service. Use it every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 2 — The database module wasn't loaded
&lt;/h3&gt;

&lt;p&gt;Config &lt;em&gt;parsed&lt;/em&gt; fine, but the service still died. Running it in the foreground (&lt;code&gt;sudo kamailio -f ... -D -E&lt;/code&gt;) finally showed the real error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: db_bind_mod(): Module db_text not found. Missing loadmodule?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'd told Kamailio to use DBTEXT but never loaded the &lt;code&gt;db_text&lt;/code&gt; module. The default config only loads the MySQL one. Fix:&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;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'/^loadmodule "jsonrpcs.so"/i loadmodule "db_text.so"'&lt;/span&gt; /etc/kamailio/kamailio.cfg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; &lt;code&gt;systemctl status&lt;/code&gt; hides the real error. Running the daemon in the foreground shows exactly why it won't start. That one trick unblocked me twice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 3 — File permissions
&lt;/h3&gt;

&lt;p&gt;The phone was now &lt;em&gt;reaching&lt;/em&gt; the server (I could see &lt;code&gt;REGISTER&lt;/code&gt; requests in the log — huge milestone), but every one failed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db_text: failed to open file [/etc/kamailio/dbtext/subscriber]
auth_db: get_ha1(): failed to query database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subscriber file was owned by &lt;code&gt;root&lt;/code&gt; with &lt;code&gt;-rw-------&lt;/code&gt; permissions, but the Kamailio service runs as the &lt;code&gt;kamailio&lt;/code&gt; user, which couldn't read it. &lt;code&gt;kamctl&lt;/code&gt; had created it as root with locked-down perms because it holds password hashes. Fix:&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;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; kamailio:kamailio /etc/kamailio/dbtext
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;640 /etc/kamailio/dbtext/subscriber
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart kamailio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; "it works when I run it with sudo but not as a service" almost always means a permission or user mismatch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8 — Register the softphones
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Phone (1001):&lt;/strong&gt; I used Zoiper (Linphone works too). On the account setup screen that asks about an authentication username / outbound proxy, I left it unchecked and tapped &lt;strong&gt;Skip&lt;/strong&gt; — my auth username is the same as my SIP username. The account settings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Username&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password&lt;/td&gt;
&lt;td&gt;&lt;code&gt;test1001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain / host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;52.91.109.231&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;UDP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A green checkmark and "Account is ready" meant it had registered to the cloud server.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The moment it clicked: the green check and "Account is ready" mean my phone successfully registered to the server running in AWS.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2tds1301hwzd13crf8ms.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2tds1301hwzd13crf8ms.png" alt="Zoiper on the phone showing 1001@52.91.109.231 registered and Account is ready" width="800" height="1778"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laptop (1002):&lt;/strong&gt; I installed &lt;strong&gt;MicroSIP&lt;/strong&gt; on Windows (lightweight, simple) and added an account with the same shape but username &lt;code&gt;1002&lt;/code&gt; / password &lt;code&gt;test1002&lt;/code&gt;. It showed "Online" at the bottom.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The laptop side, registered as 1002. "Online" here is the Windows equivalent of the phone's green check — both endpoints are now live on the server.&lt;/em&gt;&lt;/p&gt;

&lt;h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8j8w97etulv36sucdykt.png" alt="MicroSIP on Windows showing the 1002 account online" width="320" height="578"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Step 9 — Make the call
&lt;/h2&gt;

&lt;p&gt;I kept the live log running on the server the whole time:&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;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; kamailio &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I dialed &lt;code&gt;1002&lt;/code&gt; from my phone. The laptop rang, I answered, and the log showed the money line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;INVITE ... code=200;reason=OK;src_user=1001;...;dst_user=1002
BYE    ... code=200;reason=OK ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;200 OK&lt;/code&gt; on the INVITE means the call connected end to end. The &lt;code&gt;BYE&lt;/code&gt; with &lt;code&gt;200 OK&lt;/code&gt; means it hung up cleanly. Two-way audio, through my own server in the cloud.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; if the phone and laptop are in the same room, use headphones on at least one of them or you'll get awful echo/feedback as each picks up the other's speaker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;The call connected on the phone — live and counting.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fos6t62zgazcqz5mfohu3.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fos6t62zgazcqz5mfohu3.png" alt="Active call in progress on the phone" width="800" height="1778"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And the same call on the laptop side in MicroSIP. Both ends are talking through the cloud server.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffq7e8muf9j5zr3fhhb40.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffq7e8muf9j5zr3fhhb40.png" alt="Active call in progress on MicroSIP on the laptop" width="312" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The proof in the server log: the INVITE got a &lt;code&gt;200 OK&lt;/code&gt; (call answered) and the &lt;code&gt;BYE&lt;/code&gt; got a &lt;code&gt;200 OK&lt;/code&gt; (clean hang-up). This is exactly what a successful call looks like from Kamailio's side.&lt;/em&gt;&lt;/p&gt;

&lt;h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4avl2piztwnmkl2m1kn6.png" alt="journalctl log showing the INVITE with 200 OK and the BYE" width="800" height="378"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Reading the SIP call flow
&lt;/h2&gt;

&lt;p&gt;Once it worked, the log became a great teacher. A successful call looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;INVITE&lt;/strong&gt; — caller asks to start a call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100 Trying&lt;/strong&gt; — server acknowledges it's working on it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;180 Ringing&lt;/strong&gt; — the other phone is ringing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;200 OK&lt;/strong&gt; — the callee answered&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ACK&lt;/strong&gt; — caller confirms, media starts flowing via rtpengine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BYE / 200 OK&lt;/strong&gt; — someone hangs up&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also saw a few &lt;code&gt;486 Busy Here&lt;/code&gt; responses — those were just calls I declined or didn't pick up. Not errors, just normal call outcomes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently / next steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Try SIP over TCP or TLS.&lt;/strong&gt; Some networks block UDP 5060; TCP got through reliably, and TLS would encrypt the signaling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tighten the Security Group.&lt;/strong&gt; All-UDP-open is fine for an afternoon, not for anything persistent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a third user and try a group scenario&lt;/strong&gt;, or point a desk phone at it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch the raw packets&lt;/strong&gt; with &lt;code&gt;ngrep -d any port 5060&lt;/code&gt; or &lt;code&gt;sngrep&lt;/code&gt; to see the actual SIP messages, not just the accounting log.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The whole thing in one glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Where it runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Kamailio&lt;/td&gt;
&lt;td&gt;SIP registrar + proxy (signaling)&lt;/td&gt;
&lt;td&gt;EC2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rtpengine&lt;/td&gt;
&lt;td&gt;relays the voice (RTP), fixes NAT&lt;/td&gt;
&lt;td&gt;EC2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DBTEXT&lt;/td&gt;
&lt;td&gt;stores users 1001 / 1002&lt;/td&gt;
&lt;td&gt;EC2 (flat files)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zoiper (1001)&lt;/td&gt;
&lt;td&gt;softphone with a mic&lt;/td&gt;
&lt;td&gt;my phone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MicroSIP (1002)&lt;/td&gt;
&lt;td&gt;softphone with a mic&lt;/td&gt;
&lt;td&gt;my laptop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One cloud server, two phones, a real call. The hard parts weren't the concepts — they were the duplicate config line, the unloaded module, and the file permission. Each one failed loudly, and reading the error (especially in the foreground) was always the way out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't forget:&lt;/strong&gt; when you're finished, &lt;strong&gt;stop or terminate the EC2 instance&lt;/strong&gt; so it isn't billing or sitting open to the internet.&lt;/p&gt;

</description>
      <category>voip</category>
      <category>sip</category>
      <category>linux</category>
      <category>aws</category>
    </item>
    <item>
      <title>Learning Ansible Without Opening the Docs Website</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Wed, 14 Oct 2026 09:58:41 +0000</pubDate>
      <link>https://dev.to/jayakrishnayadav24/learning-ansible-without-opening-the-docs-website-5dm8</link>
      <guid>https://dev.to/jayakrishnayadav24/learning-ansible-without-opening-the-docs-website-5dm8</guid>
      <description>&lt;p&gt;&lt;em&gt;How I used &lt;code&gt;ansible-doc&lt;/code&gt;, local collection files, and a few terminal commands to&lt;br&gt;
understand Ansible instead of blindly copying YAML.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've been poking at Ansible lately, and somewhere along the way I made a small&lt;br&gt;
decision that changed how I learn it: I stopped reaching for the browser.&lt;/p&gt;

&lt;p&gt;Every time I didn't know how something worked, my reflex was to Google it, land on&lt;br&gt;
the official docs, copy a snippet, move on. It worked, but I never really &lt;em&gt;kept&lt;/em&gt;&lt;br&gt;
anything. So this time I tried something different. I wanted to see how far I could&lt;br&gt;
get using only what's already sitting on my machine. Turns out, pretty far.&lt;/p&gt;

&lt;p&gt;Here's the trail I followed.&lt;/p&gt;
&lt;h2&gt;
  
  
  It started with a broken command
&lt;/h2&gt;

&lt;p&gt;I ran a simple scaffolding command and got slapped with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR! Unexpected Exception, this is probably a bug: module 'lib' has no attribute 'SSL_load_error_strings'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not exactly welcoming. The traceback pointed at &lt;code&gt;pyOpenSSL&lt;/code&gt; calling into&lt;br&gt;
&lt;code&gt;cryptography&lt;/code&gt;, so I checked what versions were actually installed — making sure I&lt;br&gt;
queried the same Python that runs Ansible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip show pyOpenSSL cryptography
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The version check revealed a compatibility problem: an older &lt;code&gt;pyOpenSSL&lt;/code&gt; was running&lt;br&gt;
alongside a much newer &lt;code&gt;cryptography&lt;/code&gt; package. The older library expected an OpenSSL&lt;br&gt;
binding that was no longer available in that combination. This kind of drift is easy&lt;br&gt;
to end up with when system packages and pip installs get mixed together.&lt;/p&gt;

&lt;p&gt;Rather than modifying system-managed Python packages, I created a virtual&lt;br&gt;
environment and installed Ansible in isolation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv ~/.venvs/ansible
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.venvs/ansible/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; pip ansible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gave me a cleaner, more predictable environment and avoided changing&lt;br&gt;
dependencies the operating system relies on. One &lt;code&gt;ansible --version&lt;/code&gt; later and I had&lt;br&gt;
a modern &lt;code&gt;ansible-core&lt;/code&gt; with everything matched up. Lesson one, re-learned the hard&lt;br&gt;
way: when your Python environment is a mess, don't patch it, isolate it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The command that does most of the work
&lt;/h2&gt;

&lt;p&gt;The thing I kept coming back to is &lt;code&gt;ansible-doc&lt;/code&gt;. It ships with Ansible, works&lt;br&gt;
completely offline, and documents basically everything.&lt;/p&gt;

&lt;p&gt;Want to know what you can do? List it all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-doc &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my setup that printed thousands of available plugins and modules, because I'd&lt;br&gt;
installed the full community bundle — the exact number depends on which Ansible&lt;br&gt;
packages and collections you have installed. Searching through them is just grep:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-doc &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to actually learn a module, you read its page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-doc copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part I now rely on most is the &lt;strong&gt;EXAMPLES&lt;/strong&gt; section that many module pages carry&lt;br&gt;
near the bottom. That's where you see real, working YAML you can adapt. No website&lt;br&gt;
needed. For a quick skeleton of just the options, there's a snippet mode too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-doc &lt;span class="nt"&gt;-s&lt;/span&gt; copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The confusion that finally made things click
&lt;/h2&gt;

&lt;p&gt;Here's where I got stuck for a bit, and honestly it was the most useful stumble of&lt;br&gt;
the whole thing.&lt;/p&gt;

&lt;p&gt;I tried to read up on &lt;code&gt;loop&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-doc loop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[WARNING]: loop was not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not found? I use loops all the time. Then I tried &lt;code&gt;tasks&lt;/code&gt;, &lt;code&gt;when&lt;/code&gt;, same thing.&lt;br&gt;
Nothing.&lt;/p&gt;

&lt;p&gt;The penny dropped when I realised Ansible has a few distinct pieces, and I'd been&lt;br&gt;
treating them as one. The mental model that helped me was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modules&lt;/strong&gt; perform actions, such as copying files, installing packages, or
managing services. These are what &lt;code&gt;ansible-doc &amp;lt;name&amp;gt;&lt;/code&gt; looks up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module arguments&lt;/strong&gt; specify what the module should do, such as the package name
or the desired state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keywords&lt;/strong&gt; control how Ansible organises and executes those actions, such as
&lt;code&gt;when&lt;/code&gt;, &lt;code&gt;loop&lt;/code&gt;, &lt;code&gt;become&lt;/code&gt;, and &lt;code&gt;register&lt;/code&gt;. These live under a different flag.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So keywords need this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-doc &lt;span class="nt"&gt;-t&lt;/span&gt; keyword loop
ansible-doc &lt;span class="nt"&gt;-t&lt;/span&gt; keyword when
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And suddenly it told me exactly what I wanted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;applies_to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Task&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Takes a list for the task to iterate over, saving each element into&lt;/span&gt;
    &lt;span class="s"&gt;the item variable&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I understood those three pieces — module, arguments, keywords — reading an&lt;br&gt;
Ansible task became much easier. A task is a module invocation, with its arguments,&lt;br&gt;
optionally wrapped in task-level keywords. Plays add their own play-level keywords on&lt;br&gt;
top of that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                          &lt;span class="c1"&gt;# play-level keyword&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;Install nginx&lt;/span&gt;         &lt;span class="c1"&gt;# task-level keyword&lt;/span&gt;
    &lt;span class="na"&gt;ansible.builtin.package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="c1"&gt;# module&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;nginx&lt;/span&gt;               &lt;span class="c1"&gt;# module argument&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;            &lt;span class="c1"&gt;# module argument&lt;/span&gt;
    &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ansible_os_family == "Debian"&lt;/span&gt;   &lt;span class="c1"&gt;# task-level keyword&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every line traces back to one of those categories. Once I saw that, I didn't feel&lt;br&gt;
like I was guessing anymore.&lt;/p&gt;
&lt;h2&gt;
  
  
  A rough edge, and how I got past it
&lt;/h2&gt;

&lt;p&gt;A couple of times &lt;code&gt;ansible-doc&lt;/code&gt; just fell over on me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR! ... CLI.pager_pipe() takes 1 positional argument but 2 were given
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was intermittent, which made it annoying to pin down. The fix that worked for me&lt;br&gt;
was to force a plain pager for that invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PAGER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;ansible-doc &lt;span class="nt"&gt;-s&lt;/span&gt; copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That disables interactive paging for the command. I now just prefix everything with&lt;br&gt;
&lt;code&gt;PAGER=cat&lt;/code&gt; — it does nothing when it isn't needed and saves me when it is. If the&lt;br&gt;
error shows up for you, it's worth checking your installed Ansible version and&lt;br&gt;
whether it reproduces with your default pager, since the behaviour can depend on&lt;br&gt;
both. If you'd rather not type it every time, bury it in an alias:&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;alias &lt;/span&gt;&lt;span class="nv"&gt;adoc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'PAGER=cat ansible-doc'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Keywords don't have examples, so I went looking for them
&lt;/h2&gt;

&lt;p&gt;One frustration with keywords: their doc pages describe what they do but never show&lt;br&gt;
them in use. There's no EXAMPLES section for &lt;code&gt;when&lt;/code&gt; or &lt;code&gt;loop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But the collections I'd installed came with lots of real YAML files bundled right on&lt;br&gt;
disk. So instead of searching the internet for "ansible when example", I searched my&lt;br&gt;
own machine.&lt;/p&gt;

&lt;p&gt;First, find where the collections actually live (the path varies by machine and&lt;br&gt;
Python version, so don't hardcode it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-galaxy collection list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That prints the collection paths. Then grep the relevant directory for the keyword&lt;br&gt;
you want to see used — searching the whole tree, not just an &lt;code&gt;examples/&lt;/code&gt; folder,&lt;br&gt;
since not every collection keeps one:&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-rl&lt;/span&gt; &lt;span class="s2"&gt;"when:"&lt;/span&gt; &amp;lt;collections-path&amp;gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.yml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That surfaced real examples and reusable patterns from the installed collections.&lt;br&gt;
Worth knowing: these files can also include tests, fixtures, and other development&lt;br&gt;
content, so not everything you find is a polished playbook. But plenty of it is&lt;br&gt;
genuinely useful to read. Here's the kind of thing that came back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="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;Add command completion&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;command_completion.yml&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcloud_command_completion&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading other people's real tasks, right there offline, taught me more about idiom&lt;br&gt;
and style than any tutorial had.&lt;/p&gt;
&lt;h2&gt;
  
  
  Putting it together in one small playbook
&lt;/h2&gt;

&lt;p&gt;All of this only clicked once I assembled the pieces into something that runs. Here's&lt;br&gt;
a tiny playbook that installs nginx and makes sure it's running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&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;Install and start nginx&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webservers&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;tasks&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;Install nginx&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.package&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;nginx&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&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;Ensure nginx is running&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.service&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;nginx&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;started&lt;/span&gt;
        &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Writing it, I leaned entirely on the offline docs to get the module arguments right:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-doc ansible.builtin.package
ansible-doc ansible.builtin.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And before running anything, I checked the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook site.yml &lt;span class="nt"&gt;--syntax-check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one thing that playbook assumes is an inventory. &lt;code&gt;hosts: webservers&lt;/code&gt; only means&lt;br&gt;
something if a group called &lt;code&gt;webservers&lt;/code&gt; exists, so I wrote a tiny inventory file to&lt;br&gt;
go with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# inventory.ini
&lt;/span&gt;&lt;span class="nn"&gt;[webservers]&lt;/span&gt;
&lt;span class="err"&gt;192.168.1.10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn't have to guess the format, either — the INI inventory plugin documents&lt;br&gt;
itself, examples included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-doc &lt;span class="nt"&gt;-t&lt;/span&gt; inventory ansible.builtin.ini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I pointed the playbook at it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook &lt;span class="nt"&gt;-i&lt;/span&gt; inventory.ini site.yml &lt;span class="nt"&gt;--syntax-check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of honest caveats here. &lt;code&gt;--syntax-check&lt;/code&gt; only validates the playbook's&lt;br&gt;
structure — it doesn't confirm the hosts are reachable or that the tasks will&lt;br&gt;
actually succeed. It just catches the silly mistakes before you waste a real run. If&lt;br&gt;
you want to see how Ansible reads your inventory back, &lt;code&gt;ansible-inventory -i&lt;br&gt;
inventory.ini --graph&lt;/code&gt; prints the groups and hosts as a tree, which is a nice offline&lt;br&gt;
sanity check before you run anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I've landed
&lt;/h2&gt;

&lt;p&gt;After all this, my loop for learning any new piece of Ansible is pretty tight:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the module: &lt;code&gt;ansible-doc -l | grep -i &amp;lt;topic&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Read its arguments and examples: &lt;code&gt;ansible-doc &amp;lt;module&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Look up any structure keywords I need: &lt;code&gt;ansible-doc -t keyword &amp;lt;word&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Borrow patterns from the bundled collection files with a quick &lt;code&gt;grep&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check my own file before running it: &lt;code&gt;ansible-playbook file.yml --syntax-check&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing happens in a terminal, nothing opens in a browser, and because I'm&lt;br&gt;
reading and assembling instead of copy-pasting, it actually sticks.&lt;/p&gt;

&lt;p&gt;I think that's the real takeaway for me. The docs were never the thing holding me&lt;br&gt;
back. Slowing down enough to understand &lt;em&gt;why&lt;/em&gt; a command is shaped the way it is, and&lt;br&gt;
using the tools already in front of me, is what finally made Ansible feel less like&lt;br&gt;
magic and more like something I can just write.&lt;/p&gt;

&lt;p&gt;And the method isn't really about Ansible. Investigating the error instead of&lt;br&gt;
pasting it into a search box, inspecting the references already on disk, reading real&lt;br&gt;
examples, validating my own work — that same loop works just as well for Terraform,&lt;br&gt;
Docker, or Kubernetes.&lt;/p&gt;

&lt;p&gt;Still a lot to explore.&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>devops</category>
      <category>linux</category>
      <category>automation</category>
    </item>
    <item>
      <title>Learn RAID the Hard Way: Building 0/1/5/6/10 by Hand on EC2</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Sat, 10 Oct 2026 05:10:54 +0000</pubDate>
      <link>https://dev.to/jayakrishnayadav24/learn-raid-the-hard-way-building-015610-by-hand-on-ec2-2p0a</link>
      <guid>https://dev.to/jayakrishnayadav24/learn-raid-the-hard-way-building-015610-by-hand-on-ec2-2p0a</guid>
      <description>&lt;p&gt;I wanted to actually &lt;em&gt;understand&lt;/em&gt; RAID instead of memorizing a comparison table. So I launched an Ubuntu 24.04 EC2 instance, attached a few spare EBS volumes, and built every common RAID level by hand with &lt;code&gt;mdadm&lt;/code&gt;: &lt;strong&gt;RAID 0, 1, 5, 6, and 10&lt;/strong&gt;. For each one I created the array, wrote a file, deliberately failed a disk to watch what happens, then rebuilt it.&lt;/p&gt;

&lt;p&gt;This article is the full walkthrough, in plain language, and it has three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The concepts&lt;/strong&gt; — what RAID and an "array" actually are, and the storage vocabulary around them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The hands-on build&lt;/strong&gt; — every level with the real commands and real output, including the warnings you'll hit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production reality&lt;/strong&gt; — the errors you face on live systems and exactly how to handle each one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because this runs entirely on AWS, I've kept EC2 and EBS specifics inline where they matter. If you've ever been confused about &lt;em&gt;why&lt;/em&gt; RAID 5 and RAID 10 give different usable space, or frozen at the word "degraded" in a 2 AM alert, this is for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is RAID, in one minute?
&lt;/h3&gt;

&lt;p&gt;RAID stands for &lt;strong&gt;Redundant Array of Independent Disks&lt;/strong&gt;. You combine several physical disks into one logical disk. Depending on &lt;em&gt;how&lt;/em&gt; you combine them, you trade between three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt; — spread data across disks so reads and writes happen in parallel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redundancy&lt;/strong&gt; — keep extra copies or parity so you survive a disk dying.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capacity&lt;/strong&gt; — how much usable space you actually get.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You cannot maximize all three at once. Each RAID "level" is a different compromise. That is the entire concept.&lt;/p&gt;

&lt;p&gt;The Linux tool for software RAID is &lt;strong&gt;&lt;code&gt;mdadm&lt;/code&gt;&lt;/strong&gt; (multiple-device admin). It builds a virtual device like &lt;code&gt;/dev/md0&lt;/code&gt; out of your real disks.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is an "array"?
&lt;/h3&gt;

&lt;p&gt;You will see the word &lt;em&gt;array&lt;/em&gt; on nearly every line below, so let's define it clearly.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;array&lt;/strong&gt; is the single logical disk that RAID creates out of your group of physical disks. "Array" just means "an organized collection" — here, a collection of disks organized to behave as one.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;You have 4 separate physical disks: &lt;code&gt;nvme1n1&lt;/code&gt;, &lt;code&gt;nvme2n1&lt;/code&gt;, &lt;code&gt;nvme3n1&lt;/code&gt;, &lt;code&gt;nvme4n1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You tell &lt;code&gt;mdadm&lt;/code&gt; to combine them.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mdadm&lt;/code&gt; hands you back &lt;strong&gt;one&lt;/strong&gt; new device: &lt;code&gt;/dev/md0&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That &lt;code&gt;/dev/md0&lt;/code&gt; &lt;strong&gt;is the array.&lt;/strong&gt; Your OS treats it like a normal single disk — you put a filesystem on it, mount it, read and write files — with no idea there are really 4 disks underneath. &lt;code&gt;mdadm&lt;/code&gt; quietly handles spreading data (and copies or parity) across the members.&lt;/p&gt;

&lt;p&gt;Vocabulary for the rest of the article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Member / component disk&lt;/strong&gt; — one real physical disk in the group (&lt;code&gt;nvme1n1&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Array&lt;/strong&gt; — the combined virtual disk (&lt;code&gt;/dev/md0&lt;/code&gt;) those members form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAID level&lt;/strong&gt; — the &lt;em&gt;rule&lt;/em&gt; for how data is spread across the members (stripe, mirror, parity).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So "create the array" means "fuse these physical disks into one &lt;code&gt;/dev/md0&lt;/code&gt;," and "the array is degraded" means "the combined disk still works, but one member is missing."&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to partition the disks first (fdisk)?
&lt;/h3&gt;

&lt;p&gt;Short answer: &lt;strong&gt;no, not for this.&lt;/strong&gt; You will notice there is no &lt;code&gt;fdisk&lt;/code&gt; anywhere below — the arrays are built directly on whole raw disks.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;partition&lt;/strong&gt; divides one physical disk into separate logical sections, each acting like its own disk. Tools like &lt;code&gt;fdisk&lt;/code&gt;, &lt;code&gt;parted&lt;/code&gt;, or &lt;code&gt;gdisk&lt;/code&gt; write a small &lt;em&gt;partition table&lt;/em&gt; at the start of the disk recording where each section begins and ends. You partition when you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;separate areas for different purposes (OS vs data vs swap) on one disk,&lt;/li&gt;
&lt;li&gt;boot-specific partitions (the EFI System Partition, &lt;code&gt;/boot/efi&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;or multiple filesystems / operating systems on one disk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But a filesystem or a RAID array can live directly on a whole raw disk — &lt;code&gt;/dev/nvme1n1&lt;/code&gt; and &lt;code&gt;/dev/nvme1n1p1&lt;/code&gt; (its first partition) are both just block devices. On EC2, each EBS volume is already a clean, single-purpose disk, so using whole disks is simplest and common. (On physical servers, some admins still partition first so a slightly-smaller replacement drive from a different vendor still fits the array.)&lt;/p&gt;

&lt;p&gt;The three layers, kept straight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fdisk&lt;/code&gt;&lt;/strong&gt; = divide a disk into sections (optional).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mkfs&lt;/code&gt;&lt;/strong&gt; = put a filesystem on a section, a whole disk, or a RAID array.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mdadm&lt;/code&gt;&lt;/strong&gt; = combine disks into a RAID array.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A word on filesystems: ext4 vs xfs
&lt;/h3&gt;

&lt;p&gt;A RAID array is just a block device — it needs a filesystem before it can hold files. The two common choices:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ext4&lt;/th&gt;
&lt;th&gt;xfs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Best at&lt;/td&gt;
&lt;td&gt;General purpose, small-to-medium files&lt;/td&gt;
&lt;td&gt;Large files, high parallelism, big filesystems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can &lt;strong&gt;grow&lt;/strong&gt;?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (online only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can &lt;strong&gt;shrink&lt;/strong&gt;?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No — grow only&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default on&lt;/td&gt;
&lt;td&gt;Debian, Ubuntu&lt;/td&gt;
&lt;td&gt;RHEL, Fedora, Amazon Linux 2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Grow&lt;/strong&gt; means enlarging a filesystem to use more space (common and safe — e.g. after expanding an EBS volume you still run &lt;code&gt;resize2fs&lt;/code&gt; or &lt;code&gt;xfs_growfs&lt;/code&gt; to make the new space usable). &lt;strong&gt;Shrink&lt;/strong&gt; means reducing it to free space (rarer, riskier, and xfs cannot do it at all).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;&lt;code&gt;mkfs&lt;/code&gt; always erases.&lt;/strong&gt; Reformatting a device — even to switch from ext4 to xfs — destroys everything on it. There is no in-place ext4-to-xfs conversion. To change filesystem type without losing data: copy the data off, &lt;code&gt;mkfs&lt;/code&gt; the new type, then copy it back. In this lab, reformatting between RAID levels was fine only because the test files were disposable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I used ext4 throughout. For most workloads either is fine; pick xfs for very large files and heavy concurrency, ext4 for everything else or when you might need to shrink.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The hands-on build
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The setup
&lt;/h3&gt;

&lt;p&gt;On EC2 I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1 × 30 GiB&lt;/strong&gt; root volume — the OS. Never touched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 × 10 GiB&lt;/strong&gt; EBS volumes — the RAID practice disks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modern EC2 instances present disks as NVMe devices. Here is &lt;code&gt;lsblk&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;nvme0n1      30G  disk              &amp;lt;- ROOT. Has /, /boot. NEVER touch.
├─nvme0n1p1  29G  part /
├─nvme0n1p15 106M part /boot/efi
└─nvme0n1p16 913M part /boot
nvme1n1      10G  disk              &amp;lt;- RAID disk
nvme2n1      10G  disk              &amp;lt;- RAID disk
nvme3n1      10G  disk              &amp;lt;- RAID disk
nvme4n1      10G  disk              &amp;lt;- RAID disk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The one rule:&lt;/strong&gt; the root disk is the one with partitions mounted at &lt;code&gt;/&lt;/code&gt;. Every RAID command below names only &lt;code&gt;nvme1n1&lt;/code&gt;–&lt;code&gt;nvme4n1&lt;/code&gt;. If you ever see &lt;code&gt;nvme0n1&lt;/code&gt; in a format or RAID command, stop.&lt;/p&gt;

&lt;p&gt;Install the tool and learn two status commands:&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;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; mdadm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cat /proc/mdstat&lt;/code&gt; — quick live status of all arrays.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo mdadm --detail /dev/md0&lt;/code&gt; — full report on one array.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to read the status symbols&lt;/strong&gt; (this is 90% of understanding &lt;code&gt;mdadm&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;[UU]&lt;/code&gt;, &lt;code&gt;[UUUU]&lt;/code&gt; — each &lt;code&gt;U&lt;/code&gt; is a disk that is &lt;strong&gt;U&lt;/strong&gt;p. An &lt;code&gt;_&lt;/code&gt; is down or missing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[2/2]&lt;/code&gt;, &lt;code&gt;[4/3]&lt;/code&gt; — (disks expected / disks present).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(F)&lt;/code&gt; beside a disk — it is marked &lt;strong&gt;F&lt;/strong&gt;ailed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resync&lt;/code&gt; — syncing a freshly created array.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;recovery&lt;/code&gt; — rebuilding a disk you re-added after a failure.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;State : clean&lt;/code&gt; = healthy; &lt;code&gt;State : degraded&lt;/code&gt; = running but missing a disk.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  RAID 0 — Striping (speed, zero safety)
&lt;/h3&gt;

&lt;p&gt;RAID 0 splits data across disks in chunks — a write goes to disk 1, then disk 2, then disk 1, so both work at once. Fast. But &lt;strong&gt;no redundancy&lt;/strong&gt;: lose either disk and everything is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capacity:&lt;/strong&gt; full sum of all disks. 2 × 10 GiB = ~20 GiB.&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;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--create&lt;/span&gt; &lt;span class="nt"&gt;--verbose&lt;/span&gt; /dev/md0 &lt;span class="nt"&gt;--level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;--raid-devices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 /dev/nvme1n1 /dev/nvme2n1
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/mdstat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;md0 : active raid0 nvme2n1[1] nvme1n1[0]
      20953088 blocks super 1.2 512k chunks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;512k chunks&lt;/code&gt; is the stripe size (how much data lands on one disk before moving to the next). &lt;code&gt;20953088&lt;/code&gt; blocks ≈ 20 GiB — full capacity, no redundancy overhead.&lt;/p&gt;

&lt;p&gt;Add a filesystem and mount it:&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;sudo &lt;/span&gt;mkfs.ext4 /dev/md0
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/raid0
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount /dev/md0 /mnt/raid0
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"RAID 0 test"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /mnt/raid0/test.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no failure drill for RAID 0 — if a disk dies, the array is simply gone. That is the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Teardown&lt;/strong&gt; (frees the disks for the next level):&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;sudo &lt;/span&gt;umount /mnt/raid0
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--stop&lt;/span&gt; /dev/md0
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--zero-superblock&lt;/span&gt; /dev/nvme1n1 /dev/nvme2n1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;superblock&lt;/em&gt; is metadata &lt;code&gt;mdadm&lt;/code&gt; writes on each disk saying "I belong to array X." &lt;code&gt;--zero-superblock&lt;/code&gt; wipes it so the disk looks fresh. Skip this and &lt;code&gt;mdadm&lt;/code&gt; may re-assemble a ghost array later.&lt;/p&gt;

&lt;h3&gt;
  
  
  RAID 1 — Mirroring (safety, half the space)
&lt;/h3&gt;

&lt;p&gt;RAID 1 writes &lt;strong&gt;identical data to both disks&lt;/strong&gt; — twins. One dies, the other has a complete copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capacity:&lt;/strong&gt; one disk's worth. 2 × 10 GiB = ~10 GiB. The 50% mirror tax.&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;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--create&lt;/span&gt; &lt;span class="nt"&gt;--verbose&lt;/span&gt; /dev/md0 &lt;span class="nt"&gt;--level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;--raid-devices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 /dev/nvme1n1 /dev/nvme2n1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First scary-but-harmless message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mdadm: Note: this array has metadata at the start and
    may not be suitable as a boot device. ...
Continue creating array? y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is only saying this array cannot &lt;em&gt;boot an OS&lt;/em&gt; (its metadata sits at the disk start). We use it for data, so type &lt;code&gt;y&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/mdstat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;md0 : active raid1 nvme2n1[1] nvme1n1[0]
      10476544 blocks super 1.2 [2/2] [UU]
      [==&amp;gt;..................]  resync = 11.8% (1241088/10476544) ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That progress bar is a &lt;strong&gt;resync&lt;/strong&gt; — &lt;code&gt;mdadm&lt;/code&gt; copying disk 1 onto disk 2 so they start identical. The array is usable immediately; you do not wait. &lt;code&gt;[UU]&lt;/code&gt; = both up. &lt;code&gt;10476544&lt;/code&gt; ≈ 10 GiB — half of RAID 0.&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;sudo &lt;/span&gt;mkfs.ext4 /dev/md0
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/raid1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;mount /dev/md0 /mnt/raid1
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"RAID 1 test"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /mnt/raid1/test.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning you'll see:&lt;/strong&gt; &lt;code&gt;mkfs.ext4&lt;/code&gt; says &lt;code&gt;/dev/md0 contains a ext4 file system ... Proceed anyway? (y,N)&lt;/code&gt;. That is leftover from the RAID 0 filesystem on these reused disks. Type &lt;code&gt;y&lt;/code&gt; to overwrite.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The failure drill:&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;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--fail&lt;/span&gt; /dev/nvme2n1
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/mdstat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;md0 : active raid1 nvme2n1[1](F) nvme1n1[0]
      10476544 blocks super 1.2 [2/1] [U_]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;(F)&lt;/code&gt; failed, &lt;code&gt;[2/1]&lt;/code&gt; one of two present, &lt;code&gt;[U_]&lt;/code&gt; one up and one down → &lt;strong&gt;degraded&lt;/strong&gt;. But:&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;cat&lt;/span&gt; /mnt/raid1/test.txt        &lt;span class="c"&gt;# RAID 1 test   &amp;lt;- still works&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A disk "died" and you lost nothing. Rebuild it:&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;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--remove&lt;/span&gt; /dev/nvme2n1
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--zero-superblock&lt;/span&gt; /dev/nvme2n1
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--add&lt;/span&gt; /dev/nvme2n1
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/mdstat        &lt;span class="c"&gt;# now shows: recovery = X%&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;recovery&lt;/code&gt; = re-mirroring onto the fresh disk. Back to &lt;code&gt;[UU]&lt;/code&gt; when done. Then tear down.&lt;/p&gt;

&lt;h3&gt;
  
  
  RAID 5 — Striping + distributed parity (the popular one)
&lt;/h3&gt;

&lt;p&gt;RAID 5 stripes like RAID 0 (fast) &lt;em&gt;and&lt;/em&gt; computes &lt;strong&gt;parity&lt;/strong&gt; (a checksum) spread across all disks. Lose one disk and &lt;code&gt;mdadm&lt;/code&gt; recalculates its data from parity plus the survivors. There is no full copy — it is math.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capacity:&lt;/strong&gt; you lose exactly &lt;strong&gt;one disk's worth&lt;/strong&gt; to parity, regardless of disk count. 3 × 10 GiB = ~20 GiB. Cheap redundancy (1/N overhead). &lt;strong&gt;Minimum 3 disks.&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;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--create&lt;/span&gt; &lt;span class="nt"&gt;--verbose&lt;/span&gt; /dev/md0 &lt;span class="nt"&gt;--level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="nt"&gt;--raid-devices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/mdstat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;md0 : active raid5 nvme3n1[3] nvme2n1[1] nvme1n1[0]
      20953088 blocks ... level 5, 512k chunk, algorithm 2 [3/2] [UU_]
      [=&amp;gt;...................]  recovery =  8.4% ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Looks broken but isn't.&lt;/strong&gt; &lt;code&gt;[3/2] [UU_]&lt;/code&gt; plus "recovery" on a &lt;em&gt;brand-new&lt;/em&gt; array (and &lt;code&gt;mdadm --detail&lt;/code&gt; showing &lt;code&gt;spare rebuilding&lt;/code&gt;) is a deliberate optimization: &lt;code&gt;mdadm&lt;/code&gt; builds the last disk via recovery instead of syncing all three from scratch. It ends at &lt;code&gt;[3/3] [UUU]&lt;/code&gt;. Don't panic.&lt;/p&gt;

&lt;p&gt;~20 GiB from three disks → one disk's worth went to parity.&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;sudo &lt;/span&gt;mkfs.ext4 /dev/md0
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/raid5 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;mount /dev/md0 /mnt/raid5
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"RAID 5 test"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /mnt/raid5/test.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Failure drill:&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;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--fail&lt;/span&gt; /dev/nvme3n1
&lt;span class="nb"&gt;cat&lt;/span&gt; /mnt/raid5/test.txt        &lt;span class="c"&gt;# RAID 5 test   &amp;lt;- reconstructed from parity, live&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no copy of the dead disk anywhere — every read rebuilds the missing bytes from parity plus the survivors on the fly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hard limit: RAID 5 survives exactly ONE failure.&lt;/strong&gt; A second disk down = total loss. That limitation is why RAID 6 exists. Rebuild with the same remove → zero → add pattern, then tear down all three disks.&lt;/p&gt;

&lt;h3&gt;
  
  
  RAID 6 — Double parity (survive TWO failures)
&lt;/h3&gt;

&lt;p&gt;RAID 6 is RAID 5 with a second, independent parity block. &lt;strong&gt;Any two disks can die at once&lt;/strong&gt; and you lose nothing. Critical for large arrays, where a second disk often fails during the stressful rebuild of the first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capacity:&lt;/strong&gt; lose &lt;strong&gt;two disks' worth&lt;/strong&gt;. 4 × 10 GiB = ~20 GiB. &lt;strong&gt;Minimum 4 disks.&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;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--create&lt;/span&gt; &lt;span class="nt"&gt;--verbose&lt;/span&gt; /dev/md0 &lt;span class="nt"&gt;--level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6 &lt;span class="nt"&gt;--raid-devices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1 /dev/nvme4n1
&lt;span class="c"&gt;# then mkfs.ext4, mount at /mnt/raid6, write test.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The headline drill — fail two at once:&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;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--fail&lt;/span&gt; /dev/nvme3n1 &lt;span class="nt"&gt;--fail&lt;/span&gt; /dev/nvme4n1
&lt;span class="nb"&gt;cat&lt;/span&gt; /mnt/raid6/test.txt        &lt;span class="c"&gt;# RAID 6 test   &amp;lt;- still works!&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/mdstat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;md0 : active raid6 nvme4n1[3](F) nvme3n1[2](F) nvme2n1[1] nvme1n1[0]
      ... [4/2] [UU__]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two disks dead at once, data survives. RAID 5 would be destroyed by this. Rebuild both:&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;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--remove&lt;/span&gt; /dev/nvme3n1
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--remove&lt;/span&gt; /dev/nvme4n1
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--zero-superblock&lt;/span&gt; /dev/nvme3n1 /dev/nvme4n1
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--add&lt;/span&gt; /dev/nvme3n1
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--add&lt;/span&gt; /dev/nvme4n1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  RAID 10 — Mirror + Stripe (speed and safety)
&lt;/h3&gt;

&lt;p&gt;RAID 10 is "nested": it makes &lt;strong&gt;mirror pairs&lt;/strong&gt;, then &lt;strong&gt;stripes across them&lt;/strong&gt;. You get RAID 1's safety and RAID 0's speed, with fast rebuilds (copy from the mirror partner — no parity math). The database workhorse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capacity:&lt;/strong&gt; half to mirroring. 4 × 10 GiB = ~20 GiB.&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;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--create&lt;/span&gt; &lt;span class="nt"&gt;--verbose&lt;/span&gt; /dev/md0 &lt;span class="nt"&gt;--level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10 &lt;span class="nt"&gt;--raid-devices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1 /dev/nvme4n1
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/mdstat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;md0 : active raid10 nvme4n1[3] nvme3n1[2] nvme2n1[1] nvme1n1[0]
      20953088 blocks ... 512K chunks 2 near-copies [4/4] [UUUU]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;2 near-copies&lt;/code&gt; = every block lives on 2 disks (the mirror part).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The crucial detail — which disks are partners?&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;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--detail&lt;/span&gt; /dev/md0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0  ... active sync set-A  /dev/nvme1n1
1  ... active sync set-B  /dev/nvme2n1
2  ... active sync set-A  /dev/nvme3n1
3  ... active sync set-B  /dev/nvme4n1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;set-A&lt;/code&gt; / &lt;code&gt;set-B&lt;/code&gt; labels are the mirror groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;set-A:&lt;/strong&gt; &lt;code&gt;nvme1n1&lt;/code&gt; ⇄ &lt;code&gt;nvme3n1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;set-B:&lt;/strong&gt; &lt;code&gt;nvme2n1&lt;/code&gt; ⇄ &lt;code&gt;nvme4n1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice the pairing is &lt;strong&gt;not&lt;/strong&gt; 1+2 / 3+4 as you might guess. Always read the &lt;code&gt;set-&lt;/code&gt; labels. This controls survival:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lose &lt;strong&gt;1 disk&lt;/strong&gt; → always fine.&lt;/li&gt;
&lt;li&gt;Lose &lt;strong&gt;2 in different sets&lt;/strong&gt; → fine.&lt;/li&gt;
&lt;li&gt;Lose &lt;strong&gt;2 in the same set&lt;/strong&gt; → &lt;strong&gt;array dead&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RAID 6 survives &lt;em&gt;any&lt;/em&gt; 2; RAID 10's two-disk survival is conditional — but it rebuilds much faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure drill:&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;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--fail&lt;/span&gt; /dev/nvme3n1
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/mdstat          &lt;span class="c"&gt;# [4/3] [UU_U]&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /mnt/raid10/test.txt  &lt;span class="c"&gt;# still works — partner nvme1n1 serves it&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--remove&lt;/span&gt; /dev/nvme3n1
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--zero-superblock&lt;/span&gt; /dev/nvme3n1
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--add&lt;/span&gt; /dev/nvme3n1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rebuild speed is noticeably faster (~159 MB/s in my run) — a straight copy from the partner, no parity to compute.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Production reality — what actually goes wrong, and how to handle it
&lt;/h2&gt;

&lt;p&gt;The lab is clean. Production is not. Here are the failures you will genuinely face on live systems, roughly in order of how often they bite.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. A disk fails for real (the everyday case)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What you see:&lt;/strong&gt; an email from &lt;code&gt;mdadm --monitor&lt;/code&gt;, or &lt;code&gt;cat /proc/mdstat&lt;/code&gt; shows &lt;code&gt;[U_UU]&lt;/code&gt; and &lt;code&gt;State : degraded&lt;/code&gt;. The array still serves data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to handle:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Don't panic — degraded means &lt;em&gt;working but no safety margin&lt;/em&gt;. Reads and writes continue.&lt;/li&gt;
&lt;li&gt;Identify the dead disk and its serial so you pull the &lt;em&gt;right&lt;/em&gt; one from the chassis:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--detail&lt;/span&gt; /dev/md0
   &lt;span class="nb"&gt;sudo &lt;/span&gt;smartctl &lt;span class="nt"&gt;-a&lt;/span&gt; /dev/sdX | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; serial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Fail + remove it (if not already failed), swap the physical disk, then add the new one:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--fail&lt;/span&gt; /dev/sdX &lt;span class="nt"&gt;--remove&lt;/span&gt; /dev/sdX
   &lt;span class="c"&gt;# physically replace the drive, then:&lt;/span&gt;
   &lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm /dev/md0 &lt;span class="nt"&gt;--add&lt;/span&gt; /dev/sdY
   watch &lt;span class="nb"&gt;cat&lt;/span&gt; /proc/mdstat   &lt;span class="c"&gt;# watch recovery climb to [UUUU]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pull the correct disk.&lt;/strong&gt; Yanking a healthy disk from a degraded RAID 5 destroys the array. Match by serial number every time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Set up alerting so you even know a disk died:&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;sudo &lt;/span&gt;nano /etc/mdadm/mdadm.conf   &lt;span class="c"&gt;# add: MAILADDR you@example.com&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--monitor&lt;/span&gt; &lt;span class="nt"&gt;--scan&lt;/span&gt; &lt;span class="nt"&gt;--daemon&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without monitoring, a disk can sit failed for weeks until a &lt;em&gt;second&lt;/em&gt; one dies and takes everything. This is the number one cause of "RAID didn't save us" stories.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The rebuild kills a second disk (the RAID 5 killer)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What happens:&lt;/strong&gt; a disk fails, you add a replacement, and during the long, intense full-disk rebuild another aging disk hits a bad sector and drops out. On RAID 5 that is &lt;strong&gt;total array loss&lt;/strong&gt; — the classic reason RAID 5 is discouraged for large modern drives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to prevent:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;RAID 6&lt;/strong&gt; (or RAID 10) for anything large or important — a second failure during rebuild is survivable.&lt;/li&gt;
&lt;li&gt;Run periodic &lt;strong&gt;scrubs&lt;/strong&gt; to find bad sectors &lt;em&gt;before&lt;/em&gt; a rebuild forces you to read them:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;echo &lt;/span&gt;check | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /sys/block/md0/md/sync_action
  &lt;span class="nb"&gt;cat&lt;/span&gt; /sys/block/md0/md/mismatch_cnt     &lt;span class="c"&gt;# 0 is good&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Keep backups. RAID is &lt;strong&gt;not&lt;/strong&gt; a backup — it protects against disk failure, not deletion, corruption, ransomware, or a controller frying every disk at once.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. "Array won't assemble after reboot"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What you see on boot:&lt;/strong&gt; the array is missing, you're dropped to an emergency shell, or &lt;code&gt;/mnt/data&lt;/code&gt; is empty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; the array config was never persisted, so the OS does not know to assemble it. (In this lab we skipped that on purpose, which is exactly why nothing survived a reboot.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix — do this for any array you want to keep:&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="c"&gt;# 1. Save the array definition&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--detail&lt;/span&gt; &lt;span class="nt"&gt;--scan&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/mdadm/mdadm.conf

&lt;span class="c"&gt;# 2. Rebuild the initramfs so the array assembles early at boot&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;update-initramfs &lt;span class="nt"&gt;-u&lt;/span&gt;          &lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
&lt;span class="c"&gt;# (RHEL/CentOS: sudo dracut -f)&lt;/span&gt;

&lt;span class="c"&gt;# 3. Mount by UUID in /etc/fstab (never by /dev/md0 — numbers can change)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;blkid /dev/md0               &lt;span class="c"&gt;# grab the UUID&lt;/span&gt;
&lt;span class="c"&gt;# add to /etc/fstab:&lt;/span&gt;
&lt;span class="c"&gt;# UUID=xxxx /mnt/data ext4 defaults,nofail 0 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;nofail&lt;/code&gt; option matters: without it, a missing array can hang the entire boot. With it, the box boots and you fix the array live.&lt;/p&gt;

&lt;p&gt;If it is already not assembling:&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;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--assemble&lt;/span&gt; &lt;span class="nt"&gt;--scan&lt;/span&gt;                  &lt;span class="c"&gt;# try auto-assemble&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--assemble&lt;/span&gt; /dev/md0 /dev/sd[a-d]   &lt;span class="c"&gt;# or name the members&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--examine&lt;/span&gt; /dev/sdX                 &lt;span class="c"&gt;# inspect each disk's superblock&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. The device name changed (&lt;code&gt;/dev/md0&lt;/code&gt; became &lt;code&gt;/dev/md127&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What you see:&lt;/strong&gt; the array shows up as &lt;code&gt;/dev/md127&lt;/code&gt; instead of &lt;code&gt;/dev/md0&lt;/code&gt;, and fstab entries pointing at &lt;code&gt;/dev/md0&lt;/code&gt; fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; when &lt;code&gt;mdadm&lt;/code&gt; cannot match the array to a config entry, it auto-numbers from 127 downward. Almost always means &lt;code&gt;mdadm.conf&lt;/code&gt; is missing or stale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; regenerate the config (step 3 above) with the correct &lt;code&gt;ARRAY&lt;/code&gt; line, run &lt;code&gt;update-initramfs -u&lt;/code&gt;, and always mount by &lt;strong&gt;UUID&lt;/strong&gt;, not device name.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Split brain after a crash (Events counter mismatch)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What you see:&lt;/strong&gt; &lt;code&gt;--assemble&lt;/code&gt; refuses because the disks disagree about who has the newest data. &lt;code&gt;mdadm --examine /dev/sdX&lt;/code&gt; shows different &lt;strong&gt;Events&lt;/strong&gt; counts per disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to handle:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assemble using the disks with the &lt;strong&gt;highest, matching&lt;/strong&gt; Events count:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--examine&lt;/span&gt; /dev/sd[a-d] | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'Events|/dev'&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--assemble&lt;/span&gt; /dev/md0 /dev/sda /dev/sdb /dev/sdc   &lt;span class="c"&gt;# the good ones&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;As a last resort, &lt;code&gt;--assemble --force&lt;/code&gt; lets a slightly-behind disk rejoin. Use it only when you understand the risk — it can resurrect stale data. &lt;strong&gt;Take a &lt;code&gt;dd&lt;/code&gt; image of each disk first&lt;/strong&gt; if the data matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Silent data corruption / bit rot
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What you see:&lt;/strong&gt; files are subtly wrong, or a scrub reports a non-zero &lt;code&gt;mismatch_cnt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to handle:&lt;/strong&gt; &lt;code&gt;mdadm&lt;/code&gt; RAID does not checksum data, so it cannot &lt;em&gt;detect&lt;/em&gt; silent corruption on its own — it only knows a disk is present or gone. Scrubs catch &lt;em&gt;mismatches&lt;/em&gt; between data and parity but can't always say which side is right. For true bit-rot protection, use a checksumming filesystem (&lt;strong&gt;ZFS&lt;/strong&gt; or &lt;strong&gt;Btrfs&lt;/strong&gt;) on top of, or instead of, &lt;code&gt;mdadm&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Rebuild is crawling and tanking performance
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What you see:&lt;/strong&gt; a rebuild says &lt;code&gt;finish=600min&lt;/code&gt; and your app is slow because disk I/O is saturated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to handle — tune the rebuild rate:&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;cat&lt;/span&gt; /proc/sys/dev/raid/speed_limit_min   &lt;span class="c"&gt;# current limits (KB/s)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/sys/dev/raid/speed_limit_max

&lt;span class="nb"&gt;echo &lt;/span&gt;200000 | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /proc/sys/dev/raid/speed_limit_min   &lt;span class="c"&gt;# speed up in a window&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;10000  | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /proc/sys/dev/raid/speed_limit_min   &lt;span class="c"&gt;# throttle for production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trade-off: faster rebuild means less time at risk but heavier I/O load. On a busy box, throttle during peak and open it up at night.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Out of space on a "full capacity" array
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What you see:&lt;/strong&gt; writes fail with &lt;code&gt;No space left on device&lt;/code&gt; sooner than expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reminder:&lt;/strong&gt; RAID 0/5/6 report combined capacity, but filesystem metadata, ext4's ~5% reserved blocks, and parity all eat into it. Plan for ~90–95% usable and alert well before 100% — a full filesystem can corrupt in-flight writes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The lab warnings (harmless — don't confuse them with the above)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;may not be suitable as a boot device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Can't boot an OS from this array; fine for data&lt;/td&gt;
&lt;td&gt;Type &lt;code&gt;y&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;contains a ext4 file system ... Proceed anyway?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Leftover FS from reusing a disk&lt;/td&gt;
&lt;td&gt;Type &lt;code&gt;y&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;[3/2] [UU_]&lt;/code&gt; + &lt;code&gt;spare rebuilding&lt;/code&gt; right after creating RAID 5&lt;/td&gt;
&lt;td&gt;Normal creation optimization&lt;/td&gt;
&lt;td&gt;Wait for &lt;code&gt;[UUU]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;No such file or directory&lt;/code&gt; on a test file&lt;/td&gt;
&lt;td&gt;You're looking at an unmounted mountpoint&lt;/td&gt;
&lt;td&gt;Remount&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Cleanup (and stop the AWS bill)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;umount /mnt/raidXX
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--stop&lt;/span&gt; /dev/md0
&lt;span class="nb"&gt;sudo &lt;/span&gt;mdadm &lt;span class="nt"&gt;--zero-superblock&lt;/span&gt; /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1 /dev/nvme4n1
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/mdstat        &lt;span class="c"&gt;# should say: unused devices: &amp;lt;none&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We never wrote &lt;code&gt;/etc/fstab&lt;/code&gt; or &lt;code&gt;mdadm.conf&lt;/code&gt;, so nothing persists across reboot — a clean slate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On AWS:&lt;/strong&gt; EBS volumes bill while they exist, attached or not. Detach the 4 extra volumes, delete them, and terminate the instance if you are done.&lt;/p&gt;




&lt;h2&gt;
  
  
  The one-page summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;RAID&lt;/th&gt;
&lt;th&gt;Min disks&lt;/th&gt;
&lt;th&gt;Usable (4×10 GiB)&lt;/th&gt;
&lt;th&gt;Survives&lt;/th&gt;
&lt;th&gt;Overhead&lt;/th&gt;
&lt;th&gt;Rebuild&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;20 GiB&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;n/a (data lost)&lt;/td&gt;
&lt;td&gt;scratch, max speed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;10 GiB&lt;/td&gt;
&lt;td&gt;any 1&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;copy from mirror&lt;/td&gt;
&lt;td&gt;OS/boot, small critical data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;20 GiB (3 disks)&lt;/td&gt;
&lt;td&gt;any 1&lt;/td&gt;
&lt;td&gt;~33%&lt;/td&gt;
&lt;td&gt;parity math (slow)&lt;/td&gt;
&lt;td&gt;balanced general storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;20 GiB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;any 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;parity math (slowest)&lt;/td&gt;
&lt;td&gt;large arrays, long rebuilds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;20 GiB&lt;/td&gt;
&lt;td&gt;1 always; 2 if different sets&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;copy from partner (fast)&lt;/td&gt;
&lt;td&gt;databases, write-heavy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Mental model:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stripe (0)&lt;/strong&gt; = speed, no safety.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mirror (1)&lt;/strong&gt; = safety, half capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parity (5/6)&lt;/strong&gt; = safety at lower capacity cost; 6 adds a second parity for two-failure tolerance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;10&lt;/strong&gt; = mirror + stripe; speed &lt;em&gt;and&lt;/em&gt; safety, 50% capacity, conditional two-disk survival.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Three production truths worth remembering:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;RAID is &lt;strong&gt;not&lt;/strong&gt; a backup. It survives disk failure, not deletion, corruption, or human error.&lt;/li&gt;
&lt;li&gt;A degraded array with no monitoring is a time bomb — set up &lt;code&gt;mdadm --monitor&lt;/code&gt; on day one.&lt;/li&gt;
&lt;li&gt;Persist the config (&lt;code&gt;mdadm.conf&lt;/code&gt; + &lt;code&gt;update-initramfs&lt;/code&gt; + fstab by UUID), or your array vanishes on the next reboot.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Building each level by hand — and &lt;em&gt;failing disks on purpose&lt;/em&gt; — made RAID click in a way no diagram ever did. But the real lesson is in Part 3: the lab always rebuilds cleanly, while production throws you degraded arrays at 2 AM, rebuilds that saturate I/O, and boxes that won't boot. Knowing the handful of commands above turns those from emergencies into routine.&lt;/p&gt;

&lt;p&gt;AWS makes this experiment cheap: a small EC2 instance plus a few tiny EBS volumes for an hour costs almost nothing, and you tear it all down when you're done. Spin one up and try it yourself — then go set up monitoring on whatever RAID you already run.&lt;/p&gt;

</description>
      <category>storage</category>
      <category>ubuntu</category>
      <category>ec2</category>
      <category>ebs</category>
    </item>
    <item>
      <title>"It Works on My Machine" — How a Shared Redis Database Broke Authentication Across Environments</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Sat, 03 Oct 2026 03:02:23 +0000</pubDate>
      <link>https://dev.to/jayakrishnayadav24/it-works-on-my-machine-how-a-shared-redis-database-broke-authentication-across-environments-ci8</link>
      <guid>https://dev.to/jayakrishnayadav24/it-works-on-my-machine-how-a-shared-redis-database-broke-authentication-across-environments-ci8</guid>
      <description>&lt;p&gt;We've all heard the classic developer line: &lt;em&gt;"But it works on my local machine!"&lt;/em&gt; Usually it's a punchline. This time, it was the starting point of a debugging journey that taught me a lesson I won't forget about environment isolation, Redis, and the subtle ways shared infrastructure can betray you.&lt;/p&gt;

&lt;p&gt;In this post I'll walk through a real production-adjacent incident I ran into recently: the application ran perfectly on local, worked fine when I ran it myself, but kept failing in the Dev and QA environments with authentication errors coming from a third-party vendor. The root cause turned out to be something that looked completely innocent until the very end.&lt;/p&gt;

&lt;p&gt;If you run multiple environments that share backing services, this one is worth reading to the end.&lt;/p&gt;




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

&lt;p&gt;Our application is a fairly standard web service. Without going into proprietary details, the moving parts relevant to this story are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The application&lt;/strong&gt; — runs in multiple environments: local (developer machines), Dev, and QA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL&lt;/strong&gt; — our primary relational data store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; — used for caching and, importantly, for storing session/authentication tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A third-party vendor server&lt;/strong&gt; — we authenticate against it, and it issues tokens we store and reuse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The flow that mattered: a user logs in, we obtain an authentication token from the third-party vendor, and we cache that token in Redis so we don't have to re-authenticate on every request.&lt;/p&gt;

&lt;p&gt;Simple enough. Until it wasn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Symptom
&lt;/h2&gt;

&lt;p&gt;The report came in like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The dev team runs the application locally and everything works. But in Dev and QA, it's not working."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That phrasing is a trap. "Not working" can mean a hundred things. The actual symptom, once I dug in, was an &lt;strong&gt;authentication error returned by the third-party vendor server&lt;/strong&gt; — but only in the shared Dev and QA environments. Local was perfectly happy.&lt;/p&gt;

&lt;p&gt;The confusing part: the error was intermittent and seemed to have nothing to do with what any individual developer was doing. One minute a user was authenticated, the next minute they were getting kicked out, and the vendor was rejecting tokens that should have been valid.&lt;/p&gt;

&lt;p&gt;Classic "works on my machine" territory. Which is exactly why I started where most of us start.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Investigation (and Every Dead End Along the Way)
&lt;/h2&gt;

&lt;p&gt;I want to be honest about the order I actually went through this, because the dead ends are the most instructive part. Debugging is rarely a straight line, and each thing I ruled out narrowed the search.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: "Did someone change the code?"
&lt;/h3&gt;

&lt;p&gt;The first instinct when something works in one place and breaks in another: &lt;strong&gt;what changed?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I checked the recent commits and diffs between what was running locally and what was deployed to Dev and QA. I was looking for any code change — a new feature, a refactor, a tweak to the auth flow, anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; No code changes. The code running locally was effectively the same code running in Dev and QA. If the code is identical, the code probably isn't the problem.&lt;/p&gt;

&lt;p&gt;That's an important deduction: when the same code behaves differently in different places, the difference is almost always in the &lt;strong&gt;environment&lt;/strong&gt;, not the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: "Is it a dependency version mismatch?"
&lt;/h3&gt;

&lt;p&gt;The next usual suspect: &lt;strong&gt;package versions.&lt;/strong&gt; Maybe local had a different version of a library than the deployed environments — a different Redis client, a different HTTP client, a different auth SDK. Version drift between environments is a very common cause of "works here, not there."&lt;/p&gt;

&lt;p&gt;So I compared the dependency versions across environments. Lockfiles, installed package versions, the whole list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Same versions everywhere. Another dead end, but another data point: it's not a dependency problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: "Let me reproduce it myself"
&lt;/h3&gt;

&lt;p&gt;At this point I did the thing you should always do — I stopped theorizing and ran the application myself, locally, trying to reproduce the exact failing flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; It worked perfectly for me. No authentication errors. Everything flowed as expected.&lt;/p&gt;

&lt;p&gt;This is the most frustrating stage of any bug hunt. The thing refuses to break when you're watching it. But it's also a huge clue: if it works in isolation on local but fails in the shared environments, the problem lives in something the shared environments have in common that local doesn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: "Maybe the backing services are misconfigured"
&lt;/h3&gt;

&lt;p&gt;Now I turned my attention to the infrastructure. The application depends on Redis and MySQL. My hypothesis: maybe Redis or MySQL in Dev/QA was &lt;strong&gt;misconfigured&lt;/strong&gt; — wrong settings, wrong memory limits, wrong eviction policy, something.&lt;/p&gt;

&lt;p&gt;I went through the server-side configuration for these services across environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; The server-side configurations were all the same. The Redis and MySQL servers themselves looked correctly and identically configured. Yet another dead end for the "something is broken" theory.&lt;/p&gt;

&lt;p&gt;By now a pattern was forming in my head. Nothing was &lt;em&gt;broken&lt;/em&gt;. Everything was &lt;em&gt;configured the same&lt;/em&gt;. And yet behavior differed. That usually means something is being &lt;strong&gt;shared&lt;/strong&gt; when it shouldn't be.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: "Let me look at the application environment config"
&lt;/h3&gt;

&lt;p&gt;The final place to look was the application's own environment configuration — the &lt;code&gt;.env&lt;/code&gt;-style config that tells each environment &lt;em&gt;which&lt;/em&gt; Redis, &lt;em&gt;which&lt;/em&gt; MySQL, &lt;em&gt;which&lt;/em&gt; vendor endpoint to talk to.&lt;/p&gt;

&lt;p&gt;And here's where it clicked.&lt;/p&gt;

&lt;p&gt;I looked at where the application was pointed for Redis, and I saw that &lt;strong&gt;Dev and QA were pointed at the same Redis instance and the same Redis database.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That was the smoking gun.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Root Cause
&lt;/h2&gt;

&lt;p&gt;Here's what was actually happening.&lt;/p&gt;

&lt;p&gt;Redis supports multiple logical databases within a single instance (numbered &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, and so on by default). Our Dev and QA environments were both pointed at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;same Redis host&lt;/strong&gt;, and&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;same Redis logical database&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because Redis stores data as keys, and both environments used the &lt;strong&gt;same key naming&lt;/strong&gt; for authentication tokens, the two environments were writing to and reading from the &lt;strong&gt;same slots&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now layer the authentication flow on top of that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A developer logs into &lt;strong&gt;Dev&lt;/strong&gt;. The app authenticates against the third-party vendor, gets a token, and stores it in Redis under some key — say, a key derived from the user's identity.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;same user&lt;/strong&gt; (or the same key) logs into &lt;strong&gt;QA&lt;/strong&gt;. The app authenticates again, gets a &lt;em&gt;new&lt;/em&gt; token from the vendor, and &lt;strong&gt;overwrites the same Redis key&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Back in Dev, the next request pulls the token from Redis — but now it's holding the &lt;strong&gt;QA session's token&lt;/strong&gt;, or a token that the vendor has effectively superseded.&lt;/li&gt;
&lt;li&gt;The third-party vendor sees a token that doesn't match the session it expects and &lt;strong&gt;rejects it with an authentication error.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In other words: &lt;strong&gt;logging into one environment was effectively logging the user out of the other.&lt;/strong&gt; The two environments were stomping all over each other's tokens because they shared the same Redis database.&lt;/p&gt;

&lt;p&gt;That perfectly explained every observation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Why no code change mattered&lt;/strong&gt; — the code was correct; the data store was shared.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why versions didn't matter&lt;/strong&gt; — it was never a dependency issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it worked on local&lt;/strong&gt; — local pointed at its own isolated Redis, so nothing else was overwriting its tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it worked when I ran it myself&lt;/strong&gt; — I was the only one touching those keys at that moment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it was intermittent&lt;/strong&gt; — it only broke when activity in Dev and QA overlapped on the same user/keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why the vendor threw the auth error&lt;/strong&gt; — the token it received had been replaced by the other environment's token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The services weren't misconfigured in the sense of being &lt;em&gt;broken&lt;/em&gt;. They were misconfigured in the sense of being &lt;strong&gt;shared across environments that should have been isolated.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;The fix was refreshingly small once the cause was understood: &lt;strong&gt;give each environment its own Redis database.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Redis makes this easy because a single instance exposes multiple numbered logical databases. By changing the Redis database index (and ideally the host) per environment in the application's environment config, each environment got its own isolated keyspace.&lt;/p&gt;

&lt;p&gt;Conceptually, the configuration went from this:&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;# Dev&lt;/span&gt;
&lt;span class="nv"&gt;REDIS_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;shared-redis
&lt;span class="nv"&gt;REDIS_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="c"&gt;# QA&lt;/span&gt;
&lt;span class="nv"&gt;REDIS_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;shared-redis
&lt;span class="nv"&gt;REDIS_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0      &lt;span class="c"&gt;# &amp;lt;-- same instance AND same DB as Dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dev
REDIS_HOST=shared-redis
REDIS_DB=0

# QA
REDIS_HOST=shared-redis
REDIS_DB=1      # &amp;lt;-- separate logical database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After separating the Redis databases per environment, the token collisions stopped. Logging into Dev no longer touched QA's tokens, the vendor stopped rejecting tokens, and the authentication errors disappeared entirely.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on logical databases vs. separate instances:&lt;/strong&gt; Using separate Redis logical databases (&lt;code&gt;REDIS_DB&lt;/code&gt;) solved our immediate problem and is a perfectly valid quick fix. For stronger isolation, the better long-term approach is a &lt;strong&gt;completely separate Redis instance&lt;/strong&gt; per environment, since logical databases still share the same memory, CPU, eviction policy, and &lt;code&gt;FLUSHALL&lt;/code&gt; blast radius. Another robust option is &lt;strong&gt;key prefixing / namespacing&lt;/strong&gt; (for example, prefixing every key with the environment name) so collisions are impossible even on a shared database. Pick the level of isolation that matches your risk tolerance.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why This Was So Easy to Miss
&lt;/h2&gt;

&lt;p&gt;This is the kind of bug that hides in plain sight, and I think it's worth reflecting on &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Nothing was broken.&lt;/strong&gt; Every service was up. Every config "looked" correct in isolation. There was no stack trace pointing at Redis — the error surfaced at the &lt;em&gt;third-party vendor&lt;/em&gt;, which is as far downstream from the real cause as you can get.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The error was misleading.&lt;/strong&gt; An authentication failure from an external vendor makes you suspect credentials, tokens, network, or the vendor itself — not your cache layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It only manifested under concurrency.&lt;/strong&gt; When one environment was quiet, the other worked fine. That's why it never reproduced on demand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared infrastructure is often invisible in day-to-day work.&lt;/strong&gt; You rarely stop and ask, "Wait, are these two environments literally the same Redis database?" You assume isolation that was never actually there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The real lesson is that the error message pointed at the symptom (vendor auth rejection), while the cause was several layers upstream (shared cache state). Good debugging means following the chain backward past the first plausible-looking culprit.&lt;/p&gt;




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

&lt;p&gt;Here are the takeaways I'm carrying forward, and that I'd offer to anyone running multi-environment systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Environments must be isolated — all the way down
&lt;/h3&gt;

&lt;p&gt;It's not enough to have separate application deployments. Every backing service — Redis, MySQL, message queues, object storage, search indexes — needs to be isolated per environment, or at minimum &lt;strong&gt;namespaced&lt;/strong&gt; so there's no chance of cross-environment collision. Shared state between environments is a latent bug waiting for enough concurrent traffic to surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. "Same code, different behavior" means look at the environment
&lt;/h3&gt;

&lt;p&gt;When identical code behaves differently across environments, the bug is almost never in the code. It's in configuration, data, infrastructure, or state. Save yourself time by ruling out code quickly and moving to the environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Follow the error upstream, past the obvious suspect
&lt;/h3&gt;

&lt;p&gt;The vendor threw the authentication error, but the vendor wasn't the problem. The first component that &lt;em&gt;reports&lt;/em&gt; an error is frequently just the victim of something further up the chain. Keep asking "but why did &lt;em&gt;that&lt;/em&gt; happen?" until you reach a cause that explains &lt;strong&gt;all&lt;/strong&gt; your observations, not just some of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The config file deserves the same scrutiny as the code
&lt;/h3&gt;

&lt;p&gt;I checked code, versions, and server-side service configuration before I looked carefully at the application's environment config — which is exactly where the problem was. Environment/config files are code. Diff them across environments as a first-class debugging step.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Reproduce in isolation to localize the problem
&lt;/h3&gt;

&lt;p&gt;The fact that it worked on local and worked when I ran it alone was itself the key insight: the problem only existed where state was &lt;em&gt;shared&lt;/em&gt;. "Can I reproduce it in isolation?" is a powerful question — the answer tells you whether the bug is in the component or in its surroundings.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Design keyspaces defensively
&lt;/h3&gt;

&lt;p&gt;Even if you believe your environments are isolated today, prefix your cache keys with the environment name. It costs almost nothing and makes an entire category of cross-environment collisions structurally impossible. Defense in depth applies to cache keys too.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Simple Mental Checklist for "Works Here, Not There" Bugs
&lt;/h2&gt;

&lt;p&gt;Next time you hit a bug that works in one place but fails in another, run through this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Did the code change?&lt;/strong&gt; Diff it. If it's identical, move on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Did the dependencies change?&lt;/strong&gt; Compare versions and lockfiles. If identical, move on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can you reproduce it in isolation?&lt;/strong&gt; If it only breaks in a shared environment, suspect shared state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are the backing services misconfigured?&lt;/strong&gt; Check server-side config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where is each environment actually pointed?&lt;/strong&gt; Check the application's env/config — host, port, database index, credentials, endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is anything being shared that shouldn't be?&lt;/strong&gt; Same database, same keyspace, same bucket, same queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow the error upstream&lt;/strong&gt; until you find a cause that explains every symptom.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In my case, the answer was hiding at step 5 and 6 the whole time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;The bug felt enormous while I was chasing it — authentication failing in Dev and QA, a third-party vendor rejecting tokens, nothing reproducing locally. The fix was one line of configuration: point each environment at its own Redis database.&lt;/p&gt;

&lt;p&gt;That contrast is the whole story of debugging distributed systems. The symptoms are loud and scattered; the cause is quiet and specific. The work is in patiently ruling things out until the quiet cause has nowhere left to hide.&lt;/p&gt;

&lt;p&gt;If you take one thing away: &lt;strong&gt;isolate your environments completely, and never assume that two environments pointed at "Redis" are pointed at &lt;em&gt;different&lt;/em&gt; Redis.&lt;/strong&gt; Verify it. Because the day your traffic overlaps, that shared database will find you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you run into a similar shared-infrastructure gotcha? I'd love to hear how it surfaced for you and how you tracked it down. Drop it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>redis</category>
      <category>debugging</category>
      <category>containers</category>
    </item>
    <item>
      <title>How I Eliminated Lambda Deployment Downtime with Terraform and Blue-Green Deployments</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Sat, 06 Jun 2026 11:31:09 +0000</pubDate>
      <link>https://dev.to/aws-builders/how-i-eliminated-lambda-deployment-downtime-with-terraform-and-blue-green-deployments-3hji</link>
      <guid>https://dev.to/aws-builders/how-i-eliminated-lambda-deployment-downtime-with-terraform-and-blue-green-deployments-3hji</guid>
      <description>&lt;p&gt;I Got Tired of Lambda Deployment Headaches, So I Built My Own Blue-Green Deployment System&lt;/p&gt;

&lt;p&gt;We've all had that moment.&lt;/p&gt;

&lt;p&gt;You push a new Lambda version to production, hit deploy, and then spend the next few minutes refreshing dashboards and logs hoping nothing breaks.&lt;/p&gt;

&lt;p&gt;I've been there more times than I'd like to admit.&lt;/p&gt;

&lt;p&gt;Sometimes deployments worked perfectly. Other times I would see unexpected 503 errors, traffic routing issues, or situations where rolling back wasn't as fast as I wanted. The Lambda deployment itself wasn't usually the problem. The challenge was everything around it—traffic routing, validation, and rollback.&lt;/p&gt;

&lt;p&gt;After hitting the same deployment issues multiple times, I decided to build a deployment model that matched how I actually wanted releases to work.&lt;/p&gt;

&lt;p&gt;The result was a Terraform-driven blue-green deployment framework for AWS Lambda that gives me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero-downtime deployments&lt;/li&gt;
&lt;li&gt;IP-based canary testing&lt;/li&gt;
&lt;li&gt;Weighted traffic shifting&lt;/li&gt;
&lt;li&gt;Instant rollback&lt;/li&gt;
&lt;li&gt;No CodeDeploy&lt;/li&gt;
&lt;li&gt;No Route53 dependency&lt;/li&gt;
&lt;li&gt;No hardcoded VPC IDs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is controlled through Terraform.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem I Wanted to Solve
&lt;/h2&gt;

&lt;p&gt;AWS already provides deployment options for Lambda through aliases and CodeDeploy.&lt;/p&gt;

&lt;p&gt;For many teams, that's enough.&lt;/p&gt;

&lt;p&gt;But in my case, there were a few limitations that kept bothering me.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. I Wanted to Test Production Before Users Did
&lt;/h3&gt;

&lt;p&gt;When deploying a new version, I wanted a way to access the new release from my own machine while everyone else continued using the stable version.&lt;/p&gt;

&lt;p&gt;With standard Lambda deployments, that wasn't straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Rollbacks Needed To Be Instant
&lt;/h3&gt;

&lt;p&gt;If something goes wrong after deployment, I don't want to wait for another deployment process.&lt;/p&gt;

&lt;p&gt;I want traffic to switch back immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. I Wanted Full Traffic Control
&lt;/h3&gt;

&lt;p&gt;Sometimes I want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10% rollout&lt;/li&gt;
&lt;li&gt;25% rollout&lt;/li&gt;
&lt;li&gt;50% rollout&lt;/li&gt;
&lt;li&gt;75% rollout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not just predefined deployment strategies.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. I Wanted Less Moving Parts
&lt;/h3&gt;

&lt;p&gt;CodeDeploy works, but it introduces additional services, configurations, hooks, and deployment definitions.&lt;/p&gt;

&lt;p&gt;For this project, I wanted something simpler.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;Instead of thinking about Lambda deployments as version updates, I started thinking about them the same way ECS blue-green deployments work.&lt;/p&gt;

&lt;p&gt;What if two Lambda environments always existed?&lt;/p&gt;

&lt;p&gt;One would be stable.&lt;/p&gt;

&lt;p&gt;One would contain the new release.&lt;/p&gt;

&lt;p&gt;Traffic could simply move between them.&lt;/p&gt;

&lt;p&gt;That led to this architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blue Lambda (current production)&lt;/li&gt;
&lt;li&gt;Green Lambda (new version)&lt;/li&gt;
&lt;li&gt;Blue Target Group&lt;/li&gt;
&lt;li&gt;Green Target Group&lt;/li&gt;
&lt;li&gt;ALB Listener Rules controlling traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important detail is that both environments always exist.&lt;/p&gt;

&lt;p&gt;Nothing is destroyed.&lt;/p&gt;

&lt;p&gt;Nothing is recreated.&lt;/p&gt;

&lt;p&gt;Traffic is simply redirected.&lt;/p&gt;

&lt;p&gt;That's what eliminates deployment interruptions.&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%2Fq8jvqrndro3isbj3c8rk.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%2Fq8jvqrndro3isbj3c8rk.png" alt="Architecture Diagram" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Everything Is Controlled Through Terraform
&lt;/h2&gt;

&lt;p&gt;The deployment workflow is driven through a simple Terraform configuration.&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;nginx-lambda&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;family&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nginx-svc"&lt;/span&gt;

  &lt;span class="nx"&gt;blue_image&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;".../nginx-lambda:blue"&lt;/span&gt;
  &lt;span class="nx"&gt;green_image&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;".../nginx-lambda:green"&lt;/span&gt;

  &lt;span class="nx"&gt;active_color&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Whenever I want to change deployment behavior, I update the values and run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;No deployment pipelines.&lt;/p&gt;

&lt;p&gt;No manual traffic switching.&lt;/p&gt;

&lt;p&gt;No complicated release process.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1: Canary Testing
&lt;/h2&gt;

&lt;p&gt;The first thing I do after deploying a new version is test it myself.&lt;/p&gt;

&lt;p&gt;I enable canary mode:&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;activate_canary&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;green_weight&lt;/span&gt;    &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbs5scpf3q0wxyesldnw7.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%2Fbs5scpf3q0wxyesldnw7.png" alt="Canary Configuration" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This creates a higher-priority ALB rule.&lt;/p&gt;

&lt;p&gt;If requests come from my whitelisted IP address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;My IP → Green Version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everyone else still reaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Users → Blue Version
&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%2Fncmk3hoferk7m4k9nm41.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%2Fncmk3hoferk7m4k9nm41.png" alt="Canary Test Result" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This allows me to validate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application functionality&lt;/li&gt;
&lt;li&gt;API responses&lt;/li&gt;
&lt;li&gt;Headers&lt;/li&gt;
&lt;li&gt;Authentication flows&lt;/li&gt;
&lt;li&gt;Database interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All in production.&lt;/p&gt;

&lt;p&gt;Without exposing the new version to actual users.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2: Gradual Traffic Shifting
&lt;/h2&gt;

&lt;p&gt;Once canary testing looks good, I begin moving real traffic.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;green_weight&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ayr6v1vf96jub6dv293.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%2F5ayr6v1vf96jub6dv293.png" alt="Weighted Rollout Configuration" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50% → Blue
50% → Green
&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%2F1l7h9mdb99h0lygph8ai.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%2F1l7h9mdb99h0lygph8ai.png" alt="50-50 Traffic Distribution" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Application Load Balancer handles traffic distribution automatically.&lt;/p&gt;

&lt;p&gt;Because both target groups already exist and already have Lambda functions attached, traffic shifting happens smoothly.&lt;/p&gt;

&lt;p&gt;No downtime.&lt;/p&gt;

&lt;p&gt;No target registration delays.&lt;/p&gt;

&lt;p&gt;No deployment gaps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 3: Full Promotion
&lt;/h2&gt;

&lt;p&gt;When confidence is high enough, I move all traffic to Green.&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;green_weight&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh0u56rjwhc93qiq9rylt.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%2Fh0u56rjwhc93qiq9rylt.png" alt="Full Promotion Configuration" width="799" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100% Users → Green
&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%2F1ruroys6xbgatbjdbzy5.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%2F1ruroys6xbgatbjdbzy5.png" alt="Full Promotion Result" width="799" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But here's the part I like most.&lt;/p&gt;

&lt;p&gt;Blue still exists.&lt;/p&gt;

&lt;p&gt;It's sitting there untouched.&lt;/p&gt;

&lt;p&gt;Ready for rollback.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 4: Instant Rollback
&lt;/h2&gt;

&lt;p&gt;Every deployment system claims to support rollback.&lt;/p&gt;

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

&lt;p&gt;How fast?&lt;/p&gt;

&lt;p&gt;With this setup, rollback is literally:&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;green_weight&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzm539y9id8bpb50z5mkl.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%2Fzm539y9id8bpb50z5mkl.png" alt="Rollback Configuration" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apply Terraform.&lt;/p&gt;

&lt;p&gt;Traffic immediately returns to Blue.&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%2Fk2e9xwphiewrr4cq136b.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%2Fk2e9xwphiewrr4cq136b.png" alt="Instant Rollback" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No redeployment.&lt;/p&gt;

&lt;p&gt;No waiting for Lambda versions.&lt;/p&gt;

&lt;p&gt;No waiting for target group registration.&lt;/p&gt;

&lt;p&gt;No waiting for containers to start.&lt;/p&gt;

&lt;p&gt;The previous stable environment never disappeared.&lt;/p&gt;




&lt;h2&gt;
  
  
  What About The Next Release?
&lt;/h2&gt;

&lt;p&gt;This was actually one of the hardest design problems.&lt;/p&gt;

&lt;p&gt;Let's say:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blue = v1&lt;/li&gt;
&lt;li&gt;Green = v2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After promoting v2, what happens when v3 arrives?&lt;/p&gt;

&lt;p&gt;The answer is surprisingly simple.&lt;/p&gt;

&lt;p&gt;Rotate the images.&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;blue_image&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;".../nginx-lambda:green"&lt;/span&gt;
&lt;span class="nx"&gt;green_image&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;".../nginx-lambda:yellow"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxo6a44j2n06fyuhaici5.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%2Fxo6a44j2n06fyuhaici5.png" alt="v3 Deployment Configuration" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blue  = v2 (stable)
Green = v3 (candidate)
&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%2Fz3awew6p5d1bsfvwyolq.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%2Fz3awew6p5d1bsfvwyolq.png" alt="v3 Canary Testing" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The deployment cycle repeats.&lt;/p&gt;

&lt;p&gt;Canary.&lt;/p&gt;

&lt;p&gt;Weighted rollout.&lt;/p&gt;

&lt;p&gt;Promotion.&lt;/p&gt;

&lt;p&gt;Rollback if needed.&lt;/p&gt;

&lt;p&gt;Forever.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Stopped Seeing 503 Errors
&lt;/h2&gt;

&lt;p&gt;Before building this, I experimented with several approaches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda Alias Switching
&lt;/h3&gt;

&lt;p&gt;This occasionally introduced timing issues during updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Target Groups
&lt;/h3&gt;

&lt;p&gt;Using conditional resources sometimes caused Terraform to destroy resources before listener rules updated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Permission Propagation Delays
&lt;/h3&gt;

&lt;p&gt;Lambda permissions occasionally weren't fully propagated before attachments occurred.&lt;/p&gt;

&lt;p&gt;The common problem was simple:&lt;/p&gt;

&lt;p&gt;Resources were being created and destroyed during deployment.&lt;/p&gt;

&lt;p&gt;Eventually I realized the best deployment is often the one that changes the least.&lt;/p&gt;

&lt;p&gt;So I stopped changing infrastructure during releases.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Blue always exists&lt;/li&gt;
&lt;li&gt;Green always exists&lt;/li&gt;
&lt;li&gt;Target groups always exist&lt;/li&gt;
&lt;li&gt;Attachments always exist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only thing that changes is traffic routing.&lt;/p&gt;

&lt;p&gt;That's why the process is so reliable.&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/jayakrishnayadav24/lambda-blue-green-deployment" rel="noopener noreferrer"&gt;https://github.com/jayakrishnayadav24/lambda-blue-green-deployment&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;The biggest lesson from building this wasn't about Lambda or Terraform.&lt;/p&gt;

&lt;p&gt;It was about reducing moving parts.&lt;/p&gt;

&lt;p&gt;Instead of making deployments smarter, I made them simpler.&lt;/p&gt;

&lt;p&gt;Both environments stay alive.&lt;/p&gt;

&lt;p&gt;Traffic moves gradually.&lt;/p&gt;

&lt;p&gt;Rollback is immediate.&lt;/p&gt;

&lt;p&gt;And I can personally validate a release before customers ever touch it.&lt;/p&gt;

&lt;p&gt;After several production deployments using this pattern, it's become one of my favorite deployment strategies for Lambda workloads.&lt;/p&gt;

&lt;p&gt;If you've been fighting deployment-related downtime or complicated release workflows, you might find this approach useful too.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>lambda</category>
      <category>devops</category>
    </item>
    <item>
      <title>What If You Need Two ArgoCD Instances in One EKS Cluster?</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Sat, 30 May 2026 08:26:58 +0000</pubDate>
      <link>https://dev.to/aws-builders/what-if-you-need-two-argocd-instances-in-one-eks-cluster-57id</link>
      <guid>https://dev.to/aws-builders/what-if-you-need-two-argocd-instances-in-one-eks-cluster-57id</guid>
      <description>&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%2F4h3333pda5omttfimuou.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%2F4h3333pda5omttfimuou.png" alt="Cover Image" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most ArgoCD tutorials start the same way:&lt;/p&gt;

&lt;p&gt;Deploy ArgoCD.&lt;/p&gt;

&lt;p&gt;Connect a Git repository.&lt;/p&gt;

&lt;p&gt;Create an Application.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;But what happens when multiple teams start sharing the same Kubernetes cluster?&lt;/p&gt;

&lt;p&gt;Recently I was exploring a scenario where a single Amazon EKS cluster needed to support two different ArgoCD environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Custom ArgoCD instance for the Platform Team&lt;/li&gt;
&lt;li&gt;A Managed ArgoCD instance for Application Teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Custom ArgoCD would manage infrastructure components such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cert-manager&lt;/li&gt;
&lt;li&gt;external-dns&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;ingress controllers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While the Managed ArgoCD would be used by application teams to deploy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;frontends&lt;/li&gt;
&lt;li&gt;microservices&lt;/li&gt;
&lt;li&gt;business applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The challenge was figuring out how to keep both environments isolated while still sharing the same EKS cluster, Cognito User Pool, and AWS Application Load Balancer.&lt;/p&gt;

&lt;p&gt;At first, I thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why not just use a single ArgoCD instance?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The more I thought about it, the more questions came up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do we separate responsibilities?&lt;/li&gt;
&lt;li&gt;How do we avoid accidental changes?&lt;/li&gt;
&lt;li&gt;How do we provide SSO?&lt;/li&gt;
&lt;li&gt;How do we keep costs under control?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That led me down an interesting path:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can we run two ArgoCD instances inside the same EKS cluster, authenticate both using Cognito SSO, and still share the same ALB?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Turns out we can.&lt;/p&gt;

&lt;p&gt;This article walks through the architecture, challenges, lessons learned, and a few surprises along the way.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Scenario
&lt;/h1&gt;

&lt;p&gt;Imagine you have a Kubernetes cluster used by multiple teams.&lt;/p&gt;

&lt;p&gt;The Platform Team owns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cert-manager&lt;/li&gt;
&lt;li&gt;external-dns&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;ingress controllers&lt;/li&gt;
&lt;li&gt;cluster add-ons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Application Teams own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;frontends&lt;/li&gt;
&lt;li&gt;microservices&lt;/li&gt;
&lt;li&gt;business applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single ArgoCD instance technically works.&lt;/p&gt;

&lt;p&gt;But should both groups operate from the same GitOps control plane?&lt;/p&gt;

&lt;p&gt;For smaller environments, probably yes.&lt;/p&gt;

&lt;p&gt;For larger environments, things start getting messy.&lt;/p&gt;

&lt;p&gt;Different permissions.&lt;/p&gt;

&lt;p&gt;Different ownership boundaries.&lt;/p&gt;

&lt;p&gt;Different operational responsibilities.&lt;/p&gt;

&lt;p&gt;That was the motivation behind splitting ArgoCD into two instances.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Goal
&lt;/h1&gt;

&lt;p&gt;The architecture I wanted looked like this:&lt;br&gt;
&lt;/p&gt;

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

├── ArgoCD (Platform Team) - Custom argocd
│     ├── cert-manager
│     ├── external-dns
│     └── monitoring
│
└── ArgoCD (Application Teams) - Managed argocd
      ├── frontend
      ├── backend
      └── business applications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on top of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single Sign-On&lt;/li&gt;
&lt;li&gt;Fully automated provisioning&lt;/li&gt;
&lt;li&gt;Infrastructure as Code&lt;/li&gt;
&lt;li&gt;Minimal AWS cost&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  The Final Architecture
&lt;/h1&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%2F4h3333pda5omttfimuou.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%2F4h3333pda5omttfimuou.png" alt="Architecture Diagram" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final design consisted of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer
    |
    | HTTPS
    v

Shared AWS ALB
(group.name = argocd)

    |
    +---------------------+
    |                     |
    v                     v

argocd              argocd2
Namespace:          Namespace:
argocd              argocd-managed

Platform Team       Application Teams

    |                     |
    +----------+----------+
               |
               v

AWS Cognito User Pool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both ArgoCD instances run inside the same EKS cluster.&lt;/p&gt;

&lt;p&gt;The difference is that each lives in its own namespace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;argocd
argocd-managed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives logical separation without requiring another cluster.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Cognito?
&lt;/h1&gt;

&lt;p&gt;Before choosing Cognito, I looked at AWS IAM Identity Center.&lt;/p&gt;

&lt;p&gt;Identity Center is generally the preferred enterprise solution.&lt;/p&gt;

&lt;p&gt;However, for this learning exercise I wanted everything automated using Terraform.&lt;/p&gt;

&lt;p&gt;My ideal deployment process was:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and automatically create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User Pool&lt;/li&gt;
&lt;li&gt;App Clients&lt;/li&gt;
&lt;li&gt;Groups&lt;/li&gt;
&lt;li&gt;Users&lt;/li&gt;
&lt;li&gt;Passwords&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without opening the AWS Console.&lt;/p&gt;

&lt;p&gt;Cognito made that surprisingly easy.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building the Authentication Layer
&lt;/h1&gt;

&lt;p&gt;The first step was creating a Cognito User Pool.&lt;/p&gt;

&lt;p&gt;Each ArgoCD instance gets its own App Client.&lt;/p&gt;

&lt;p&gt;Visually, it looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cognito User Pool

├── App Client
│     └── ArgoCD Platform
│
└── App Client
      └── ArgoCD Managed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what it looked like in AWS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqbeo900l4bzt9w763kq.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%2Fqqbeo900l4bzt9w763kq.png" alt="Cognito User Pool showing app clients and ArgoCDAdmins group" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing I particularly liked was eliminating manual user creation.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;User Pool&lt;/li&gt;
&lt;li&gt;Domain&lt;/li&gt;
&lt;li&gt;App Clients&lt;/li&gt;
&lt;li&gt;Groups&lt;/li&gt;
&lt;li&gt;Admin User&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything becomes Infrastructure as Code.&lt;/p&gt;

&lt;p&gt;No temporary passwords.&lt;/p&gt;

&lt;p&gt;No manual console work.&lt;/p&gt;

&lt;p&gt;No forgotten setup steps.&lt;/p&gt;




&lt;h1&gt;
  
  
  Connecting ArgoCD to Cognito
&lt;/h1&gt;

&lt;p&gt;ArgoCD supports OIDC natively.&lt;/p&gt;

&lt;p&gt;That means Cognito can act as the identity provider.&lt;/p&gt;

&lt;p&gt;The authentication flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  |
  v
ArgoCD
  |
  v
Cognito Hosted UI
  |
  v
Authentication
  |
  v
OIDC Callback
  |
  v
ArgoCD Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once logged in, ArgoCD reads the Cognito groups from the token and maps them to ArgoCD roles.&lt;/p&gt;

&lt;p&gt;That means administrators are controlled centrally through Cognito instead of managing users inside ArgoCD itself.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Shared ALB Experiment
&lt;/h1&gt;

&lt;p&gt;This was probably the most interesting part.&lt;/p&gt;

&lt;p&gt;Initially I considered creating two separate Application Load Balancers.&lt;/p&gt;

&lt;p&gt;One for each ArgoCD instance.&lt;/p&gt;

&lt;p&gt;That would work.&lt;/p&gt;

&lt;p&gt;But it also means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More AWS resources&lt;/li&gt;
&lt;li&gt;More management&lt;/li&gt;
&lt;li&gt;More cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I remembered the AWS Load Balancer Controller supports Ingress Groups.&lt;/p&gt;

&lt;p&gt;That changed everything.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALB #1 → ArgoCD
ALB #2 → ArgoCD Managed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One ALB
Two Hostnames
Two ArgoCD Instances
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ALB creates listener rules based on the hostname.&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%2F1g8zzrafbecsgb9fqxf7.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%2F1g8zzrafbecsgb9fqxf7.png" alt="ALB listener rules showing host-based routing for both ArgoCD instances" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And from Kubernetes:&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%2Foai143ied4666bb68i13.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%2Foai143ied4666bb68i13.png" alt="kubectl get ingress showing both ingresses sharing the same ALB address" width="798" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both ingress resources point to the same ALB.&lt;/p&gt;

&lt;p&gt;The routing happens automatically based on the Host header.&lt;/p&gt;

&lt;p&gt;A nice cost optimization with very little additional complexity.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Problem Nobody Warned Me About
&lt;/h1&gt;

&lt;p&gt;The first ArgoCD deployment worked perfectly.&lt;/p&gt;

&lt;p&gt;The second one failed immediately.&lt;/p&gt;

&lt;p&gt;The error looked something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customresourcedefinitions.apiextensions.k8s.io already exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first it wasn't obvious.&lt;/p&gt;

&lt;p&gt;Then it clicked.&lt;/p&gt;

&lt;p&gt;ArgoCD installs CRDs.&lt;/p&gt;

&lt;p&gt;CRDs are cluster-scoped resources.&lt;/p&gt;

&lt;p&gt;The first installation already created them.&lt;/p&gt;

&lt;p&gt;The second installation attempted to create them again.&lt;/p&gt;

&lt;p&gt;Kubernetes refused.&lt;/p&gt;

&lt;p&gt;The fix was simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;crds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I disabled CRD installation for the second ArgoCD instance, everything worked.&lt;/p&gt;

&lt;p&gt;It was one of those issues that takes a while to understand but only seconds to fix.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Login Experience
&lt;/h1&gt;

&lt;p&gt;After deployment, the user experience was exactly what I wanted.&lt;/p&gt;

&lt;p&gt;First, users see the ArgoCD login screen.&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%2Fywksma4y8v0x1ph8y09f.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%2Fywksma4y8v0x1ph8y09f.png" alt="ArgoCD login page with Log in via Cognito button" width="799" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Selecting the Cognito login option redirects users to the Hosted UI.&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%2Fet7kq8fyy1gucjogl3t8.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%2Fet7kq8fyy1gucjogl3t8.png" alt="Cognito Hosted UI login form after redirect" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After successful authentication, users land directly inside ArgoCD.&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%2F6300yryza29u6ahdggwr.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%2F6300yryza29u6ahdggwr.png" alt="ArgoCD dashboard after successful SSO login showing admin user" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcydnko4ef6iqovo0fj1r.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%2Fcydnko4ef6iqovo0fj1r.png" alt="ArgoCD dashboard after successful SSO login showing admin user" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No local accounts.&lt;/p&gt;

&lt;p&gt;No shared admin credentials.&lt;/p&gt;

&lt;p&gt;No Kubernetes secret lookups.&lt;/p&gt;

&lt;p&gt;Just SSO.&lt;/p&gt;




&lt;h1&gt;
  
  
  Another Small Gotcha
&lt;/h1&gt;

&lt;p&gt;While testing Applications, I ran into this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app path is not a directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I had accidentally pointed ArgoCD to a YAML file instead of a directory.&lt;/p&gt;

&lt;p&gt;ArgoCD expects:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manifests/app.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A small detail, but one that can waste a surprising amount of time when you're learning.&lt;/p&gt;




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

&lt;p&gt;The most valuable lesson wasn't about Cognito.&lt;/p&gt;

&lt;p&gt;It wasn't about Terraform.&lt;/p&gt;

&lt;p&gt;It wasn't even about ArgoCD.&lt;/p&gt;

&lt;p&gt;It was about ownership.&lt;/p&gt;

&lt;p&gt;As environments grow, separating responsibilities becomes more important than simply making things work.&lt;/p&gt;

&lt;p&gt;Running a Custom ArgoCD for platform operations and a Managed ArgoCD for application teams gave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better operational boundaries&lt;/li&gt;
&lt;li&gt;Cleaner RBAC&lt;/li&gt;
&lt;li&gt;Clear ownership between teams&lt;/li&gt;
&lt;li&gt;Reduced risk of accidental changes&lt;/li&gt;
&lt;li&gt;Independent GitOps workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The platform team can safely manage infrastructure applications while application teams operate in their own ArgoCD environment without interfering with cluster-level services.&lt;/p&gt;

&lt;p&gt;And because both instances share a single ALB, the cost impact remained minimal.&lt;/p&gt;




&lt;h1&gt;
  
  
  Would I Do This Again?
&lt;/h1&gt;

&lt;p&gt;For a small team?&lt;/p&gt;

&lt;p&gt;Probably not.&lt;/p&gt;

&lt;p&gt;A single ArgoCD instance is easier to manage.&lt;/p&gt;

&lt;p&gt;For organizations with dedicated platform teams and application teams?&lt;/p&gt;

&lt;p&gt;Absolutely.&lt;/p&gt;

&lt;p&gt;The pattern scales nicely, provides cleaner ownership boundaries, and integrates well with SSO.&lt;/p&gt;

&lt;p&gt;Most importantly, the entire environment can be created with a single:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is exactly what I wanted from the beginning.&lt;/p&gt;




&lt;p&gt;If you've experimented with multiple ArgoCD instances or different GitOps multi-tenancy approaches, I'd love to hear how you've solved it.&lt;/p&gt;

&lt;p&gt;Happy GitOps 🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/jayakrishnayadav24/eks-argocd-cognito-sso" rel="noopener noreferrer"&gt;https://github.com/jayakrishnayadav24/eks-argocd-cognito-sso&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>argocd</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Zero-Downtime Blue-Green and IP-Based Canary Deployments on ECS Fargate</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Sat, 23 May 2026 08:36:49 +0000</pubDate>
      <link>https://dev.to/aws-builders/zero-downtime-blue-green-and-ip-based-canary-deployments-on-ecs-fargate-4ea8</link>
      <guid>https://dev.to/aws-builders/zero-downtime-blue-green-and-ip-based-canary-deployments-on-ecs-fargate-4ea8</guid>
      <description>&lt;p&gt;Most ECS blue-green deployment tutorials eventually lead to the same stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS CodeDeploy&lt;/li&gt;
&lt;li&gt;Deployment groups&lt;/li&gt;
&lt;li&gt;AppSpec files&lt;/li&gt;
&lt;li&gt;Lifecycle hooks&lt;/li&gt;
&lt;li&gt;Weighted traffic shifting&lt;/li&gt;
&lt;li&gt;Complex rollback orchestration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And while CodeDeploy works, I kept running into one practical limitation during real deployments:&lt;/p&gt;

&lt;p&gt;I couldn’t let my internal team validate a new release on the &lt;em&gt;actual production URL&lt;/em&gt; before exposing it to customers.&lt;/p&gt;

&lt;p&gt;That became the entire motivation behind this setup.&lt;/p&gt;

&lt;p&gt;I didn’t want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;separate staging domains&lt;/li&gt;
&lt;li&gt;duplicate ALBs&lt;/li&gt;
&lt;li&gt;temporary preview environments&lt;/li&gt;
&lt;li&gt;“almost production” testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted something much simpler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal users should see the new version first&lt;/li&gt;
&lt;li&gt;Customers should continue seeing the stable version&lt;/li&gt;
&lt;li&gt;Both should use the same production domain&lt;/li&gt;
&lt;li&gt;Rollback should be immediate&lt;/li&gt;
&lt;li&gt;Deployments should remain fully zero downtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built a Terraform-driven deployment workflow using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ECS Fargate&lt;/li&gt;
&lt;li&gt;Application Load Balancer (ALB)&lt;/li&gt;
&lt;li&gt;ALB listener priorities&lt;/li&gt;
&lt;li&gt;Source IP routing&lt;/li&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without using CodeDeploy.&lt;/p&gt;

&lt;p&gt;After running this setup in practice, I ended up preferring it for many ECS workloads.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Core Idea
&lt;/h1&gt;

&lt;p&gt;Both BLUE and GREEN environments run behind the same ALB.&lt;/p&gt;

&lt;p&gt;Internal office/VPN IPs get routed to GREEN first.&lt;/p&gt;

&lt;p&gt;Everyone else continues hitting BLUE.&lt;/p&gt;

&lt;p&gt;That means QA and internal teams can validate the new release directly on the real production infrastructure before public rollout begins.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;domain&lt;/li&gt;
&lt;li&gt;SSL certificate&lt;/li&gt;
&lt;li&gt;ALB&lt;/li&gt;
&lt;li&gt;authentication flow&lt;/li&gt;
&lt;li&gt;redirects&lt;/li&gt;
&lt;li&gt;networking path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No “staging surprises” later.&lt;/p&gt;

&lt;p&gt;A lot of deployment issues only appear on the real production routing path.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real Example
&lt;/h1&gt;

&lt;p&gt;Internal users open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://nginx.jayakrishnayadav.cloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…and immediately see the GREEN version.&lt;/p&gt;

&lt;p&gt;Meanwhile, public users continue seeing BLUE.&lt;/p&gt;

&lt;p&gt;No DNS switching.&lt;/p&gt;

&lt;p&gt;No duplicate infrastructure.&lt;/p&gt;

&lt;p&gt;Just ALB listener routing.&lt;/p&gt;




&lt;h1&gt;
  
  
  Architecture Overview
&lt;/h1&gt;

&lt;p&gt;The deployment flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                ┌────────────────────┐
                │   Application LB   │
                └─────────┬──────────┘
                          │
         ┌────────────────┴────────────────┐
         │                                 │
 Internal Office/VPN IPs             Public Users
         │                                 │
         ▼                                 ▼
   GREEN Target Group               BLUE Target Group
         │                                 │
    ECS GREEN Tasks                  ECS BLUE Tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The canary routing rule gets evaluated first.&lt;/p&gt;

&lt;p&gt;If the request source IP matches internal CIDRs, traffic goes to GREEN.&lt;/p&gt;

&lt;p&gt;Everything else falls back to BLUE.&lt;/p&gt;




&lt;h1&gt;
  
  
  Terraform Structure
&lt;/h1&gt;

&lt;p&gt;I kept the Terraform layout modular so it could be reused across multiple services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── main.tf
├── variables.tf
├── outputs.tf
├── env/
│   ├── backend.hcl
│   └── terraform.tfvars
├── modules/
│   ├── vpc/
│   ├── iam/
│   ├── alb/
│   ├── ecs-cluster/
│   └── ecs-blue-green-service/
└── scripts/
    └── zero-downtime-test.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each ECS service gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BLUE ECS service&lt;/li&gt;
&lt;li&gt;GREEN ECS service&lt;/li&gt;
&lt;li&gt;BLUE target group&lt;/li&gt;
&lt;li&gt;GREEN target group&lt;/li&gt;
&lt;li&gt;production listener rule&lt;/li&gt;
&lt;li&gt;optional canary listener rule&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  ALB Listener Rule Logic
&lt;/h1&gt;

&lt;p&gt;The entire deployment behavior depends on ALB listener priorities.&lt;/p&gt;

&lt;p&gt;The canary listener rule gets evaluated first.&lt;/p&gt;

&lt;p&gt;If the request source IP matches internal CIDRs, traffic gets forwarded to GREEN.&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_lb_listener_rule"&lt;/span&gt; &lt;span class="s2"&gt;"canary"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activate_canary&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;

  &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;source_ip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;values&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;canary_source_ips&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;host_header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"nginx.jayakrishnayadav.cloud"&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;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"forward"&lt;/span&gt;
    &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;green&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The production rule remains below it:&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_lb_listener_rule"&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

  &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;host_header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"nginx.jayakrishnayadav.cloud"&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;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"forward"&lt;/span&gt;
    &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_target_group&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;/p&gt;

&lt;p&gt;No weighted routing.&lt;/p&gt;

&lt;p&gt;No lifecycle hooks.&lt;/p&gt;

&lt;p&gt;Just listener priorities.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real Deployment Workflow
&lt;/h1&gt;

&lt;p&gt;This wasn’t built as a theoretical architecture exercise.&lt;/p&gt;

&lt;p&gt;I tested the rollout flow directly from Terraform while continuously validating traffic behavior against live ECS Fargate services.&lt;/p&gt;

&lt;p&gt;Terraform initialization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init &lt;span class="nt"&gt;-backend-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;/backend.hcl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deployment apply:&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;/terraform.tfvars &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-lock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During canary validation, I continuously verified my public IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl ifconfig.me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That mattered because the ALB source-IP rule decides whether traffic reaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BLUE&lt;/li&gt;
&lt;li&gt;GREEN&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once my IP matched the configured canary CIDRs, traffic immediately started routing to GREEN.&lt;/p&gt;




&lt;h1&gt;
  
  
  Deployment Flow
&lt;/h1&gt;

&lt;p&gt;The nice part about this setup is that everything becomes variable-driven.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Normal Production State
&lt;/h2&gt;

&lt;p&gt;BLUE handles all production traffic.&lt;/p&gt;

&lt;p&gt;GREEN remains scaled down.&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;enable_canary&lt;/span&gt;   &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="nx"&gt;activate_canary&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="nx"&gt;promote_to_all&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply:&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;/terraform.tfvars &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-lock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BLUE active&lt;/li&gt;
&lt;li&gt;GREEN inactive&lt;/li&gt;
&lt;li&gt;minimal Fargate cost&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 2 — Start GREEN Tasks
&lt;/h2&gt;

&lt;p&gt;Now we start the GREEN environment.&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;enable_canary&lt;/span&gt;   &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;activate_canary&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="nx"&gt;promote_to_all&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply again:&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;/terraform.tfvars &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-lock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GREEN tasks start&lt;/li&gt;
&lt;li&gt;ECS health checks complete&lt;/li&gt;
&lt;li&gt;ALB target registration completes&lt;/li&gt;
&lt;li&gt;no production traffic reaches GREEN yet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users never hit partially starting containers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Internal Canary Validation
&lt;/h2&gt;

&lt;p&gt;Now we enable canary routing.&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;enable_canary&lt;/span&gt;   &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;activate_canary&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;promote_to_all&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply again:&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;/terraform.tfvars &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-lock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;internal office/VPN users hit GREEN&lt;/li&gt;
&lt;li&gt;public users continue hitting BLUE&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This became the most valuable phase of the deployment workflow.&lt;/p&gt;

&lt;p&gt;Because now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;QA validates production behavior&lt;/li&gt;
&lt;li&gt;developers inspect logs&lt;/li&gt;
&lt;li&gt;authentication flows get tested&lt;/li&gt;
&lt;li&gt;sessions and redirects get verified&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while customers remain completely unaffected.&lt;/p&gt;




&lt;h1&gt;
  
  
  Internal Canary Routing
&lt;/h1&gt;

&lt;p&gt;This is the ALB listener rules view while canary routing is enabled.&lt;/p&gt;

&lt;p&gt;The priority 99 rule matches internal source IPs and forwards them to GREEN, while everyone else continues hitting BLUE.&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%2Fz2yonvkqhrrlgsr2srp9.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%2Fz2yonvkqhrrlgsr2srp9.png" alt="ALB Canary Routing" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 4 — Promote GREEN to Production
&lt;/h1&gt;

&lt;p&gt;Once validation looks good:&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;enable_canary&lt;/span&gt;   &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;activate_canary&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="nx"&gt;promote_to_all&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply again:&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;/terraform.tfvars &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-lock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;production listener switches to GREEN&lt;/li&gt;
&lt;li&gt;BLUE scales down&lt;/li&gt;
&lt;li&gt;all users see the new version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No downtime occurs.&lt;/p&gt;

&lt;p&gt;Traffic simply moves from one target group to another.&lt;/p&gt;




&lt;h1&gt;
  
  
  Verifying Zero Downtime
&lt;/h1&gt;

&lt;p&gt;I didn’t want to assume the deployment was safe.&lt;/p&gt;

&lt;p&gt;I wanted to verify it continuously during rollout.&lt;/p&gt;

&lt;p&gt;So I used a simple curl-based validation script that continuously hit both applications while traffic shifted between BLUE and GREEN.&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="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;1..100&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;do
  for &lt;/span&gt;url &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"https://nginx.jayakrishnayadav.cloud/"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"https://apache.jayakrishnayadav.cloud/"&lt;/span&gt;
  &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;" HTTPSTATUS:%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="p"&gt;% HTTPSTATUS&lt;/span&gt;:&lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
    &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="p"&gt;##*HTTPSTATUS&lt;/span&gt;:&lt;span class="k"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"BLUE - v"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nv"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"BLUE"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"GREEN - v"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nv"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"GREEN"&lt;/span&gt;
    &lt;span class="k"&gt;else
      &lt;/span&gt;&lt;span class="nv"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"UNKNOWN"&lt;/span&gt;
    &lt;span class="k"&gt;fi

    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Run: &lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt; | URL: &lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt; | Status: &lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="s2"&gt; | Version: &lt;/span&gt;&lt;span class="nv"&gt;$color&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;done
done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output during deployment:&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%2F0fcwccsu1bdk01xwtk2l.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%2F0fcwccsu1bdk01xwtk2l.png" alt="Zero Downtime Validation" width="799" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can clearly see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP 200 responses throughout deployment&lt;/li&gt;
&lt;li&gt;no failed requests&lt;/li&gt;
&lt;li&gt;no 503s&lt;/li&gt;
&lt;li&gt;clean traffic movement from BLUE to GREEN&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That confirmed the deployment was genuinely zero downtime.&lt;/p&gt;




&lt;h1&gt;
  
  
  Production Promotion View
&lt;/h1&gt;

&lt;p&gt;After promotion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the canary rule disappears&lt;/li&gt;
&lt;li&gt;the production listener points directly to GREEN&lt;/li&gt;
&lt;li&gt;all traffic reaches the new version&lt;/li&gt;
&lt;li&gt;BLUE scales down to zero&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clean and simple.&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%2F8q1tnzrowf05yue2aysz.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%2F8q1tnzrowf05yue2aysz.png" alt="Production Listener Switch" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6dlebcnuvfkkw1rj22g.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%2Fe6dlebcnuvfkkw1rj22g.png" alt="Final Traffic Flow" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Rollback
&lt;/h1&gt;

&lt;p&gt;Rollback became extremely simple.&lt;/p&gt;

&lt;p&gt;I just reverted the Terraform variables:&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;enable_canary&lt;/span&gt;   &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="nx"&gt;activate_canary&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="nx"&gt;promote_to_all&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply Terraform again:&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;/terraform.tfvars &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-lock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ALB immediately routes traffic back to BLUE.&lt;/p&gt;

&lt;p&gt;The rollback process stays predictable because traffic switching is entirely controlled through ALB listener rules.&lt;/p&gt;




&lt;h1&gt;
  
  
  HTTPS Configuration
&lt;/h1&gt;

&lt;p&gt;The ALB uses ACM certificates for HTTPS.&lt;/p&gt;

&lt;p&gt;Listeners:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Port 80 → redirect to HTTPS&lt;/li&gt;
&lt;li&gt;Port 443 → production traffic&lt;/li&gt;
&lt;li&gt;optional internal listener → restricted to internal CIDRs&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 hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;test_listener_allowed_cidrs&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"160.30.39.198/32"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That keeps internal preview traffic private while still using the same production infrastructure.&lt;/p&gt;




&lt;h1&gt;
  
  
  Cost Optimization
&lt;/h1&gt;

&lt;p&gt;One thing I specifically wanted to avoid was permanently doubling infrastructure cost.&lt;/p&gt;

&lt;p&gt;Normal state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;only BLUE tasks run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deployment window:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BLUE + GREEN both run temporarily&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After promotion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BLUE scales down again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So infrastructure cost only increases briefly during deployments.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;This project started because I wanted a very practical deployment workflow:&lt;/p&gt;

&lt;p&gt;Internal users should validate the new version on the actual production URL before customers ever see it.&lt;/p&gt;

&lt;p&gt;Once I implemented that using ALB listener priorities and source IP routing, I realized I no longer really needed CodeDeploy for this workflow.&lt;/p&gt;

&lt;p&gt;The end result became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simpler&lt;/li&gt;
&lt;li&gt;easier to operate&lt;/li&gt;
&lt;li&gt;easier to rollback&lt;/li&gt;
&lt;li&gt;easier to debug&lt;/li&gt;
&lt;li&gt;easier to reason about&lt;/li&gt;
&lt;li&gt;fully zero downtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And because everything is Terraform-driven, the deployment process stays reproducible and predictable.&lt;/p&gt;




&lt;h1&gt;
  
  
  GitHub Repository
&lt;/h1&gt;

&lt;p&gt;Full Terraform implementation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jayakrishnayadav24/ecs-blue-green-deployment/tree/canary" rel="noopener noreferrer"&gt;https://github.com/jayakrishnayadav24/ecs-blue-green-deployment/tree/canary&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ecs</category>
      <category>aws</category>
      <category>terraform</category>
      <category>devops</category>
    </item>
    <item>
      <title>I Replaced 47 DevOps Scripts With One AI Agent — Here’s What Happened</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Fri, 17 Apr 2026 10:15:34 +0000</pubDate>
      <link>https://dev.to/aws-builders/i-replaced-47-devops-scripts-with-one-ai-agent-heres-what-happened-4jbo</link>
      <guid>https://dev.to/aws-builders/i-replaced-47-devops-scripts-with-one-ai-agent-heres-what-happened-4jbo</guid>
      <description>&lt;h2&gt;
  
  
  The Hook: I Was Wrong About Automation
&lt;/h2&gt;

&lt;p&gt;I thought I automated DevOps.&lt;/p&gt;

&lt;p&gt;I had 47 deployment scripts.&lt;/p&gt;

&lt;p&gt;Then I started replacing them with an AI agent —&lt;br&gt;
and most scripts became unnecessary.&lt;/p&gt;

&lt;p&gt;Not by following instructions.&lt;br&gt;
By making decisions.&lt;/p&gt;

&lt;p&gt;And the 2 AM debugging stopped.&lt;/p&gt;



&lt;p&gt;Note: The code shown here is simplified for clarity.&lt;br&gt;
The GitHub repo contains a more modular implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's when I realized:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wasn't automating.&lt;br&gt;
I was hardcoding decisions.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;I gave Claude a list of AWS tools and one instruction: "Deploy this app."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No hardcoded logic.&lt;br&gt;
No decision trees.&lt;/p&gt;

&lt;p&gt;Just: &lt;strong&gt;describe the goal, Claude figures out how.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment time:&lt;/strong&gt; 3 hours → in minutes for most cases.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;em&gt;(Built this while deploying real workloads on AWS: Docker, ECS, EC2, IAM.)&lt;/em&gt;
&lt;/h2&gt;
&lt;h2&gt;
  
  
  🎯 Is This For You?
&lt;/h2&gt;

&lt;p&gt;This post is for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DevOps engineers&lt;/strong&gt; with 10+ deployment scripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform engineers&lt;/strong&gt; building internal developer platforms
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone&lt;/strong&gt; exploring AI agents beyond chatbots&lt;/li&gt;
&lt;li&gt;Teams on AWS (Docker, EC2, ECS, IAM)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If manual deployment takes &amp;gt; 30 minutes, read this.&lt;/p&gt;


&lt;h2&gt;
  
  
  ✨ What's Possible
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Old way (manual):&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 ecs create-cluster &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; prod
aws ecs register-task-definition &lt;span class="nt"&gt;--family&lt;/span&gt; myapp ...
&lt;span class="c"&gt;# [50+ commands, 3 hours, manual debugging]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;New way (agent):&lt;/strong&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="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy FastAPI with PostgreSQL, auto-scale to 20, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minimal IAM perms, CloudWatch monitoring&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ✅ Done in minutes for most cases, with significantly reduced debugging
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No scripts.&lt;/p&gt;

&lt;p&gt;Just Claude thinking out loud about your infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 How It Actually Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You describe the goal (natural language):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Deploy my app on 5 ECS tasks, auto-scale to 20 on high CPU"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Claude gets these tools:&lt;/strong&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="n"&gt;dispatcher&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;ecs__create_cluster&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ecs__register_task&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register_task_definition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ecs__create_service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iam__create_role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# For permissions
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Claude reasons autonomously:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"User wants ECS deployment.
 I need to:
 1. Check if cluster exists
 2. Register task definition
 3. Create service with 5 tasks
 4. Setup auto-scaling
 5. Verify it's running"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;It executes:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call &lt;code&gt;ecs__create_cluster()&lt;/code&gt; → Get cluster ARN&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;ecs__register_task()&lt;/code&gt; → Get task definition &lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;ecs__create_service()&lt;/code&gt; → Get running tasks&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;ecs__setup_autoscaling()&lt;/code&gt; → Confirmed&lt;/li&gt;
&lt;li&gt;Return: "All 5 tasks running, auto-scaling 5-20"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;** Minimal manual intervention in most cases. Just reasoning + execution + feedback loops.**&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ The Problem I Started With
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;deploy_docker.py&lt;/code&gt; — 150 lines of Docker logic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deploy_ec2.py&lt;/code&gt; — 200 lines of EC2 logic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deploy_ecs.py&lt;/code&gt; — 300 lines of ECS logic&lt;/li&gt;
&lt;li&gt;3 routers trying to chain them together&lt;/li&gt;
&lt;li&gt;0 way to handle "deploy to both EC2 AND ECS"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Every new service = new script. Every new workflow = rewrite everything.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Solution: Reduce rigid scripts — let the agent handle orchestration logic dynamically.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of routing to specific tools, give Claude all available tools and let it decide which ones to use, in which order, adapting as it goes.&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;# All tools in one place
&lt;/span&gt;&lt;span class="n"&gt;tools&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;docker__run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;run_container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ec2__create&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;create_instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ecs__deploy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;create_service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iam__create_role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;create_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ... more tools
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Let Claude orchestrate
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy with Docker locally, then scale to ECS production&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Claude figures out: Docker first, then ECS, then IAM for permissions
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🏗️ The Architecture (3 Layers)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────┐
│ User Goal (natural language) │  
│ "Deploy scalable production  │
│  stack with auto-scaling"    │
└──────────┬───────────────────┘
           │
           ▼
    ┌─────────────────────────────────────────────┐
    │  Claude (Bedrock)                           │
    │  ← Reads goal + available tools             │
    │  ← Decides sequence of actions              │
    │  ← Adapts when things fail                  │
    └──────────────┬────────────────────────────┘
                   │
    ┌──────────────┴──────────────┐
    │                             │
    ▼                             ▼
┌─────────────┐  ┌──────────────────┐
│ AWS APIs    │  │ Conversation     │
│ (via boto3) │  │ Memory (DynamoDB)│
│ ← Executes  │  │ ← Recalls setup  │
│   decisions │  │   from last week │
└─────────────┘  └──────────────────┘
    │                    │
    └────────┬───────────┘
             │
    ┌────────▼─────────┐
    │ Actual Resources │
    │ EC2, ECS, Docker │
    │      IAM, etc    │
    └──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3 moving parts:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Claude&lt;/strong&gt; — Reasons about the task&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt; — Execute AWS API calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt; — Remember past deployments for coherence&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔧 The Setup (Code Foundation)
&lt;/h2&gt;

&lt;p&gt;Here's the entire base system in ~100 lines. &lt;a href="https://github.com/jayakrishnayadav24/ai-agents" rel="noopener noreferrer"&gt;Full code on GitHub&lt;/a&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agents.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_message&lt;/span&gt;

&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apac.amazon.nova-lite-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Fast, cheap 
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Foundation for all agents (Docker, EC2, ECS, IAM)&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;AGENT_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# "docker", "ec2", etc.
&lt;/span&gt;    &lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;  &lt;span class="c1"&gt;# Agent's personality
&lt;/span&gt;    &lt;span class="n"&gt;CAPABILITIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;  &lt;span class="c1"&gt;# What this agent can do
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&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="n"&gt;name&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_bedrock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Lazy load Bedrock connection (only when needed)&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_bedrock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_bedrock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock-runtime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_bedrock&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;The agentic loop: think → decide → act → repeat&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;dispatcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_dispatcher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;)&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;role&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;user&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;content&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;}]}]&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Max 10 iterations to prevent infinite loops
&lt;/span&gt;            &lt;span class="c1"&gt;# Ask Claude what to do
&lt;/span&gt;            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;converse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;modelId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;toolUseDepth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Is Claude done?
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stopReason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;endTurn&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;SUCCESS&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;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;

            &lt;span class="c1"&gt;# Execute tools Claude wants to call
&lt;/span&gt;            &lt;span class="n"&gt;tool_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tool_use&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="n"&gt;tool_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toolUse&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;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_use[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="n"&gt;toolUse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;result&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;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="n"&gt;tool_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toolUseId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toolUse&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;toolUseId&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;content&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}]})&lt;/span&gt;

            &lt;span class="c1"&gt;# Add Claude's decision + results to conversation
&lt;/span&gt;            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;assistant&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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;content&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toolResult&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tool_results&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;

            &lt;span class="c1"&gt;# Save for future reference
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;save_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;FAILED&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;reason&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;Max iterations reached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lazy loading&lt;/strong&gt; (&lt;code&gt;@property bedrock&lt;/code&gt;): Don't connect to Claude until needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool loop&lt;/strong&gt;: Call tools, capture results, show Claude the output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error handling&lt;/strong&gt;: Don't crash—tell Claude what failed, let it adapt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt;: Save each turn so Claude remembers next week&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 Agents in Action: 3 Real Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DockerAgent: Deploy Locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DockerAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseAgent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;AGENT_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;docker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You manage Docker containers.
Rules: Pull image first, check if container exists, use sensible defaults.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;CAPABILITIES&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;list_containers&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;description&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;List running containers&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;run_container&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;description&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;Pull and run image&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;stop_container&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;description&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;Stop a container&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Usage:&lt;/strong&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="n"&gt;docker_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DockerAgent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;docker_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy FastAPI on port 8000 with health check&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Claude calls: list_containers → run_container → confirms it's running
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  EC2Agent: Scale to Cloud
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EC2Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseAgent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;AGENT_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;ec2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You manage EC2 instances.
Rules: Tag for organization, verify security groups, create new→test→retire old.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;CAPABILITIES&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;describe_instances&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;description&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;List instances&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;create_instance&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;description&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;Launch new instance&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;stop_instance&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;description&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;Stop instance&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Usage:&lt;/strong&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="n"&gt;ec2_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EC2Agent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ec2_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Create 2 t2.micro instances, tag as app-server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Claude: checks if instances exist → creates 2 → returns IPs → confirms running
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  ECSAgent: Production Auto-Scaling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ECSAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseAgent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;AGENT_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;ecs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You manage ECS services at production scale.
Rules: Task definition first, use Fargate, always configure auto-scaling.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;CAPABILITIES&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;create_cluster&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;description&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;Create ECS cluster&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;register_task_definition&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;description&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;Register blueprint&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;create_service&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;description&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;Deploy service&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;setup_autoscaling&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;description&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;Configure scaling rules&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Usage:&lt;/strong&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="n"&gt;ecs_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ECSAgent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ecs_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy with 5 tasks, auto-scale to 20 on high CPU, health monitoring&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Claude orchestrates: cluster → task definition → service → autoscaling → verification
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Real Usage: From Local to Production
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Deploy FastAPI from laptop to production in minutes for most cases, with significantly reduced debugging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 1: Test locally&lt;/strong&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="n"&gt;docker_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Run myapp:latest on port 8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&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_001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ✅ Docker container running
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stage 2: Scale to cloud&lt;/strong&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="n"&gt;ec2_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Create 2 instances for load balancing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&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_001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ✅ 2 EC2 instances up (same session = Claude remembers port 8000)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stage 3: Auto-scaling production&lt;/strong&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="n"&gt;ecs_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy with 5-20 auto-scaling, CloudWatch monitoring&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&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_001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ✅ 5 ECS tasks running, auto-scaling 5-20 based on CPU
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;All with the same &lt;code&gt;session_id&lt;/code&gt;:&lt;/strong&gt; Claude remembers the image name, port, and configuration from Stage 1. Everything connects seamlessly.&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Memory: Agents Remember
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Day 1
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy on Docker with port 8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Week later
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Move this to ECS for production&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Claude reads history from DynamoDB:
# "I remember this app used port 8000. I'll keep that for ECS too."
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DynamoDB stores every conversation turn. Claude recalls past decisions and uses them for coherence.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛡️ Production-Ready Code Patterns
&lt;/h2&gt;

&lt;p&gt;This is what the user feedback emphasized. Real code needs safety.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: Error Handling (Don't Crash, Recover)
&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;# ❌ Wrong: crashes on error
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;tool_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ✅ Right: tell Claude about the error
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;tool_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&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;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;failed&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;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# For debugging
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# Claude sees the error and tries a different approach
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; If Docker pull fails, don't give up. Tell Claude: "Pull failed, but let me check if the image is locally cached." Claude adapts.&lt;/p&gt;




&lt;h3&gt;
  
  
  Pattern 2: Timeouts (Prevent Hanging)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wraps&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;with_timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Prevent tools from running forever&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tool execution exceeded &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="si"&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="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGALRM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alarm&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="c1"&gt;# Cancel timeout
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;TimeoutError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorator&lt;/span&gt;

&lt;span class="nd"&gt;@with_timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;If EC2 creation hangs, timeout after 60 seconds&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;ec2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ec2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_instances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Pattern 3: Input Validation (Prevent Bad Requests)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Validate before executing&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Check required fields
&lt;/span&gt;    &lt;span class="n"&gt;required&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;ImageId&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;InstanceType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;params&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing required field: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="si"&gt;}&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;status&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;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Validate instance type
&lt;/span&gt;    &lt;span class="n"&gt;valid_types&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;t2.micro&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;t2.small&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;t3.medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;InstanceType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;valid_types&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;InstanceType must be one of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;valid_types&lt;/span&gt;&lt;span class="si"&gt;}&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;status&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;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Now safe to execute
&lt;/span&gt;    &lt;span class="n"&gt;ec2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ec2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_instances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Pattern 4: Audit Logging (Proof for Compliance)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Log everything for audits&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;log_entry&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;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# What was requested
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;result_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&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="c1"&gt;# Save to CloudWatch Logs or DynamoDB
&lt;/span&gt;    &lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TableName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_audit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;log_entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# In the tool dispatcher:
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;tool_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;log_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Pattern 5: Cost Guards (Don't Deploy Expensive Mistakes)
&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;MONTHLY_BUDGET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;  &lt;span class="c1"&gt;# $1000/month limit
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;estimate_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Estimate AWS cost before executing&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create_instance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;instance_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;InstanceType&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;t2.micro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;hourly_rates&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;t2.micro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.012&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t2.small&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.023&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t3.medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.042&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;months_running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hourly_rates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_type&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="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;730&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;months_running&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create_rds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# RDS: ~$400/month for 20GB
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# In the runnable:
&lt;/span&gt;&lt;span class="n"&gt;estimated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;estimate_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;estimated&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MONTHLY_BUDGET&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cost $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;estimated&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; exceeds budget $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;MONTHLY_BUDGET&lt;/span&gt;&lt;span class="si"&gt;}&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;status&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;rejected&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="c1"&gt;# Only execute if under budget
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;tool_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Pattern 6: Role-Based Access (Security Boundaries)
&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;# Define who can do what
&lt;/span&gt;&lt;span class="n"&gt;USER_PERMISSIONS&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;admin&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create_instance&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;delete_instance&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;create_role&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;delete_role&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;developer&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create_instance&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;stop_instance&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;deploy_to_ecs&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;readonly&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;describe_instances&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;list_containers&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;get_logs&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_permission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Prevent unauthorized actions&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;USER_PERMISSIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="c1"&gt;# Before executing:
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;check_permission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_name&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User role &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_role&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; cannot perform &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="si"&gt;}&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;status&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;permission_denied&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;tool_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✅ Why Agents &amp;gt; Scripts
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Scripts&lt;/th&gt;
&lt;th&gt;Agents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;New workflow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rewrite code&lt;/td&gt;
&lt;td&gt;Claude adapts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error recovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Crashes&lt;/td&gt;
&lt;td&gt;Tries alternatives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reasoning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None (hardcoded)&lt;/td&gt;
&lt;td&gt;Full decision log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Grows linearly&lt;/td&gt;
&lt;td&gt;One framework&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Learning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual proof&lt;/td&gt;
&lt;td&gt;Automatic audit trail&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 Getting Started
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Just 3 commands:&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/jayakrishnayadav24/ai-agents
&lt;span class="nb"&gt;cd &lt;/span&gt;ai-agents
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Then try:&lt;/strong&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agents.docker_agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DockerAgent&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DockerAgent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy FastAPI app on port 8000 with health check&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That's it.&lt;/strong&gt; Claude handles the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 What's Next (Part 2)
&lt;/h2&gt;

&lt;p&gt;This was the concept. &lt;a href="https://github.com/jayakrishnayadav24/ai-agents" rel="noopener noreferrer"&gt;Github&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Full working code (all agents)&lt;/li&gt;
&lt;li&gt;✅ DynamoDB setup for memory&lt;/li&gt;
&lt;li&gt;✅ Deploying agents to Lambda&lt;/li&gt;
&lt;li&gt;✅ Real production patterns (request tracing, cost estimation)&lt;/li&gt;
&lt;li&gt;✅ Demo with actual deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ Where This Breaks&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex compliance environments (manual approvals still needed)&lt;/li&gt;
&lt;li&gt;Cost estimation is approximate&lt;/li&gt;
&lt;li&gt;Requires well-defined tools (garbage in → garbage out)&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; 47 scripts, 3 hours/deployment, constant debugging&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; 1 agent framework, in minutes for most cases, with significantly reduced debugging/deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; Because Claude doesn't follow scripts. Claude plans and executes the steps based on the goal.&lt;/p&gt;

&lt;p&gt;It adapts. It learns. It remembers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's not automation anymore. That's the future of infrastructure.&lt;/strong&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;How many deployment scripts are you still maintaining?&lt;/p&gt;

&lt;p&gt;10+? 20+? 50+?&lt;/p&gt;

&lt;p&gt;I want to see how bad this problem is. Drop a comment 👇&lt;/p&gt;

&lt;p&gt;Your feedback shapes Part 2.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>agents</category>
      <category>aws</category>
      <category>eks</category>
      <category>automation</category>
    </item>
    <item>
      <title>The User Interface &amp; Ground-Truth Testing</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Tue, 14 Apr 2026 10:49:18 +0000</pubDate>
      <link>https://dev.to/aws-builders/the-user-interface-ground-truth-testing-3g47</link>
      <guid>https://dev.to/aws-builders/the-user-interface-ground-truth-testing-3g47</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part 5 of 5&lt;/strong&gt; | &lt;a href="https://dev.to/jayakrishnayadav24/smart-backend-routing-predictions-vs-qa-intelligently-2j7p"&gt;← Part 4&lt;/a&gt; | &lt;a href="https://dev.to/jayakrishnayadav24/series/38581"&gt;Complete Series&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Streamlit Overview
&lt;/h2&gt;

&lt;p&gt;Streamlit lets you build data apps in pure Python—no HTML, CSS, or JavaScript needed.&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%2Fegbsyfdxdwowsk7pd0kt.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%2Fegbsyfdxdwowsk7pd0kt.png" alt=" " width="799" height="388"&gt;&lt;/a&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;streamlit&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="c1"&gt;# Page config
&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_page_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;page_title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IPL AI Assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;page_icon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🏏&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wide&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="c1"&gt;# Title
&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🏏 IPL AI Assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Predictions + Q&amp;amp;A Powered by ML&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Tabs
&lt;/span&gt;&lt;span class="n"&gt;tab1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tab2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tab3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;💬 Chat&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;🎯 Predict&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;📊 Metrics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Tab 1: Chat Interface
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tab1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ask Anything&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Initialize session state
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;# Display chat history
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# User input
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ask about IPL...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Call backend
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000/chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;json&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;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;assistant_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&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;I couldn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t understand that.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;assistant&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;assistant_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;

            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assistant_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;st&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ Backend error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lvgauxouoz9237hx76i.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%2F0lvgauxouoz9237hx76i.png" alt=" " width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvebg9dhbhht3wum0t8dq.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%2Fvebg9dhbhht3wum0t8dq.png" alt=" " width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Session State:&lt;/strong&gt; &lt;code&gt;st.session_state.messages&lt;/code&gt; persists across reruns&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When user submits a message, Streamlit reruns the entire script&lt;/li&gt;
&lt;li&gt;Session state preserves conversation history&lt;/li&gt;
&lt;li&gt;Without it: chat history disappears on each input&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Chat Message:&lt;/strong&gt; &lt;code&gt;st.chat_message()&lt;/code&gt; renders messages with role-based styling&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"user" = right-aligned, blue background&lt;/li&gt;
&lt;li&gt;"assistant" = left-aligned, gray background&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Chat Input:&lt;/strong&gt; &lt;code&gt;st.chat_input()&lt;/code&gt; provides a textbox with submission handling&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Returns None until user submits&lt;/li&gt;
&lt;li&gt;Automatically clears after submission&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Tab 2: Prediction Interface
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tab2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Match Prediction Simulator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;columns&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="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subheader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Teams&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;batting_team&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Batting Team:&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mumbai Indians&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;Chennai Super Kings&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;Royal Challengers Bangalore&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;Kolkata Knight Riders&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="c1"&gt;# ... all 10 teams
&lt;/span&gt;            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;bowling_team&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bowling Team:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;options&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;All teams except batting team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subheader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Venue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;venue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ground name:&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;Wankhede&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subheader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pre-Match Form&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;h2h_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;H2H Win Rate (Batting Team)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;overall_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Overall Win Rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;col3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;venue_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Venue Win Rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;col4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rolling_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Last 5 Matches Win Rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subheader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Toss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;columns&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="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;toss_win&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;radio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Who won toss?&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Batting Team&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;Bowling Team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;toss_win&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;toss_win&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Batting Team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;toss_choice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;radio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Toss choice?&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bat&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;Field&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;toss_choice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toss_choice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Predict button
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🎯 Predict Match Outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use_container_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000/predict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;json&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;batting_team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;batting_team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bowling_team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bowling_team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;venue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h2h_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;h2h_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;overall_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;overall_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;venue_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;venue_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rolling_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rolling_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toss_win&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;toss_win&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toss_choice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;toss_choice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;winner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;winner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

            &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;### 🏆 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; wins!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**Confidence:** &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;confidence&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Show prediction breakdown
&lt;/span&gt;            &lt;span class="n"&gt;st&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**Model:** &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**Reasoning:**&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- H2H: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h2h_rate&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- Form: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rolling_rate&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- Venue: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;venue_rate&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;st&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ Prediction failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key UI Patterns:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;st.selectbox()&lt;/strong&gt; — Dropdown selector&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;st.slider()&lt;/strong&gt; — Range input (0.0-1.0)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;st.radio()&lt;/strong&gt; — Single-choice radio buttons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;st.columns()&lt;/strong&gt; — Grid layout (col1, col2, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;st.button()&lt;/strong&gt; — Form submission&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;st.success/error/info()&lt;/strong&gt; — Colored alerts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tab 3: Metrics &amp;amp; Transparency
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tab3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model Performance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Load metrics
&lt;/span&gt;    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/metrics.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&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="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test Accuracy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;accuracy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;col2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Precision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;precision&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;col3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Recall&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recall&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subheader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Confusion Matrix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/confusion_matrix.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use_column_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subheader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Feature Importance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;importance_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Feature&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h2h_rate&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;rolling_rate&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;venue_rate&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Importance&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="mf"&gt;0.32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.18&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="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Importance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar_chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;importance_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Feature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subheader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Q&amp;amp;A Engine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;st&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**Total Q&amp;amp;A Pairs:** 42,523&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**Vocabulary Size:** 18,394&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**Match Strategy:** TF-IDF + Cosine Similarity (threshold: 0.15)&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**Coverage:** &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42523&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% of expected cricket topics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Testing: The Foundation of Trust
&lt;/h2&gt;

&lt;p&gt;Good tests = confidence in deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Structure
&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;# tests/test_qa.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;src.build_qa_model&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;answer_question&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;joblib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt;

&lt;span class="c1"&gt;# Load test data
&lt;/span&gt;&lt;span class="n"&gt;test_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;datasets/ipl_2008_2024_complete.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;qa_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/qa_model.joblib&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Extract Q&amp;amp;A components
&lt;/span&gt;&lt;span class="n"&gt;tfidf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qa_model&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tfidf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;Q_matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qa_model&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Q_matrix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qa_model&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Test 1: Specific Match Facts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_match_lookup&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Can we answer specific match questions?&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;questions&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;Who won the match on 2024-04-01 between MI and RR?&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;How many runs were scored by MI in 2024-04-01?&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;What was the result of MI vs RR on 2024-04-01?&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;answer_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tfidf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Q_matrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed on: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer too short: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Confidence too low: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why data-driven?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not hardcoded: &lt;code&gt;"answer == 'Mumbai Indians wins'"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;CSV-based: Pulls real facts from dataset&lt;/li&gt;
&lt;li&gt;Robust: Works even if answer phrasing changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Test 2: Aggregate Statistics
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_most_wins&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Can we retrieve aggregate stats?&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;questions&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;Which team has won most matches?&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;Who has most IPL titles?&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;Team with highest win percentage?&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;answer_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tfidf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Q_matrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Verify answer is a valid team name
&lt;/span&gt;        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;valid_teams&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;Mumbai Indians&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;CSK&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;RCB&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="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;team&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;valid_teams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Test 3: Head-to-Head
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_head_to_head&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Can we answer H2H questions?&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;questions&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;Head-to-head record between MI and CSK?&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;Does MI have winning record vs KKR?&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;Who dominates MI vs RR?&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;answer_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tfidf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Q_matrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="c1"&gt;# H2H answers contain numbers (win counts)
&lt;/span&gt;        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;char&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Test 4: Threshold Behavior
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_threshold_protects_low_confidence&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Low-confidence matches are rejected.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;nonsense&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xyzabc qwerty asdfgh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Gibberish
&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;answer_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;nonsense&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tfidf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Q_matrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Model shouldn't hallucinate
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Got answer for nonsense: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Test 5: ML Model Predictions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_model_predictions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Can we predict match winners?&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;src.train&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;normalize_teams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engineer_features&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;joblib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt;

    &lt;span class="n"&gt;ml_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/model.joblib&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ml_model&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pipeline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Create a test case
&lt;/span&gt;    &lt;span class="n"&gt;test_row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&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="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Use real match
&lt;/span&gt;    &lt;span class="n"&gt;test_row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2023-05-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Test on recent data
&lt;/span&gt;
    &lt;span class="c1"&gt;# Engineer features
&lt;/span&gt;    &lt;span class="n"&gt;features_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;engineer_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2023-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# Historical
&lt;/span&gt;        &lt;span class="n"&gt;test_row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Predict
&lt;/span&gt;    &lt;span class="n"&gt;prediction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict_proba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;prediction&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="ow"&gt;in&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Binary classification
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob&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="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Valid probability
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob&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="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;  &lt;span class="c1"&gt;# Probabilities sum to 1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Test 6: Feature Sanity
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_feature_ranges&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Are features in valid ranges?&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;src.features&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;engineer_features&lt;/span&gt;

    &lt;span class="c1"&gt;# Get one match
&lt;/span&gt;    &lt;span class="n"&gt;test_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&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="c1"&gt;# Engineer
&lt;/span&gt;    &lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;engineer_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Check rates (should be 0-1)
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h2h_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&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="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;overall_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&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="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;venue_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&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="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rolling_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&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="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="c1"&gt;# Check binary fields
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toss_win&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&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="ow"&gt;in&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Test 7: No Data Leakage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_future_data_not_used&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Ensure no future data affects past predictions.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;src.features&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;engineer_features&lt;/span&gt;

    &lt;span class="c1"&gt;# Engineer a 2023 match using only 2022 history
&lt;/span&gt;    &lt;span class="n"&gt;before_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2023-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;hist_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;before_date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;test_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;before_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; 
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2023-02-01&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="n"&gt;iloc&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="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;engineer_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hist_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Features should only use hist_data, not test_match
&lt;/span&gt;    &lt;span class="c1"&gt;# (This is enforced in engine_features with before_date guards)
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="c1"&gt;# Verify: no 2023 data leaked into 2022 rates
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hist_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;before_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Running Tests
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install test dependencies&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;pytest

&lt;span class="c"&gt;# Run all tests&lt;/span&gt;
pytest tests/

&lt;span class="c"&gt;# Run with verbose output&lt;/span&gt;
pytest tests/ &lt;span class="nt"&gt;-v&lt;/span&gt;

&lt;span class="c"&gt;# Run specific test&lt;/span&gt;
pytest tests/test_qa.py::test_match_lookup &lt;span class="nt"&gt;-v&lt;/span&gt;

&lt;span class="c"&gt;# Coverage report&lt;/span&gt;
pytest tests/ &lt;span class="nt"&gt;--cov&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;src &lt;span class="nt"&gt;--cov-report&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;h3&gt;
  
  
  Option 1: Streamlit Cloud
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Push to GitHub&lt;/span&gt;
git push origin main

&lt;span class="c"&gt;# Link in Streamlit Cloud (streamlit.io/cloud)&lt;/span&gt;
&lt;span class="c"&gt;# - Select repository&lt;/span&gt;
&lt;span class="c"&gt;# - Select app.py&lt;/span&gt;
&lt;span class="c"&gt;# - Auto-deploys on push&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Live in 2 minutes, updates automatically.&lt;/p&gt;




&lt;h3&gt;
  
  
  Option 2: Docker
&lt;/h3&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; python:3.11&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; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8501&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; ipl-app &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8501:8501 ipl-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;http://localhost:8501&lt;/code&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Option 3: Heroku / Railway
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deploy with one command&lt;/span&gt;
heroku create ipl-ai-assistant
git push heroku main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;App runs at &lt;code&gt;ipl-ai-assistant.herokuapp.com&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Monitoring
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Log predictions to dataframe
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;predictions.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@st.cache_data&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_prediction_logs&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;predictions.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Dashboard
&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line_chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;get_prediction_logs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hour&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;confidence&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="nf"&gt;mean&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;Track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Average confidence over time&lt;/li&gt;
&lt;li&gt;Common queries&lt;/li&gt;
&lt;li&gt;Backend latency&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Common Issues &amp;amp; Solutions
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Connection refused"&lt;/td&gt;
&lt;td&gt;Backend not running on localhost:8000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chat history disappears&lt;/td&gt;
&lt;td&gt;Use st.session_state, not regular variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Predictions slow&lt;/td&gt;
&lt;td&gt;Enable model caching, use lazy loading&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests fail on new data&lt;/td&gt;
&lt;td&gt;Read from CSV, not hardcoded values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threshold too strict&lt;/td&gt;
&lt;td&gt;Lower from 0.15 to 0.10 for more results&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Summary: The Complete System
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Feature Engineering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Calculate pre-match metrics&lt;/td&gt;
&lt;td&gt;pandas, numpy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Predict match winners&lt;/td&gt;
&lt;td&gt;scikit-learn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Q&amp;amp;A Engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Answer cricket questions&lt;/td&gt;
&lt;td&gt;TF-IDF, cosine similarity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FastAPI Backend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Intelligent routing, lazy loading&lt;/td&gt;
&lt;td&gt;FastAPI, uvicorn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Streamlit Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chat, prediction, metrics&lt;/td&gt;
&lt;td&gt;Streamlit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Verify correctness&lt;/td&gt;
&lt;td&gt;pytest, CSV-based assertions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;You now have:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;✅ Production-ready predictions (61.8% accuracy)&lt;br&gt;&lt;br&gt;
✅ Intelligent Q&amp;amp;A with 42K learning pairs&lt;br&gt;&lt;br&gt;
✅ Low-latency API (&amp;lt;20ms per request)&lt;br&gt;&lt;br&gt;
✅ Smooth UI with session persistence&lt;br&gt;&lt;br&gt;
✅ 22 tests ensuring reliability&lt;br&gt;&lt;br&gt;
✅ Multiple deployment options  &lt;/p&gt;




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

&lt;p&gt;Deploy this and message me your results! 🚀&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/jayakrishnayadav24/ipl-ai-assistant" rel="noopener noreferrer"&gt;https://github.com/jayakrishnayadav24/ipl-ai-assistant&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series Complete&lt;/strong&gt; 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://dev.to/jayakrishnayadav24/smart-backend-routing-predictions-vs-qa-intelligently-2j7p"&gt;← Part 4: Backend Routing&lt;/a&gt; | &lt;a href="https://dev.to/jayakrishnayadav24/series/38581"&gt;← Return to Series&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Built with 💚 for cricket fans. Questions? DM me!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
      <category>ui</category>
    </item>
    <item>
      <title>Smart Backend Routing — Predictions vs Q&amp;A Intelligently</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Tue, 14 Apr 2026 10:47:09 +0000</pubDate>
      <link>https://dev.to/aws-builders/smart-backend-routing-predictions-vs-qa-intelligently-2j7p</link>
      <guid>https://dev.to/aws-builders/smart-backend-routing-predictions-vs-qa-intelligently-2j7p</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part 4 of 5&lt;/strong&gt; | &lt;a href="https://dev.to/jayakrishnayadav24/42000-instant-answers-without-apis-tf-idf-qa-system-j0a"&gt;← Part 3&lt;/a&gt; | &lt;a href="https://dev.to/jayakrishnayadav24/the-user-interface-ground-truth-testing-3g47"&gt;Part 5 →&lt;/a&gt; | &lt;a href="https://dev.to/jayakrishnayadav24/series/38581"&gt;View Series&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Hook: Two Systems, One Decision
&lt;/h2&gt;

&lt;p&gt;I had two systems:&lt;/p&gt;

&lt;p&gt;One predicts match winners.&lt;/p&gt;

&lt;p&gt;One answers 42,000 questions.&lt;/p&gt;

&lt;p&gt;Now the real problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you decide which one to use?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;This is where most systems fail, Not because of bad models. Because of bad decisions.&lt;/p&gt;

&lt;p&gt;Users call &lt;code&gt;/predict&lt;/code&gt; for Q&amp;amp;A. They call &lt;code&gt;/qa&lt;/code&gt; for predictions. Chaos.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Mistake I Almost Made: Separate APIs
&lt;/h2&gt;

&lt;p&gt;At first, I built two separate endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/predict  → Only for predictions
/qa       → Only for Q&amp;amp;A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Users had to decide which one to call.&lt;/p&gt;

&lt;p&gt;It was confusing. Unintuitive. Wrong.&lt;/p&gt;

&lt;p&gt;That's when I realized:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The system should decide — not the user.&lt;/strong&gt;&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.middleware.cors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CORSMiddleware&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IPL AI Assistant&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;Predictions + Q&amp;amp;A for cricket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Allow frontend to call backend
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CORSMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allow_origins&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;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;allow_methods&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;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;allow_headers&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;*&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Lazy Loading: The Secret Sauce
&lt;/h2&gt;

&lt;p&gt;Stop here for a second.&lt;/p&gt;

&lt;p&gt;This optimization seems small.&lt;/p&gt;

&lt;p&gt;But it changes everything about how your system scales.&lt;/p&gt;




&lt;p&gt;Instead of loading models at startup:&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;# ❌ Wrong way (startup)
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;joblib&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/model.joblib&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 500ms
&lt;/span&gt;&lt;span class="n"&gt;qa_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;joblib&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/qa_model.joblib&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 400ms
# Server ready: 900ms
&lt;/span&gt;
&lt;span class="c1"&gt;# ✅ Right way (lazy)
&lt;/span&gt;&lt;span class="n"&gt;BUNDLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;QA_BUNDLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_bundle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;BUNDLE&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;BUNDLE&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;BUNDLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;joblib&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/model.joblib&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# On first use
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[INFO] ML model loaded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BUNDLE&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_qa&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;QA_BUNDLE&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;QA_BUNDLE&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;QA_BUNDLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;joblib&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/qa_model.joblib&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# On first use
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[INFO] Q&amp;amp;A index loaded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;QA_BUNDLE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster startup&lt;/strong&gt; (150ms vs 900ms)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless-friendly&lt;/strong&gt; (AWS Lambda, Google Cloud)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional loading&lt;/strong&gt; (if users never ask, models never load)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost savings:&lt;/strong&gt; If you deploy 100 servers, save 100 × 750ms = 75 seconds total startup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Endpoint 1: Health Check
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Called by load balancers every 10 seconds.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;ok&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;version&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;1.0&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;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&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;If this returns 200, traffic flows. If timeout, traffic reroutes. Fast failure detection.&lt;/p&gt;




&lt;h2&gt;
  
  
  Endpoint 2: Metadata
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/model-info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;model_info&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Tell clients what models are running.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_bundle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# First call triggers load
&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ml_model&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;GradientBoostingClassifier&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;test_accuracy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.618&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;features&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&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;qa_engine&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qa_pairs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42523&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vocabulary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18394&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.15&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;Clients call this before predictions: "Are you ready? What accuracy?"&lt;/p&gt;




&lt;h2&gt;
  
  
  Endpoint 3: Raw Prediction
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PredictionRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;batting_team&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;bowling_team&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;h2h_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;        &lt;span class="c1"&gt;# 0.0-1.0
&lt;/span&gt;    &lt;span class="n"&gt;overall_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;    &lt;span class="c1"&gt;# 0.0-1.0
&lt;/span&gt;    &lt;span class="n"&gt;venue_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;      &lt;span class="c1"&gt;# 0.0-1.0
&lt;/span&gt;    &lt;span class="n"&gt;rolling_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;    &lt;span class="c1"&gt;# 0.0-1.0
&lt;/span&gt;    &lt;span class="n"&gt;toss_win&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;          &lt;span class="c1"&gt;# 0 or 1
&lt;/span&gt;    &lt;span class="n"&gt;toss_choice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;       &lt;span class="c1"&gt;# "bat" or "field"
&lt;/span&gt;
&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/predict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;predict_winner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PredictionRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Raw ML model prediction.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_bundle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Create feature vector
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;batting_team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;batting_team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bowling_team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bowling_team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;venue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h2h_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;h2h_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# ... other rates
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toss_win&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toss_win&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toss_choice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toss_choice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}])&lt;/span&gt;

    &lt;span class="c1"&gt;# Predict
&lt;/span&gt;    &lt;span class="n"&gt;prediction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pipeline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&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="n"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pipeline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;predict_proba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&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="n"&gt;winner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label_encoder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;inverse_transform&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;prediction&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;winner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&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;GradientBoostingClassifier&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&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="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/predict&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;"batting_team"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mumbai Indians"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bowling_team"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chennai Super Kings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"venue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wankhede"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"h2h_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"overall_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"venue_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rolling_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"toss_win"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"toss_choice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bat"&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="err"&gt;Response:&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;"winner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mumbai Indians"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.62&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;h2&gt;
  
  
  Endpoint 4: Intelligent Chat Router
&lt;/h2&gt;

&lt;p&gt;Stop for a moment.&lt;/p&gt;

&lt;p&gt;This endpoint is doing something powerful.&lt;/p&gt;

&lt;p&gt;It's not just forwarding requests. It's making decisions for the user.&lt;/p&gt;




&lt;p&gt;This is the magic endpoint:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Routes to prediction or Q&amp;amp;A automatically.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 1: Detect intent
&lt;/span&gt;    &lt;span class="n"&gt;is_prediction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;detect_prediction_intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 2: Extract teams
&lt;/span&gt;    &lt;span class="n"&gt;teams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_teams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 3: Route
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_prediction&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;teams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;handle_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;teams&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="n"&gt;teams&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;handle_qa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Intent Detection
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect_prediction_intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Is user asking for a prediction?&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;keywords&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;will&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;would&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;who will&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;predict&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;who wins&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;vs&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;against&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;beat&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Will MI beat KKR?" → True (has "will")
"MI vs CSK?" → True (has "vs")
"How many sixes?" → False
"Does toss matter?" → False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Team Extraction
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;difflib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_close_matches&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_teams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Find team names, even with typos.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_bundle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;team_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;team_index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="n"&gt;found_teams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Exact matching
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;team_names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;found_teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Fuzzy matching (for abbreviations like "mi" → "Mumbai Indians")
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_close_matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;team_names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;matches&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="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;found_teams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;found_teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;found_teams&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;# At most 2 teams
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Will mi beat kkr?" 
→ Fuzzy match "mi" → "Mumbai Indians"
→ Fuzzy match "kkr" → "Kolkata Knight Riders"
→ Return ["Mumbai Indians", "Kolkata Knight Riders"]

"who wins, royal challengers bangalore vs sunrisers?"
→ Exact match "royal challengers bangalore"
→ Fuzzy match "sunrisers" → "Sunrisers Hyderabad"
→ Return ["RCB", "SRH"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Prediction Handling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;team1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;team2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Compute features, run model, format response.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_bundle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;history_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;history_df&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Compute live rates from historical data
&lt;/span&gt;    &lt;span class="n"&gt;h2h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_h2h_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;team1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;team2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;overall1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_overall_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;team1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;overall2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_overall_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;team2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create prediction request
&lt;/span&gt;    &lt;span class="n"&gt;pred_req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PredictionRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;batting_team&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;team1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bowling_team&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;team2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;venue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TBD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;h2h_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;h2h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;overall_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;overall1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;venue_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rolling_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;compute_rolling_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;team1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history_df&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;toss_win&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;toss_choice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bat&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="c1"&gt;# Call /predict
&lt;/span&gt;    &lt;span class="n"&gt;prediction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;predict_winner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pred_req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Format as chat response
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;prediction&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;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🏆 **&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prediction&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;winner&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;** likely wins (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prediction&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&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;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prediction&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;confidence&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Q&amp;amp;A Handling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_qa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Call Q&amp;amp;A retrieval system.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;qa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_qa&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;answer_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;qa&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tfidf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;qa&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Q_matrix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;qa&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;qa&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;message&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;🤔 I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m not confident about that.&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;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;qa&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;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Error Handling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# ... routing logic ...
&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&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;Invalid input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Log to monitoring (Sentry, DataDog)
&lt;/span&gt;        &lt;span class="n"&gt;logger&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="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&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;Internal server error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Never&lt;/strong&gt; return stack traces. Log errors, return friendly messages.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Characteristics
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;th&gt;Queries/sec&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;/health&lt;/td&gt;
&lt;td&gt;&amp;lt;1ms&lt;/td&gt;
&lt;td&gt;10,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/model-info&lt;/td&gt;
&lt;td&gt;50ms&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/predict&lt;/td&gt;
&lt;td&gt;10ms&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/chat (ML)&lt;/td&gt;
&lt;td&gt;20ms&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/chat (Q&amp;amp;A)&lt;/td&gt;
&lt;td&gt;5ms&lt;/td&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One $5 server handles 10,000 queries/day easily.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deployment Options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1: Traditional Server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi uvicorn scikit-learn pandas

uvicorn app:app &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;API available at &lt;code&gt;http://localhost:8000&lt;/code&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Option 2: Docker Container
&lt;/h3&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; python:3.11&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; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&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; ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; ipl-api &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8000 ipl-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Option 3: AWS Lambda (Serverless)
&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;# With Lambda, lazy loading is critical
# Models load on first invocation, persist across requests
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mangum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Mangum&lt;/span&gt;

&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mangum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy with SAM or Serverless Framework.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing the Backend
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Health check&lt;/span&gt;
curl http://localhost:8000/health
&lt;span class="c"&gt;# {"status": "ok"}&lt;/span&gt;

&lt;span class="c"&gt;# Model info&lt;/span&gt;
curl http://localhost:8000/model-info
&lt;span class="c"&gt;# {"ml_model": {...}, "qa_engine": {...}}&lt;/span&gt;

&lt;span class="c"&gt;# Prediction&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/predict &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "batting_team": "Mumbai Indians",
    "bowling_team": "Chennai Super Kings",
    ...
  }'&lt;/span&gt;

&lt;span class="c"&gt;# Chat&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/chat &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"message": "Will MI beat CSK?"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Scaling Tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use gunicorn for multiple workers:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   gunicorn &lt;span class="nt"&gt;-w&lt;/span&gt; 4 &lt;span class="nt"&gt;-k&lt;/span&gt; uvicorn.workers.UvicornWorker app:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enable caching for Q&amp;amp;A (results don't change):&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;   &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;lru_cache&lt;/span&gt;

   &lt;span class="nd"&gt;@lru_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;answer_question_cached&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;answer_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Load balance across multiple servers:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Send requests to different servers&lt;/li&gt;
&lt;li&gt;Each loads models independently&lt;/li&gt;
&lt;li&gt;Scales to 1000s of requests/sec&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  💡 Key Insight: Routing is Intelligence
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AI isn't just about models.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's about deciding which model to use, when to use it, and when NOT to use it.&lt;/p&gt;

&lt;p&gt;Your ML algorithm is 61.8% accurate. That's good.&lt;/p&gt;

&lt;p&gt;But your routing logic? That's 100% accurate.&lt;/p&gt;

&lt;p&gt;Because it knows the user's intent before they do.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎤 The Mic Drop Lesson
&lt;/h2&gt;

&lt;p&gt;The smartest part of this system isn't the model.&lt;/p&gt;

&lt;p&gt;It's the decision of when NOT to use it.&lt;/p&gt;

&lt;p&gt;When a user asks "How many sixes?", the system says: "This is Q&amp;amp;A, not ML."&lt;/p&gt;

&lt;p&gt;When they ask "Will MI beat CSK?", it says: "This is prediction, not lookup."&lt;/p&gt;

&lt;p&gt;That's what makes it feel intelligent.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's in Part 5 (Frontend + Testing)
&lt;/h2&gt;

&lt;p&gt;Final post: Making it all visible and reliable:&lt;/p&gt;

&lt;p&gt;✅ Streamlit frontend (3 tabs)&lt;br&gt;&lt;br&gt;
✅ Session state (conversation persistence)&lt;br&gt;&lt;br&gt;
✅ 22 ground-truth tests&lt;br&gt;&lt;br&gt;
✅ Deployment (Streamlit Cloud, Docker)&lt;br&gt;&lt;br&gt;
✅ Common pitfalls  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sneak preview:&lt;/strong&gt; Your tests should pull data from CSV, not be hardcoded. Here's why it matters...&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;This is Part 4 of 5.&lt;/strong&gt; Subscribe for the finale! 🏏&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/jayakrishnayadav24/42000-instant-answers-without-apis-tf-idf-qa-system-j0a"&gt;← Part 3: Q&amp;amp;A Engine&lt;/a&gt; | &lt;a href="https://dev.to/jayakrishnayadav24/the-user-interface-ground-truth-testing-3g47"&gt;Part 5: Frontend + Testing →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>backend</category>
      <category>python</category>
    </item>
  </channel>
</rss>
