<?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: Sarvar Nadaf</title>
    <description>The latest articles on DEV Community by Sarvar Nadaf (@sarvar_04).</description>
    <link>https://dev.to/sarvar_04</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%2F1163149%2F5afa2902-591e-4944-b6fa-9bbba80c6e95.png</url>
      <title>DEV Community: Sarvar Nadaf</title>
      <link>https://dev.to/sarvar_04</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sarvar_04"/>
    <language>en</language>
    <item>
      <title>BrowserAct Hit #1 on Product Hunt - Why 629 Builders Voted for a BrowserAct That Gets Stuck</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Mon, 06 Jul 2026 19:02:18 +0000</pubDate>
      <link>https://dev.to/aws-builders/browseract-hit-1-on-product-hunt-why-629-builders-voted-for-a-browseract-that-gets-stuck-ppn</link>
      <guid>https://dev.to/aws-builders/browseract-hit-1-on-product-hunt-why-629-builders-voted-for-a-browseract-that-gets-stuck-ppn</guid>
      <description>&lt;p&gt;👋 Hey there, Tech Enthusiasts!&lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect who loves turning complex tech problems into simple solutions. I've worked with AWS, Azure, DevOps, Data, Analytics, Generative-AI and Agentic-AI building real systems for real companies. In this article series, I'll share what I've learned in a way that's easy to follow, whether you're experienced or just getting started.&lt;/p&gt;

&lt;p&gt;Let's get into it! 🚀&lt;/p&gt;




&lt;p&gt;So &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; hit #1 Product of the Day on Product Hunt.&lt;/p&gt;

&lt;p&gt;629 upvotes. 69 comments on the maker post alone. Beat products from established companies with larger teams on the same launch day. Ended up #3 for the week. Their GitHub has grown from 2.3k stars when I first wrote about them to 3.6k now.&lt;/p&gt;

&lt;p&gt;I wasn't surprised.&lt;/p&gt;

&lt;p&gt;I've been running this tool in production for 6 weeks. Published two articles &lt;a href="https://dev.to/aws-builders/i-gave-my-ai-agent-a-real-browser-heres-what-actually-happened-4ipk"&gt;Article 1 - Full hands-on testing&lt;/a&gt; and &lt;a href="https://dev.to/aws-builders/my-ai-agent-hit-a-login-wall-browseract-let-it-ask-for-help-and-resume-3mia"&gt;Article 2 - Headless + human handoff pattern&lt;/a&gt; it's covering full hands-on testing, real workflows, headless servers, the whole thing. So when I saw it trending on Product Hunt, my first thought was: yeah, that tracks.&lt;/p&gt;

&lt;p&gt;But here's what's more interesting than the vote count the &lt;em&gt;questions&lt;/em&gt; people asked in the comments.&lt;/p&gt;




&lt;h2&gt;
  
  
  What 629 Builders Actually Wanted to Know
&lt;/h2&gt;

&lt;p&gt;I read through the entire Product Hunt thread. And the questions developers asked reveal more about the state of browser automation in 2026 than any feature comparison table could.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"When BrowserAct hands off to a human during a stuck flow, what does that experience look like?"&lt;/strong&gt; - swati_paliwal&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Does BrowserAct re-anchor to elements semantically when the DOM drifts?"&lt;/strong&gt; - David&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Where does BrowserAct pull ahead vs Browser Use and Browserbase?"&lt;/strong&gt; - Gal Dayan&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Solve rates tend to rot fast. How do you keep CAPTCHA handling holding up over time?"&lt;/strong&gt; - Kwan Tsui&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Sites serve degraded or stale content instead of erroring out. How do you handle silent soft-blocks?"&lt;/strong&gt; - Dipankar Sarkar&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Does session state survive a locale/IP change mid-flow?"&lt;/strong&gt; - Art Stavenka&lt;/p&gt;

&lt;p&gt;These aren't feature requests. They're pain points. Every one of them comes from someone who's been burned before.&lt;/p&gt;

&lt;p&gt;And honestly reading that thread felt like reading my own notes from 6 weeks ago.&lt;/p&gt;




&lt;h2&gt;
  
  
  I Can Answer Most of These
&lt;/h2&gt;

&lt;p&gt;I'm not on the BrowserAct team. I'm a cloud architect who integrated this into my actual work monitoring dashboards, extracting data from protected pages, running agents that need to browse real sites behind Cloudflare and login walls.&lt;/p&gt;

&lt;p&gt;So let me map the community's questions to what I've actually experienced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human handoff UX?&lt;/strong&gt; I tested this extensively in &lt;a href="https://dev.to/aws-builders/my-ai-agent-hit-a-login-wall-browseract-let-it-ask-for-help-and-resume-3mia"&gt;article 2&lt;/a&gt;. The agent hits a login wall, calls &lt;code&gt;remote-assist&lt;/code&gt;, generates a URL. I open that URL on my phone, complete the 2FA, close the tab. The agent picks up right where it was. Same session, same cookies, same page state. No restart. Took me about 40 seconds the first time. Now it's under 15.&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%2Fw8l5hdxjbrenebd04fy1.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%2Fw8l5hdxjbrenebd04fy1.png" alt="Remote-assist URL generated when agent hits login wall" width="800" height="79"&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%2F5qmcixa7f0il9c286qdr.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%2F5qmcixa7f0il9c286qdr.png" alt="Remote assist interface showing Take Control button with 1 hour timer" width="800" height="595"&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%2Frpwz1y2y0a12jdgxtcfv.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%2Frpwz1y2y0a12jdgxtcfv.png" alt="Complete button in top-right corner of remote assist interface" width="799" height="235"&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%2Fver4tl65q3ofch85t3j6.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%2Fver4tl65q3ofch85t3j6.png" alt="Agent resuming with authenticated session after human handoff" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most automation tools treat human intervention as failure. BrowserAct treats it as a feature. The agent &lt;em&gt;decides&lt;/em&gt; to ask for help. That's intentional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anti-detection longevity?&lt;/strong&gt; I ran the same stealth browser against nowsecure.nl and Cloudflare's bot detection page across multiple weeks. Still passing the environment layer hasn't degraded on sites I first tested in week 1. The 3-layer system (fingerprint environment → auto-CAPTCHA solving → human fallback) means even if one layer degrades, the others catch it. In 6 weeks I haven't had a session get permanently blocked.&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%2F6qyme4vwia38pnlz2c2i.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%2F6qyme4vwia38pnlz2c2i.png" alt="Stealth browser passing nowsecure.nl Cloudflare detection" width="796" height="82"&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%2Fh8axz4tawr4llz9nzxiz.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%2Fh8axz4tawr4llz9nzxiz.png" alt="Cloudflare turnstile challenge handled automatically in network requests" width="800" height="207"&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%2Ftuxjqzrl0jwu53fdljw2.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%2Ftuxjqzrl0jwu53fdljw2.png" alt="Extracting content from Cloudflare's own product page" width="799" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Though I'm not hammering sites at scale. A few hundred requests a day, not tens of thousands. Your mileage will vary at volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallel sessions without cross-contamination?&lt;/strong&gt; Multiple browser profiles simultaneously different accounts, different sites, different fingerprints. No cookie leakage, no fingerprint overlap. Each session gets its own identity.&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%2Fr7iv6trlm1e06i502xd5.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%2Fr7iv6trlm1e06i502xd5.png" alt="Multiple isolated sessions running on the same browser" width="742" height="508"&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%2Fd5y9xl14ihi3at297bkw.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%2Fd5y9xl14ihi3at297bkw.png" alt="Opening parallel session on GitHub trending" width="795" height="81"&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%2Fgh5hvgfzxtk6xp2blpgh.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%2Fgh5hvgfzxtk6xp2blpgh.png" alt="Opening parallel session on Hacker News" width="800" height="82"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent-native output?&lt;/strong&gt; BrowserAct outputs clean, indexed text not raw HTML. Fewer tokens, lower cost, better reasoning. Every token your agent spends parsing DOM soup is a token it's not spending on solving your problem. This is what my agent sees when it looks at a page no markup, just numbered elements it can act on:&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%2Fizavir6p4ckb4awfveqb.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%2Fizavir6p4ckb4awfveqb.png" alt="Agent-native indexed output clean element references for efficient reasoning" width="799" height="214"&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%2Fzn38qfhuc67cdm5eephl.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%2Fzn38qfhuc67cdm5eephl.png" alt="Clean markdown extraction output from get markdown command" width="799" height="271"&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%2Fjc66e4ynzrj5zwiisgiv.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%2Fjc66e4ynzrj5zwiisgiv.png" alt="Opening parallel session" width="799" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd rather say "I don't know" than make something up.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Features That Won the Vote
&lt;/h2&gt;

&lt;p&gt;629 people didn't upvote because browser automation is exciting. They upvoted because this solves specific frustrations that existing tools don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. It gets stuck on purpose.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This sounds backwards. Why would you &lt;em&gt;want&lt;/em&gt; your automation to stop? Because the alternative is worse it crashes silently, retries infinitely, or returns garbage data while pretending everything's fine.&lt;/p&gt;

&lt;p&gt;I had a moment at 2am a few weeks ago client's monitoring dashboard alert went off, my agent tried to check it, hit a new CAPTCHA type it hadn't seen before. Instead of failing silently and me waking up to stale data, it pinged me. I solved the CAPTCHA half-asleep on my phone, agent continued, I went back to sleep. Old tool? I would've found out 6 hours later that nothing was collected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Local-first in a cloud-first world.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Browserbase runs your sessions in the cloud. BrowserAct runs on YOUR machine. Your Chrome, your cookies, your logins. Nothing leaves your device unless you explicitly send it.&lt;/p&gt;

&lt;p&gt;For me this isn't optional. I manage client AWS accounts, access their internal dashboards I'm not sending authenticated sessions through someone else's infrastructure. I ran it on my Linux server, headless, no display needed. My sessions, my machine, my client's data stays where it should.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The 3-layer escalation model.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most tools have one approach to anti-detection. Fingerprint spoofing, and pray. BrowserAct stacks three:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Environment layer: fingerprints, TLS rotation, navigator patching, headless concealment&lt;/li&gt;
&lt;li&gt;Execution layer: auto-solves CAPTCHAs when they pop up (reCAPTCHA, Turnstile, DataDome)&lt;/li&gt;
&lt;li&gt;Human layer: when automation can't solve it, a human can jump in live&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tested this across real sites with actual Cloudflare protection. The environment layer handled 90% of cases. The CAPTCHA solver caught most of the rest. I only needed human handoff for sites with custom 2FA flows. If layer 1 fails, layer 2 catches it. Layer 2 fails? You get a human. With Puppeteer, if fingerprint detection catches you session's dead. No fallback.&lt;/p&gt;

&lt;p&gt;What I've noticed after 6 weeks: the escalation isn't just a safety net it changes how you design workflows. You stop building for the happy path only and start trusting that edge cases won't silently break things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Built for multi-account operations and for agents, not humans.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cross-contamination between sessions isn't just annoying it's a breach. One leaked cookie, one shared fingerprint between Client A and Client B, and you've got a trust problem that no apology email fixes. The isolation model here isn't a feature checkbox. It's risk management.&lt;/p&gt;

&lt;p&gt;And the agent-native output that's not about developer experience. It's about cost. I measured it roughly: a raw HTML page burns 3-5x more tokens than BrowserAct's indexed output for the same content. At a few sessions a day, whatever. At hundreds? That's real money. My workflows got noticeably cheaper once I stopped piping full DOM through the LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Session state survives more than you'd expect.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the one I didn't appreciate until week 3. The browser state cookies, page position, auth tokens, everything persists through handoffs, through network interruptions, through the agent pausing overnight and resuming in the morning.&lt;/p&gt;

&lt;p&gt;I had a session authenticated to a client's Grafana dashboard. My agent checked it Monday. Checked it again Wednesday. Same session, still logged in, no re-auth needed. With Playwright, you'd serialize cookies, hope nothing expired, rebuild context from scratch. Here the session just... exists. Waiting.&lt;/p&gt;

&lt;p&gt;And when a human completes a step during &lt;code&gt;remote-assist&lt;/code&gt; and the page changes new elements loaded, a redirect happened the agent doesn't try to replay stale assumptions. It reads the current page as the new truth. BrowserAct's approach (from Maggie on their team, in the PH thread): "We treat the post-handoff page as the new source of truth rather than trying to replay stale DOM assumptions." After I completed GitHub login manually, my agent immediately extracted content from the authenticated state. No stale references, no "element not found" errors.&lt;/p&gt;

&lt;p&gt;Whether session state survives a locale/IP change mid-flow I haven't explicitly tested that. Sessions survived my &lt;code&gt;remote-assist&lt;/code&gt; handoffs without issue, but proxy rotation mid-session is something I'd need to verify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Works with whatever agent you're using tomorrow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I switched agents three times in the last 6 months. Claude Code, then Cursor, then Kiro. Every time I switch, I dread re-integrating my tools. BrowserAct? Didn't care. It's a CLI. Anything that can run a shell command can drive it. No SDK to install, no Python library to pin, no WebSocket connection to manage. Just &lt;code&gt;browser-act --session X do-thing&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I've run it from bash scripts, from cron jobs on a headless server, from three different agents. The integration took 5 minutes each time. In a world where everyone's switching agents every few months, not being locked into one framework isn't a nice-to-have it's survival.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. This isn't a weekend project.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'll be honest when I first tested BrowserAct, I assumed it was early-stage. Small team, niche tool, might disappear in 6 months. I checked their AWS Marketplace listing because I needed to know this thing would still exist when my client's contract renews in Q4.&lt;/p&gt;

&lt;p&gt;Then I looked at the actual numbers: 500M+ pages automated, 10M+ CAPTCHAs solved, 10K+ concurrent sessions running on the platform. G2 rating of 4.8/5. Available on AWS Marketplace. Partners include AWS, Azure, and GCP.&lt;/p&gt;

&lt;p&gt;That's not a side project. That's infrastructure. And for anyone choosing a tool they'll depend on in production knowing it's not going to vanish matters more than any single feature.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Product Hunt Voters Don't Know Yet
&lt;/h2&gt;

&lt;p&gt;Here's the part I can write that a vote count can't tell you. Stuff I wish someone had told me before I started.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed isn't instant.&lt;/strong&gt; Stealth browsers take longer to launch than a raw Puppeteer instance. You're paying for anti-detection with startup time. A stealth browser launch takes a few seconds versus near-instant for bare Chrome. For one-off tasks, fine. For tight loops that need sub-second response, you'll feel it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error messages could be better.&lt;/strong&gt; When something fails especially around proxy configuration or session naming conflicts the error output is terse. I had a session name collision once (globally unique names required, even across browsers) and the error just said the session couldn't be created. No hint about &lt;em&gt;why&lt;/em&gt;. Took me 10 minutes of docs-diving. This is the kind of thing that improves over time, but right now, expect some head-scratching moments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credits add up.&lt;/strong&gt; The pricing is credit-based, and in dollar terms it breaks down to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 credits per stealth browser (~$0.064 per browser)&lt;/li&gt;
&lt;li&gt;5 credits per workflow step (~$0.003 per step)&lt;/li&gt;
&lt;li&gt;5,000 credits per GB of proxy traffic (~$3.20/GB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For my usage a few browser sessions a day, some proxy requests it's cheap. But if you're running hundreds of concurrent sessions with dynamic proxies, do the math first. Full plan details and credit bundles are on their &lt;a href="https://browseract.com/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;. The free tier covers basic Chrome automation with no signup, which is great for testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not for everything.&lt;/strong&gt; If a site has an API, use the API. I wouldn't use it for high-frequency data collection at massive scale there are cheaper, dumber tools for pure scraping where you don't need anti-detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Silent soft-blocks are an open question.&lt;/strong&gt; Someone on the PH thread asked about sites that serve degraded or stale content instead of erroring out. Honest answer: I haven't hit this scenario but I also wasn't looking for it specifically. My workflows extract structured data and I'd notice if the numbers were wrong. Whether BrowserAct has detection for this at the platform level, I genuinely don't know.&lt;/p&gt;

&lt;p&gt;It shines when you need a &lt;em&gt;real browser&lt;/em&gt; experience: logins, multi-step flows, sites that actively fight automation, workflows where failure needs to be handled gracefully instead of silently.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Verdict
&lt;/h2&gt;

&lt;p&gt;Would I keep using BrowserAct? Already am.&lt;/p&gt;

&lt;p&gt;It sits in my workflow for anything that requires a real browser session monitoring protected dashboards, extracting data from authenticated portals, running agents against sites that block everything else. There are 30+ browser automation tools right now and hundreds of millions in funding flowing into this space. I deep-tested &lt;em&gt;this&lt;/em&gt; one for 6 weeks, and it solved problems I couldn't solve before with Puppeteer or Playwright.&lt;/p&gt;

&lt;p&gt;It's not perfect. The speed could be better. Error messages need work. And I genuinely don't know how the CAPTCHA solving holds up at scale.&lt;/p&gt;

&lt;p&gt;But the core design decision automate what works, ask for help when stuck, resume from the same state that's the right architecture. And 629 Product Hunt voters seem to agree.&lt;/p&gt;

&lt;p&gt;Next on my list: deep-testing Skill Forge for packaging repeatable workflows. If it holds up, that's article 4.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; - main site&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.producthunt.com/leaderboard/daily/2026/6/25" rel="noopener noreferrer"&gt;Product Hunt Daily Leaderboard (June 25)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.producthunt.com/leaderboard/weekly/2026/26" rel="noopener noreferrer"&gt;Product Hunt Weekly Leaderboard (Week 26)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/browser-act/skills" rel="noopener noreferrer"&gt;GitHub - browser-act/skills&lt;/a&gt; (3.6k ⭐)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/i-gave-my-ai-agent-a-real-browser-heres-what-actually-happened-4ipk"&gt;Article 1 - Full hands-on testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/my-ai-agent-hit-a-login-wall-browseract-let-it-ask-for-help-and-resume-3mia"&gt;Article 2 - Headless + human handoff pattern&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;I test tools in production, not sandboxes. Questions about running BrowserAct in real workflows? Drop a comment.&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;Thanks for reading! If this was helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value&lt;/li&gt;
&lt;li&gt;💾 Save for later&lt;/li&gt;
&lt;li&gt;🔄 Share with your team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow me for more on:&lt;/strong&gt; AWS architecture, FinOps, DevOps, and AI Infrastructure.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit my website&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;Connect on LinkedIn&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Learning&lt;/strong&gt; 🚀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>automation</category>
    </item>
    <item>
      <title>Build Your First AI Agent in 30 Minutes - CrewAI + AWS Bedrock</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Fri, 03 Jul 2026 17:18:42 +0000</pubDate>
      <link>https://dev.to/aws-builders/build-your-first-ai-agent-in-30-minutes-crewai-aws-bedrock-40lo</link>
      <guid>https://dev.to/aws-builders/build-your-first-ai-agent-in-30-minutes-crewai-aws-bedrock-40lo</guid>
      <description>&lt;p&gt;👋 Hey there, Tech Enthusiasts!&lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect who loves turning complex tech problems into simple solutions. I've worked with AWS, Azure, DevOps, Data, Analytics, Generative-AI and Agentic-AI building real systems for real companies. In this article series, I'll share what I've learned in a way that's easy to follow, whether you're experienced or just getting started.&lt;/p&gt;

&lt;p&gt;Let's get into it! 🚀&lt;/p&gt;




&lt;p&gt;Hey, welcome back.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/aws-builders/i-built-an-ai-agent-that-tailors-my-resume-heres-how-agents-actually-work-5733"&gt;last article&lt;/a&gt;, we covered what AI agents are, how they think in loops, and why they're different from just asking ChatGPT for help. We looked at the building blocks role, goal, backstory, tasks, tools and how CrewAI lets you define all of this in simple YAML files.&lt;/p&gt;

&lt;p&gt;We ended with a promise: we're going to build the thing.&lt;/p&gt;

&lt;p&gt;This is that article. No more theory. We're building the resume tailor agent from scratch. By the end, you'll have a working agent with a web UI that takes a job description, analyzes it, and tailors your resume to match in seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a Resume Tailor Agent?
&lt;/h2&gt;

&lt;p&gt;This isn't a random project idea. It comes from a real problem I've faced repeatedly.&lt;/p&gt;

&lt;p&gt;I've been working in MNCs for over 10 years. Every time a job switch comes up, the cycle is the same update the resume, start applying, wait for calls. And the calls don't come. Not because I lack the skills or experience. Not because there's a position mismatch. The problem is simpler and more frustrating than that: the things they're looking for I've actually done them. I just didn't mention them on my resume.&lt;/p&gt;

&lt;p&gt;That's where I was lagging. My resume had my experience, but it wasn't speaking the language of the job description. The keywords weren't there. The framing wasn't right. And in today's market, where competition is fiercer than ever and most resumes go through automated screening first, that gap is enough to get you filtered out before a human ever reads your application.&lt;/p&gt;

&lt;p&gt;Most companies today use ATS (Applicant Tracking Systems) that scan resumes for keywords before a recruiter ever sees them. If your resume doesn't have the right terms the exact ones from the job description it gets filtered out automatically. You could be a perfect fit and still never make it past the screen.&lt;/p&gt;

&lt;p&gt;So now that we can build AI agents that actually do things why not build one that fixes this? Give it a job description, give it your resume, and in a few seconds it extracts the keywords that matter, identifies what's missing, and rewrites your bullet points to be ATS-friendly. Result: your resume actually gets past the bots and onto someone's desk.&lt;/p&gt;

&lt;p&gt;That's what we're building. Not because it's a cool demo because I needed it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;A single AI agent that takes a job description (required) and optionally your resume, then gives you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With JD + Resume (full analysis):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;JD Summary - what they're actually looking for (5-6 sentences)&lt;/li&gt;
&lt;li&gt;Matching Skills - key technologies to highlight in your resume (e.g., LLM, MCP, RAG, Kubernetes, Terraform)&lt;/li&gt;
&lt;li&gt;Skill Gaps - short bullet points, one gap per bullet&lt;/li&gt;
&lt;li&gt;Career Guidance - short career path bullets showing what to prepare (learn X, get certified in Y, build project with Z)&lt;/li&gt;
&lt;li&gt;Tailored Resume - rewritten bullet points ready to copy-paste&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;With JD only (no resume):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;JD Summary - what the role needs&lt;/li&gt;
&lt;li&gt;Key Technologies - tech keywords the JD demands&lt;/li&gt;
&lt;li&gt;Career Path - what to prepare for this role&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Resume is optional so even if someone doesn't have one ready, they can still get value by understanding what a JD requires and what path to follow.&lt;/p&gt;

&lt;p&gt;No fancy multi-agent setup. One agent with a good prompt handles all of this in a single pass. Simpler code, faster execution, cheaper to run.&lt;/p&gt;




&lt;h2&gt;
  
  
  Input &amp;amp; Output - What Goes In, What Comes Out
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Input (what you provide):
&lt;/h3&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;Required?&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Job Description&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;The JD you're targeting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Years of Experience&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;Helps adjust suggestions to your level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expertise Level&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;Fresher / Junior / Mid-Level / Senior / Lead / Architect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resume&lt;/td&gt;
&lt;td&gt;❌ Optional&lt;/td&gt;
&lt;td&gt;Your current resume (paste as text)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Output (what you get):
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;If you provide JD + Resume:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JD Summary&lt;/td&gt;
&lt;td&gt;5-6 sentences&lt;/td&gt;
&lt;td&gt;"They want a senior cloud engineer who owns AWS infra end-to-end"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matching Skills&lt;/td&gt;
&lt;td&gt;Key technology keywords&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Terraform, Kubernetes, Python, AWS Lambda, CloudWatch&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skill Gaps&lt;/td&gt;
&lt;td&gt;Bullet points&lt;/td&gt;
&lt;td&gt;• No Kafka experience • Missing distributed systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Career Guidance&lt;/td&gt;
&lt;td&gt;Career path bullets&lt;/td&gt;
&lt;td&gt;• Learn Kafka basics • Get AWS Solutions Architect cert • Build a microservices project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tailored Resume&lt;/td&gt;
&lt;td&gt;Ready to copy-paste&lt;/td&gt;
&lt;td&gt;Rewritten bullet points matching the JD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;If you provide JD only (no resume):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JD Summary&lt;/td&gt;
&lt;td&gt;5-6 sentences&lt;/td&gt;
&lt;td&gt;"Looking for a GenAI engineer with RAG and LLM experience"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key Technologies&lt;/td&gt;
&lt;td&gt;Keywords&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LLM, RAG, MCP, LangChain, Vector DB, Python&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Career Path&lt;/td&gt;
&lt;td&gt;Bullet points&lt;/td&gt;
&lt;td&gt;• Learn RAG patterns • Build an agent with CrewAI • Study vector databases&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


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

&lt;ul&gt;
&lt;li&gt;An AWS account with Bedrock access (Amazon Nova Pro model enabled)&lt;/li&gt;
&lt;li&gt;An EC2 instance (t3.medium or bigger) or any Linux machine with Python&lt;/li&gt;
&lt;li&gt;About 30-45 minutes&lt;/li&gt;
&lt;li&gt;Basic comfort with the terminal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm using an EC2 instance running Amazon Linux 2023. You can use Ubuntu, your local Mac, WSL doesn't matter. The commands are the same.&lt;/p&gt;

&lt;p&gt;One thing before we start make sure you've enabled the Amazon Nova Pro model in your AWS Bedrock console. It's not enabled by default. Go to Bedrock &amp;gt; Model access &amp;gt; Request access to Nova Pro. Takes a minute to get approved.&lt;/p&gt;


&lt;h2&gt;
  
  
  High-Level Architecture
&lt;/h2&gt;

&lt;p&gt;Before we jump into code, here's how everything fits together:&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User&lt;/strong&gt; opens the Streamlit web UI in their browser and pastes a job description, resume, and experience level&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streamlit&lt;/strong&gt; (running on EC2) passes the inputs to the CrewAI orchestrator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;crew.py&lt;/strong&gt; loads the agent configuration (agents.yaml + tasks.yaml) and constructs the prompt&lt;/li&gt;
&lt;li&gt;The prompt is sent to &lt;strong&gt;Amazon Bedrock Nova Pro&lt;/strong&gt; a foundation model that does the actual analysis&lt;/li&gt;
&lt;li&gt;Bedrock returns the analysis skill gaps, career guidance, and tailored resume&lt;/li&gt;
&lt;li&gt;Results flow back through CrewAI → Streamlit → displayed to the user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire stack runs on a single EC2 instance (t3.medium). The only external service is Amazon Bedrock, which handles the LLM inference. No databases, no queues, no complex infra just a Python app talking to a foundation model.&lt;/p&gt;


&lt;h2&gt;
  
  
  Setting Up the Environment
&lt;/h2&gt;

&lt;p&gt;First, SSH into your server. If you're working locally, skip this.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; your-key.pem ec2-user@your-server-ip
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6lrc4txgp8aymepoklpj.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%2F6lrc4txgp8aymepoklpj.png" alt=" " width="799" height="235"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Install Python 3.11
&lt;/h3&gt;

&lt;p&gt;CrewAI needs Python 3.11 or higher.&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;dnf &lt;span class="nb"&gt;install &lt;/span&gt;python3.11 python3.11-pip &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Check it worked:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3.11 &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You should see something like &lt;code&gt;Python 3.11.x&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;uv is a fast Python package manager. CrewAI uses it under the hood for dependency management. Think of it like npm for Python, but faster.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh | sh
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc7jimmayf1fvhemmaczz.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%2Fc7jimmayf1fvhemmaczz.png" alt=" " width="798" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now add it to your PATH so you can use it right 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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.local/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nt"&gt;--version&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftaodezujlflkpbsd14x9.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%2Ftaodezujlflkpbsd14x9.png" alt=" " width="690" height="117"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Install CrewAI CLI
&lt;/h3&gt;

&lt;p&gt;This gives you the &lt;code&gt;crewai&lt;/code&gt; command the tool you'll use to create projects, install dependencies, and run agents.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool &lt;span class="nb"&gt;install &lt;/span&gt;crewai-cli &lt;span class="nt"&gt;--python&lt;/span&gt; python3.11 &lt;span class="nt"&gt;--with&lt;/span&gt; crewai
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh5znqczfy2tqi588btvr.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%2Fh5znqczfy2tqi588btvr.png" alt=" " width="796" height="69"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two things to note here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's &lt;code&gt;crewai-cli&lt;/code&gt;, not &lt;code&gt;crewai&lt;/code&gt;. If you install just &lt;code&gt;crewai&lt;/code&gt;, you get the library but not the CLI command.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;--with crewai&lt;/code&gt; flag is important it includes the crewai library in the CLI tool's environment. Without it, &lt;code&gt;crewai install&lt;/code&gt; will fail later with a &lt;code&gt;ModuleNotFoundError&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crewai 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff2c1bvn7957pc7ju65ag.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%2Ff2c1bvn7957pc7ju65ag.png" alt=" " width="664" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. Environment is ready.&lt;/p&gt;


&lt;h2&gt;
  
  
  Creating the Project
&lt;/h2&gt;

&lt;p&gt;CrewAI has a scaffolding command that creates the entire project structure for you. No need to create folders manually.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crewai create crew resume_tailor &lt;span class="nt"&gt;--classic&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3icbh1nrndxv7fi7d7gd.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%2F3icbh1nrndxv7fi7d7gd.png" alt=" " width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--classic&lt;/code&gt; flag gives you the standard Python/YAML project structure (which is what we want). Without it, newer versions of CrewAI may generate a JSON-based structure instead.&lt;/p&gt;

&lt;p&gt;It'll ask you a few questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provider:&lt;/strong&gt; Choose bedrock (option 9 in the menu)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; Choose &lt;code&gt;bedrock/us.amazon.nova-pro-v1:0&lt;/code&gt; (option 28)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS keys:&lt;/strong&gt; Press Enter to skip we'll use the IAM role attached to the instance (or your AWS CLI credentials)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This creates a folder called &lt;code&gt;resume_tailor&lt;/code&gt; with everything you need.&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%2F7avn54uzfznryekx92al.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%2F7avn54uzfznryekx92al.png" alt=" " width="799" height="308"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Quick fix - pyproject.toml (if needed)
&lt;/h3&gt;

&lt;p&gt;Check your generated &lt;code&gt;pyproject.toml&lt;/code&gt;. If you see a pinned pre-release version like &lt;code&gt;crewai[tools]==1.14.5a2&lt;/code&gt;, change it to:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;crewai[bedrock,tools]&amp;gt;=1.14.0,&amp;lt;2.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The scaffolding sometimes generates &lt;code&gt;crewai[tools]&lt;/code&gt; with a pre-release pin. We're changing it to include the &lt;code&gt;bedrock&lt;/code&gt; extra (since we're using Amazon Bedrock) and pinning to a stable version range.&lt;/p&gt;


&lt;h2&gt;
  
  
  What You Get - CrewAI Project Structure
&lt;/h2&gt;

&lt;p&gt;Let's look at what was generated. This is the standard CrewAI project structure every CrewAI project 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;resume_tailor/
├── src/resume_tailor/
│   ├── config/
│   │   ├── agents.yaml        ← WHO: define your agents here
│   │   └── tasks.yaml         ← WHAT: define what agents do
│   ├── tools/
│   │   └── custom_tool.py     ← HOW: custom tools (we won't need this)
│   ├── crew.py                ← WIRING: connects agents to tasks
│   └── main.py                ← START: entry point, kicks everything off
├── knowledge/                 ← FACTS: files agents can read
├── .env                       ← SECRETS: API keys, model config
└── pyproject.toml             ← DEPENDENCIES: Python packages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here's how to think about it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;agents.yaml&lt;/code&gt; - you're writing a job description for your AI worker. Who are they? What's their expertise?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tasks.yaml&lt;/code&gt; - you're writing a work order. What exactly should they do? What should the output look like?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;crew.py&lt;/code&gt; - you're the manager connecting the worker to the work order.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main.py&lt;/code&gt; - you're pressing the "start" button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole framework. Four files that matter.&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%2Fy0taqn2edkxidp2ans8g.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%2Fy0taqn2edkxidp2ans8g.png" alt=" " width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's go into the project:&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;cd &lt;/span&gt;resume_tailor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring the Agent
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;src/resume_tailor/config/agents.yaml&lt;/code&gt;. This is where you define WHO your agent is.&lt;/p&gt;

&lt;p&gt;Replace everything in the file with this:&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;resume_tailor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;Senior Resume Tailor &amp;amp; Career Advisor&lt;/span&gt;
  &lt;span class="na"&gt;goal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;Analyze the job description, compare it with the candidate's resume,&lt;/span&gt;
    &lt;span class="s"&gt;identify skill gaps, and rewrite the resume to match the job requirements.&lt;/span&gt;
  &lt;span class="na"&gt;backstory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;You're a seasoned career advisor with 15+ years of experience in tech recruiting.&lt;/span&gt;
    &lt;span class="s"&gt;You've reviewed thousands of resumes and job descriptions. You know exactly what&lt;/span&gt;
    &lt;span class="s"&gt;hiring managers look for, can instantly spot skill gaps, and you rewrite resumes&lt;/span&gt;
    &lt;span class="s"&gt;that get interviews. You're direct, practical, and never give generic fluff.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Three things here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Role&lt;/strong&gt; - tells the agent who it is. "Senior Resume Tailor &amp;amp; Career Advisor" not just "helper" or "assistant." The more specific the role, the better the output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal&lt;/strong&gt; - tells it what to achieve. This is the finish line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backstory&lt;/strong&gt; - gives it personality. "15+ years in tech recruiting" makes it write like someone who's actually done this, not like a generic chatbot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it like hiring a contractor. You wouldn't say "do some work." You'd say "You're a senior plumber. Your job is to fix the kitchen leak. You've been doing this for 20 years." Same idea.&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%2Fqsmzl9eao25bwaofn6dm.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%2Fqsmzl9eao25bwaofn6dm.png" alt=" " width="799" height="252"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Configuring the Task
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;src/resume_tailor/config/tasks.yaml&lt;/code&gt;. This is the work order what exactly should the agent do?&lt;/p&gt;

&lt;p&gt;Replace everything with this:&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;tailor_resume_task&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="pi"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;Analyze the following job description and produce career guidance.&lt;/span&gt;

    &lt;span class="s"&gt;Job Description:&lt;/span&gt;
    &lt;span class="s"&gt;{job_description}&lt;/span&gt;

    &lt;span class="s"&gt;Candidate's Resume:&lt;/span&gt;
    &lt;span class="s"&gt;{resume}&lt;/span&gt;

    &lt;span class="s"&gt;Years of Experience: {years_experience}&lt;/span&gt;
    &lt;span class="s"&gt;Expertise Level: {expertise_level}&lt;/span&gt;

    &lt;span class="s"&gt;IMPORTANT: Tailor your suggestions based on the candidate's experience and expertise level.&lt;/span&gt;
    &lt;span class="s"&gt;A Fresher needs different guidance than a Senior or Architect. Adjust complexity, depth, and expectations accordingly.&lt;/span&gt;

    &lt;span class="s"&gt;IMPORTANT: If the candidate's resume is "Not provided", skip sections 2, 3, and 5.&lt;/span&gt;
    &lt;span class="s"&gt;Instead, provide: JD Summary, Key Technologies, and Career Path.&lt;/span&gt;

    &lt;span class="s"&gt;If the resume IS provided, include all 5 sections:&lt;/span&gt;

    &lt;span class="s"&gt;1. JD Summary - One or two sentences max. What role/expertise are they looking for?&lt;/span&gt;

    &lt;span class="s"&gt;2. Matching Skills - List as key technologies/keywords the candidate should highlight in their resume (e.g., LLM, MCP, RAG, Kubernetes, Terraform, CrewAI). Just the tech keywords, no sentences.&lt;/span&gt;

    &lt;span class="s"&gt;3. Skill Gaps - Bullet points, short and simple. One gap per bullet. No paragraphs.&lt;/span&gt;

    &lt;span class="s"&gt;4. Career Guidance - Short bullet points showing the preparation path for this JD based on their expertise level. Like a career roadmap with heads/topics to learn. Example:&lt;/span&gt;
       &lt;span class="s"&gt;- Learn X&lt;/span&gt;
       &lt;span class="s"&gt;- Get certified in Y&lt;/span&gt;
       &lt;span class="s"&gt;- Build a project using Z&lt;/span&gt;

    &lt;span class="s"&gt;5. Tailored Resume - Rewrite the candidate's resume bullet points to match the job description. Adjust tone and depth based on expertise level. Ready to copy-paste.&lt;/span&gt;

  &lt;span class="na"&gt;expected_output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;If resume provided: JD Summary, Matching Skills (key technologies as keywords), Skill Gaps (bullet points), Career Guidance (short career path bullets based on expertise level), Tailored Resume.&lt;/span&gt;
    &lt;span class="s"&gt;If resume not provided: JD Summary, Key Technologies, Career Path (adjusted for expertise level).&lt;/span&gt;
  &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;resume_tailor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A few things to notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{job_description}&lt;/code&gt;, &lt;code&gt;{resume}&lt;/code&gt;, &lt;code&gt;{years_experience}&lt;/code&gt;, &lt;code&gt;{expertise_level}&lt;/code&gt; these are variables. They get filled in at runtime when you pass inputs. CrewAI handles the interpolation.&lt;/li&gt;
&lt;li&gt;The agent adapts based on expertise level a Fresher gets beginner-friendly guidance, a Senior gets advanced recommendations.&lt;/li&gt;
&lt;li&gt;The resume is optional if "Not provided", the agent adapts and gives a shorter but still useful output (JD Summary, Key Technologies, Career Path).&lt;/li&gt;
&lt;li&gt;Matching Skills outputs key technology keywords (like LLM, RAG, Kubernetes) that the candidate should add to their resume.&lt;/li&gt;
&lt;li&gt;Skill Gaps are short bullet points one gap per bullet, no paragraphs.&lt;/li&gt;
&lt;li&gt;Career Guidance gives a career roadmap with actionable steps tailored to your level.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;agent: resume_tailor&lt;/code&gt; this connects the task to the agent we defined in agents.yaml. The names must match.&lt;/li&gt;
&lt;/ul&gt;

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


&lt;h2&gt;
  
  
  Wiring It Up - crew.py
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;src/resume_tailor/crew.py&lt;/code&gt;. This is where you connect the agent to the task.&lt;/p&gt;

&lt;p&gt;Replace everything with this:&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;crewai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai.project&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CrewBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai.agents.agent_builder.base_agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseAgent&lt;/span&gt;


&lt;span class="nd"&gt;@CrewBase&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ResumeTailor&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;ResumeTailor crew&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="nd"&gt;@agent&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resume_tailor&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;config&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;agents_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;resume_tailor&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore[index]
&lt;/span&gt;            &lt;span class="n"&gt;verbose&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="nd"&gt;@task&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tailor_resume_task&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="o"&gt;-&amp;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;return&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;config&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;tasks_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tailor_resume_task&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore[index]
&lt;/span&gt;            &lt;span class="n"&gt;output_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tailored_resume.md&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@crew&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;crew&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;agents&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;agents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;tasks&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;tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sequential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;verbose&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@CrewBase&lt;/code&gt; - tells CrewAI this class is a crew. It automatically loads your YAML configs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@agent&lt;/code&gt; method - creates the agent from the YAML config. The method name &lt;code&gt;resume_tailor&lt;/code&gt; must match the key in agents.yaml.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@task&lt;/code&gt; method - creates the task from the YAML config. Same rule &lt;code&gt;tailor_resume_task&lt;/code&gt; must match tasks.yaml.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;output_file='tailored_resume.md'&lt;/code&gt; - saves the result to a file. Nice for reviewing later.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Process.sequential&lt;/code&gt; - tasks run one after another. We only have one task, so this doesn't matter much here. But if you add more agents later, this controls the order.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verbose=True&lt;/code&gt; - shows you what the agent is thinking while it works. Great for debugging. Turn it off in production.&lt;/li&gt;
&lt;/ul&gt;

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


&lt;h2&gt;
  
  
  Setting Up the Entry Point - main.py
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;src/resume_tailor/main.py&lt;/code&gt;. This is where you pass inputs and kick off the agent.&lt;/p&gt;

&lt;p&gt;Replace everything with this:&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;#!/usr/bin/env python
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;warnings&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;resume_tailor.crew&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ResumeTailor&lt;/span&gt;

&lt;span class="n"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filterwarnings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;SyntaxWarning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pysbd&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;run&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;inputs&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;job_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;We are looking for a Senior Cloud Engineer with experience in AWS, Terraform, Kubernetes, and CI/CD pipelines. Must have 5+ years experience with cloud infrastructure, strong Python skills, and experience with monitoring tools like Datadog or CloudWatch.&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;resume&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;Cloud Engineer with 3 years experience. Skills: AWS (EC2, S3, Lambda), Docker, Jenkins, Python, CloudFormation. Built CI/CD pipelines for microservices. Managed monitoring with CloudWatch. Deployed applications on 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;years_experience&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;3&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;expertise_level&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;Mid-Level&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;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nc"&gt;ResumeTailor&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;kickoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;inputs&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;An error occurred while running the crew: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&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;This is just for testing. We're hardcoding a sample resume and job description so we can verify everything works before adding the UI. The Streamlit app will replace these hardcoded inputs with whatever the user types.&lt;/p&gt;


&lt;h2&gt;
  
  
  Configuring the Environment
&lt;/h2&gt;

&lt;p&gt;Open the &lt;code&gt;.env&lt;/code&gt; file in the project root. Remove anything that was auto-generated and just keep these two lines:&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;MODEL&lt;/span&gt;=&lt;span class="n"&gt;bedrock&lt;/span&gt;/&lt;span class="n"&gt;us&lt;/span&gt;.&lt;span class="n"&gt;amazon&lt;/span&gt;.&lt;span class="n"&gt;nova&lt;/span&gt;-&lt;span class="n"&gt;pro&lt;/span&gt;-&lt;span class="n"&gt;v1&lt;/span&gt;:&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;AWS_REGION_NAME&lt;/span&gt;=&lt;span class="n"&gt;us&lt;/span&gt;-&lt;span class="n"&gt;east&lt;/span&gt;-&lt;span class="m"&gt;1&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsw8ur8hw8bm969cd0enm.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%2Fsw8ur8hw8bm969cd0enm.png" alt=" " width="757" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. The model to use and the AWS region.&lt;/p&gt;

&lt;p&gt;If you're on EC2 with an IAM role attached, you don't need AWS keys the SDK picks up credentials automatically from the instance profile. Don't put AWS access keys in this file if you're using an IAM role it's unnecessary and a security risk. If you're running locally without an IAM role, make sure your AWS CLI is configured:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F57r2gxb6stj8zverj80t.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%2F57r2gxb6stj8zverj80t.png" alt=" " width="800" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When it asks for region, set it to &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Don't pass a region parameter in the model name Nova Pro is a cross-region model and adding a region breaks it. Just set &lt;code&gt;AWS_REGION_NAME&lt;/code&gt; in the .env and leave it at that.&lt;/p&gt;


&lt;h2&gt;
  
  
  Installing Dependencies and Running
&lt;/h2&gt;

&lt;p&gt;First, lock and install all the dependencies:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crewai &lt;span class="nb"&gt;install&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpk5cepemnmu56xlj3hn3.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%2Fpk5cepemnmu56xlj3hn3.png" alt=" " width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This creates a &lt;code&gt;uv.lock&lt;/code&gt; file and installs everything the project needs.&lt;/p&gt;

&lt;p&gt;Now add Bedrock support if you already updated your &lt;code&gt;pyproject.toml&lt;/code&gt; to include &lt;code&gt;crewai[bedrock,tools]&lt;/code&gt;, you can skip this step. Otherwise, add it now:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add &lt;span class="s2"&gt;"crewai[bedrock]"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fohgqjcx2goh4r16bwhdb.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%2Fohgqjcx2goh4r16bwhdb.png" alt=" " width="799" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, the moment of truth:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crewai run
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl81ps4dnqb4axl07iuc1.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%2Fl81ps4dnqb4axl07iuc1.png" alt=" " width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll see the agent start up, show you what it's thinking, and then produce the output. The whole thing takes about 20-30 seconds.&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%2Fp6vujmounuw08445zoew.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%2Fp6vujmounuw08445zoew.png" alt=" " width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result also gets saved to &lt;code&gt;tailored_resume.md&lt;/code&gt; in your project folder.&lt;/p&gt;

&lt;p&gt;If you got output congratulations. You just built your first AI agent. It read a job description, compared it to a resume, found the gaps, and rewrote the resume to match. All on its own.&lt;/p&gt;


&lt;h2&gt;
  
  
  Adding a Streamlit UI
&lt;/h2&gt;

&lt;p&gt;Running agents from the terminal is fine for testing. But try showing that to someone who doesn't code. "Look at this terminal output!" doesn't impress anyone.&lt;/p&gt;

&lt;p&gt;Streamlit gives us a web interface in about 30 lines of code. Two text boxes, a button, results on screen. Anyone can use it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Install Streamlit
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add streamlit
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu5744updty4846agv7lo.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%2Fu5744updty4846agv7lo.png" alt=" " width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create the UI
&lt;/h3&gt;

&lt;p&gt;Create a file called &lt;code&gt;streamlit_app.py&lt;/code&gt; in the project root (not inside src/):&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;resume_tailor.crew&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ResumeTailor&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;Resume Tailor Agent&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="c1"&gt;# Header
&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
&amp;lt;h1 style=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text-align: center;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;📄 Resume Tailor Agent&amp;lt;/h1&amp;gt;
&amp;lt;p style=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text-align: center; color: gray; font-size: 1.1em;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;Paste a job description → Get career guidance, skill gaps, and a tailored resume in seconds.&amp;lt;/p&amp;gt;
&amp;lt;hr&amp;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;unsafe_allow_html&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="c1"&gt;# Input section
&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;📥 Input&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;job_description&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_area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Job Description *&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Paste the job description here (required)...&lt;/span&gt;&lt;span class="sh"&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;resume&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_area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your Resume (Optional)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Paste your resume here leave empty if you don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t have one yet...&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="n"&gt;col4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col5&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="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;years_experience&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;number_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;Years of Experience *&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_value&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="n"&gt;max_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&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;expertise_level&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;Expertise Level *&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;Fresher&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;Junior&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;Mid-Level&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;Senior&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;Lead&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;Architect&lt;/span&gt;&lt;span class="sh"&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;col5&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;br&amp;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;unsafe_allow_html&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;analyze_btn&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;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;🚀 Analyze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary&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="c1"&gt;# Info box
&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;job_description&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;💡 **Tip:** Even without a resume, you&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ll get JD analysis, key technologies, and a career path to follow.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Process
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;analyze_btn&lt;/span&gt;&lt;span class="p"&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;job_description&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;⚠️ Please paste the job description to continue.&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;divider&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;📊 Analysis Results&lt;/span&gt;&lt;span class="sh"&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;spinner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🤖 Agent is analyzing... this takes about 30 seconds.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;inputs&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;job_description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;job_description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resume&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resume&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not provided&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;years_experience&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;years_experience&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expertise_level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;expertise_level&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="nc"&gt;ResumeTailor&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;kickoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;inputs&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Analysis complete!&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Download button
&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;download_button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;📥 Download Results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;data&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="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resume_analysis.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;mime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/markdown&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;# Footer
&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;divider&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
&amp;lt;p style=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text-align: center; color: gray; font-size: 0.85em;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;
Built with CrewAI + Amazon Bedrock Nova Pro | By &amp;lt;a href=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://sarvarnadaf.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;Sarvar Nadaf&amp;lt;/a&amp;gt;
&amp;lt;/p&amp;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;unsafe_allow_html&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;/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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3bkhnfv497d60newxzr2.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%2F3bkhnfv497d60newxzr2.png" alt=" " width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few things to notice compared to a bare-bones approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wide layout and styled header for a cleaner look&lt;/li&gt;
&lt;li&gt;An info tip box when JD is empty reminds users they can still get value without a resume&lt;/li&gt;
&lt;li&gt;A download button so users can save the analysis as a markdown file&lt;/li&gt;
&lt;li&gt;A footer with attribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're on EC2, make sure port 8501 is open in your security group before running. Go to EC2 console &amp;gt; your instance &amp;gt; Security &amp;gt; Security group &amp;gt; Edit inbound rules &amp;gt; Add rule: Custom TCP, port 8501, source 0.0.0.0/0. Save. (For production, restrict this to your IP instead of opening to the world.)&lt;/p&gt;
&lt;h3&gt;
  
  
  Run It
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run streamlit run streamlit_app.py &lt;span class="nt"&gt;--server&lt;/span&gt;.address 0.0.0.0 &lt;span class="nt"&gt;--server&lt;/span&gt;.port 8501
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open your browser and go to &lt;code&gt;http://your-server-ip:8501&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Paste a job description on the left, your resume on the right, click the button, and watch the agent work.&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%2Fwr9sf35p4ffdz3eb8y7s.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%2Fwr9sf35p4ffdz3eb8y7s.png" alt=" " width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Let's Test It - Real Input, Real Output
&lt;/h2&gt;

&lt;p&gt;Now that it's working, let's throw some real-ish data at it and see how good the output actually is.&lt;/p&gt;

&lt;p&gt;For this test, I'm using a resume of an AI Engineer with 6 years of total experience, applying for a Senior GenAI Developer role. Both are kept short 5-6 bullet points each. I'm using random data here; you can use your own.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Resume (what we already have):
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Developed machine learning and Generative AI solutions using Python, TensorFlow, PyTorch, Scikit-learn, and LLM frameworks.
- Built and deployed RAG-based chatbots, AI assistants, and NLP applications using LangChain, LangGraph, and vector databases.
- Designed and optimized data pipelines, performed feature engineering, and trained predictive models for business use cases.
- Developed REST APIs with FastAPI/Flask and deployed AI applications on AWS, Azure, or Google Cloud using Docker and Kubernetes.
- Integrated foundation models, embeddings, prompt engineering, and MCP-based tools into enterprise AI workflows.
- Worked with SQL/NoSQL databases, data visualization tools, and cloud-native services to support AI and analytics solutions.
- Collaborated with cross-functional teams to deliver scalable, production-ready AI applications while following MLOps and DevOps best practices.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  The Job Description (what we're targeting):
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Design, develop, and deploy enterprise-grade Generative AI applications using Large Language Models (LLMs).
- Build AI agents, RAG pipelines, and MCP-based integrations using frameworks such as LangChain, LangGraph, and LlamaIndex.
- Develop scalable backend services and REST APIs using Python, FastAPI, or Flask, and deploy them on cloud platforms (AWS/Azure/GCP).
- Integrate vector databases, embeddings, and foundation models to deliver intelligent search and conversational AI solutions.
- Optimize AI applications for performance, scalability, security, cost efficiency, and responsible AI practices.
- Implement CI/CD pipelines, containerization (Docker/Kubernetes), monitoring, and MLOps/LLMOps best practices.
- Collaborate with cross-functional teams, mentor junior developers, and contribute to solution architecture and technical design.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  The Settings:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Years of Experience:&lt;/strong&gt; 6&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expertise Level:&lt;/strong&gt; Mid-Level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick note on expertise level this isn't your total years in tech. It's how deep you are specifically in the GenAI space. You might have 6 years overall but still be mid-level when it comes to GenAI work. The agent adjusts its guidance and resume rewriting based on this, so set it based on your domain expertise, not your total career length.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Results
&lt;/h3&gt;

&lt;p&gt;Let's see what the agent gives us. Remember the output has five sections when you provide both a JD and a resume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. JD Summary&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;2. Matching Skills&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;3. Skill Gaps&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;4. Career Guidance&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;5. Tailored Resume&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fixfkrzege7big801g2l6.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%2Fixfkrzege7big801g2l6.png" alt=" " width="799" height="247"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why This Test Matters
&lt;/h3&gt;

&lt;p&gt;You might be wondering - why pick two slightly different skill sets? The resume is an AI Engineer. The JD is a Senior GenAI Developer. They overlap, but they're not identical.&lt;/p&gt;

&lt;p&gt;That's the point. I wanted to see how the agent handles the gap between "close but not quite" - and the results are genuinely impressive. It identified exactly where the resume falls short, suggested a clear path to bridge the gap, and rewrote the bullet points to speak the JD's language.&lt;/p&gt;

&lt;p&gt;And here's the thing - you can customize the output further. Want exactly 8 tailored resume points? Add that to the task description. Want the career guidance to focus only on certifications? Tweak the prompt. The agent does what you tell it to. You control the output by controlling the instructions.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Just Happened
&lt;/h2&gt;

&lt;p&gt;Let's step back and look at what we built:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One YAML file to define the agent (who it is, what it's good at)&lt;/li&gt;
&lt;li&gt;One YAML file to define the task (what to do, what the output should look like)&lt;/li&gt;
&lt;li&gt;One Python file to wire them together&lt;/li&gt;
&lt;li&gt;One Python file for the web UI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Four files. That's a working AI agent with a web interface.&lt;/p&gt;

&lt;p&gt;The agent reads the job description, compares it to the resume (if provided), identifies key technologies to highlight, lists skill gaps as clean bullet points, gives a career roadmap, and rewrites the entire resume to match all in one pass, in about 30 seconds.&lt;/p&gt;

&lt;p&gt;Even without a resume, it still delivers value JD analysis, key technologies, and a preparation path. So anyone can use it, whether they have a resume ready or not.&lt;/p&gt;

&lt;p&gt;No training data. No fine-tuning. No complex pipelines. Just a well-written prompt, a good LLM, and a framework that handles the plumbing.&lt;/p&gt;


&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;The complete source code for this project is available on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/simplynadaf" rel="noopener noreferrer"&gt;
        simplynadaf
      &lt;/a&gt; / &lt;a href="https://github.com/simplynadaf/resume-tailor-agent" rel="noopener noreferrer"&gt;
        resume-tailor-agent
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AI agent that analyzes job descriptions, identifies skill gaps, and tailors your resume - built with CrewAI and Amazon Bedrock.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;📄 Resume Tailor Agent&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;AI-powered resume tailoring — paste a job description, get a perfect match in seconds.&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://crewai.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7cdc69810a095f81345819f961753d8e7f279946f03d377b9257dae9de41c202/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4275696c74253230776974682d4372657741492d626c75653f7374796c653d666f722d7468652d6261646765" alt="CrewAI"&gt;&lt;/a&gt;
&lt;a href="https://aws.amazon.com/bedrock/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/45f0409e4350b359b91b36c447f709e2b0f1f9206fb5f1dad64e23cb498fa6dd/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c4c4d2d416d617a6f6e253230426564726f636b2d6f72616e67653f7374796c653d666f722d7468652d6261646765" alt="Amazon Bedrock"&gt;&lt;/a&gt;
&lt;a href="https://streamlit.io" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2f227e4780bd3e01e8c72f946067aa787867cafa2b0c384b14b7ca07a5077be9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f55492d53747265616d6c69742d7265643f7374796c653d666f722d7468652d6261646765" alt="Streamlit"&gt;&lt;/a&gt;
&lt;a href="https://python.org" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0d5baa2bb4112d14c526944c9c1827a966b6e8c92fccd20c6634e97b733d0c38/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f507974686f6e2d332e31312b2d677265656e3f7374796c653d666f722d7468652d6261646765" alt="Python"&gt;&lt;/a&gt;
&lt;a href="https://github.com/simplynadaf/resume-tailor-agent/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2792a6b590e1b7fbcc5f7c80df8da3149453c596df80f16fa86bd82c487bec8d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f773f7374796c653d666f722d7468652d6261646765" alt="License: MIT"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/simplynadaf/resume-tailor-agent/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/c5c85213636886698541cd1ed88a5d13822eb92e8bb17d173d48ed4b61a11ece/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f73696d706c796e616461662f726573756d652d7461696c6f722d6167656e743f7374796c653d736f6369616c" alt="Stars"&gt;&lt;/a&gt;
&lt;a href="https://github.com/simplynadaf/resume-tailor-agent/network/members" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/950933626e1712d680f6ec68cf98ac894f27b474ac4cf2aaedac539c769d0313/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f666f726b732f73696d706c796e616461662f726573756d652d7461696c6f722d6167656e743f7374796c653d736f6369616c" alt="Forks"&gt;&lt;/a&gt;
&lt;a href="https://github.com/simplynadaf/resume-tailor-agent/issues" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/48a58e625549535fca24f60235dc7ece8779ea065aae7bfe1fb2222ad96616a8/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f73696d706c796e616461662f726573756d652d7461696c6f722d6167656e74" alt="Issues"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⭐ If this helped you, give it a star! It helps others find it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/simplynadaf/resume-tailor-agent#-getting-started" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt; • &lt;a href="https://github.com/simplynadaf/resume-tailor-agent#-how-it-works" rel="noopener noreferrer"&gt;How It Works&lt;/a&gt; • &lt;a href="https://github.com/simplynadaf/resume-tailor-agent#-example-output" rel="noopener noreferrer"&gt;Demo&lt;/a&gt; • &lt;a href="https://github.com/simplynadaf/resume-tailor-agent#-contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🤔 The Problem&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;You're job hunting. Every company wants something slightly different. You've done the work — but your resume doesn't say it in the JD's language. ATS systems scan for keywords before a human ever sees your application. Wrong framing = filtered out automatically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One application = 30 minutes of tweaking.&lt;/strong&gt; Multiply that by 10+ applications.&lt;/p&gt;
&lt;p&gt;This agent does it in &lt;strong&gt;30 seconds.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ What It Does&lt;/h2&gt;

&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;One agent. One pass. Five powerful outputs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width="50%"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;🎯 With JD + Resume&lt;/h3&gt;

&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;What You Get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;📋 JD Summary&lt;/td&gt;
&lt;td&gt;What they're &lt;em&gt;actually&lt;/em&gt; looking for&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔑 Matching Skills&lt;/td&gt;
&lt;td&gt;ATS keywords to highlight (e.g., &lt;code&gt;LLM&lt;/code&gt;, &lt;code&gt;RAG&lt;/code&gt;, &lt;code&gt;K8s&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
⚠️ Skill&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;


&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/simplynadaf/resume-tailor-agent" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;





&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;Follow these steps to run the project on your machine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/simplynadaf/resume-tailor-agent.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Navigate to the project and set up:
&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;cd &lt;/span&gt;resume-tailor-agent
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
crewai &lt;span class="nb"&gt;install
&lt;/span&gt;uv add &lt;span class="s2"&gt;"crewai[bedrock]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run the agent:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crewai run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Or launch the web UI:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run streamlit run streamlit_app.py &lt;span class="nt"&gt;--server&lt;/span&gt;.address 0.0.0.0 &lt;span class="nt"&gt;--server&lt;/span&gt;.port 8501
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;We built one agent that does everything analyzes the JD, finds gaps, rewrites the resume. It works. But what if we split the work across specialists? A recruiter who decodes the JD, an analyst who finds the gaps, and a writer who nails the rewrite. Each one focused, each one sharper.&lt;/p&gt;

&lt;p&gt;In the next article, we're upgrading to a multi-agent crew and you'll see how collaboration between specialized agents produces noticeably better results than one agent doing it all.&lt;/p&gt;

&lt;p&gt;Until then try it with your own resume. Paste a real job description you're interested in. See what the agent comes up with. You might be surprised how good it is&lt;/p&gt;




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

&lt;p&gt;Thanks for reading! If this was helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value&lt;/li&gt;
&lt;li&gt;💾 Save for later&lt;/li&gt;
&lt;li&gt;🔄 Share with your team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow me for more on:&lt;/strong&gt; AWS architecture, FinOps, DevOps, and AI Infrastructure.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit my website&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;Connect on LinkedIn&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Learning&lt;/strong&gt; 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>agents</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Introducing Lambda MicroVMs - Isolated, Stateful Sandboxes for Running Untrusted Code on AWS</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Tue, 30 Jun 2026 13:42:54 +0000</pubDate>
      <link>https://dev.to/aws-builders/introducing-lambda-microvms-isolated-stateful-sandboxes-for-running-untrusted-code-on-aws-5chf</link>
      <guid>https://dev.to/aws-builders/introducing-lambda-microvms-isolated-stateful-sandboxes-for-running-untrusted-code-on-aws-5chf</guid>
      <description>&lt;p&gt;👋 Hey there, Tech Enthusiasts!&lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect who loves turning complex tech problems into simple solutions. I've worked with AWS, Azure, DevOps, Data, Analytics, Generative-AI and Agentic-AI building real systems for real companies. In this article series, I'll share what I've learned in a way that's easy to follow, whether you're experienced or just getting started.&lt;/p&gt;

&lt;p&gt;Let's get into it! 🚀&lt;/p&gt;




&lt;p&gt;On June 22, 2026, AWS launched Lambda MicroVMs. It is a new compute primitive inside Lambda that gives you a dedicated Firecracker virtual machine per user or session. It is not an update to Lambda functions. It is a different thing with a different model, different pricing, and different use cases.&lt;/p&gt;

&lt;p&gt;If you are building anything where users or AI agents execute arbitrary code and you need strong isolation, fast startup, and persistent state this is what you reach for now.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Existed Before and Why It Was Not Enough
&lt;/h2&gt;

&lt;p&gt;Before this launch, if you needed to sandbox untrusted code on AWS, you had three options. Each one forced a compromise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EC2 instances&lt;/strong&gt; give you full VM isolation and persistent state. But they are not fast enough for interactive use. Between AMI boot, instance initialization, and user data scripts, you are looking at 30 seconds to several minutes before a user can do anything. That kills the experience for coding assistants or on-demand sandboxes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Containers (ECS/Fargate)&lt;/strong&gt; start faster and keep state while running. But containers share a kernel with the host. That shared kernel is a security boundary you cannot fully trust when running code from strangers on the internet. You can layer security on top, but the fundamental model is weaker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda functions&lt;/strong&gt; give you real VM-level isolation (they already run on Firecracker) and start in milliseconds. But they die after 15 minutes. They are stateless between invocations. They follow a request/response model. You cannot give a user a persistent environment where they write code, run it, see the output, install a package, and run again all within the same session.&lt;/p&gt;

&lt;p&gt;Teams building coding assistants, AI agent sandboxes, or multi-tenant notebook platforms had to stitch together custom solutions. EC2 with a lifecycle manager. ECS with heavy security configuration. Running Firecracker directly on bare metal. All of it was operational overhead solving a problem that should have had a managed answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Lambda MicroVMs Actually Is
&lt;/h2&gt;

&lt;p&gt;Lambda MicroVMs is that managed answer.&lt;/p&gt;

&lt;p&gt;You package your application code and a Dockerfile into a zip archive, upload it to S3, and call the Lambda API to create a MicroVM image. Lambda executes your Dockerfile, starts your application, and captures a snapshot of the fully initialized environment.&lt;/p&gt;

&lt;p&gt;When you need a sandbox for a user, you call &lt;code&gt;run-microvm&lt;/code&gt;. Lambda launches a MicroVM from that snapshot with rapid startup.&lt;/p&gt;

&lt;p&gt;Each MicroVM gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its own dedicated HTTPS endpoint (no load balancers or ingress infrastructure needed)&lt;/li&gt;
&lt;li&gt;Full VM-level isolation (separate kernel, no shared resources with other tenants)&lt;/li&gt;
&lt;li&gt;Persistent state for up to 8 hours (memory and disk survive suspend/resume)&lt;/li&gt;
&lt;li&gt;Automatic suspend when idle (you stop paying for compute)&lt;/li&gt;
&lt;li&gt;Automatic or programmatic resume when traffic arrives (picks up where it left off)&lt;/li&gt;
&lt;li&gt;Vertical scaling up to 4x the configured baseline CPU and memory (e.g., 2 GB / 1 vCPU baseline scales to 8 GB / 4 vCPU)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users connect over HTTP/2, gRPC, or WebSockets. Authentication is handled through bearer tokens you generate via the &lt;code&gt;CreateMicrovmAuthToken&lt;/code&gt; API.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model Shift
&lt;/h2&gt;

&lt;p&gt;This is important: Lambda MicroVMs is not request/response.&lt;/p&gt;

&lt;p&gt;With Lambda functions, a request comes in, your code runs, it returns a response, the invocation ends. Scaling is automatic. You think in terms of individual invocations.&lt;/p&gt;

&lt;p&gt;With Lambda MicroVMs, you spin up a persistent VM for a user or session. It stays alive. It has a dedicated URL. Multiple requests hit the same VM. State accumulates. The user installs a package, it is still there on the next request.&lt;/p&gt;

&lt;p&gt;You manage the fleet yourself. You decide when to create a MicroVM, which user it belongs to, and when to tear it down. There is no automatic horizontal scaling. Your application owns that logic.&lt;/p&gt;

&lt;p&gt;This is closer to managing a pool of servers than it is to writing Lambda functions. The difference is you do not manage the infrastructure underneath no AMIs, no instance types, no patching, no capacity planning for the host.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;Here is the basic workflow using the AWS CLI. &lt;em&gt;Command names below follow the API naming convention. Check the &lt;a href="https://docs.aws.amazon.com/lambda/latest/microvm-api/API_Operations.html" rel="noopener noreferrer"&gt;CLI reference&lt;/a&gt; for your SDK version.&lt;/em&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;# Step 1: Upload your application package (Dockerfile + code) to S3&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;my-sandbox-app.zip s3://my-bucket/my-sandbox-app.zip

&lt;span class="c"&gt;# Step 2: Create a MicroVM image from your package&lt;/span&gt;
aws lambda create-microvm-image &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-name&lt;/span&gt; my-sandbox &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--s3-bucket&lt;/span&gt; my-bucket &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--s3-key&lt;/span&gt; my-sandbox-app.zip

&lt;span class="c"&gt;# Step 3: Launch a MicroVM for a user session&lt;/span&gt;
aws lambda run-microvm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-name&lt;/span&gt; my-sandbox &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory-size&lt;/span&gt; 2048

&lt;span class="c"&gt;# Step 4: Generate an auth token for the user to connect&lt;/span&gt;
aws lambda create-microvm-auth-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--microvm-id&lt;/span&gt; mvm-abc123

&lt;span class="c"&gt;# Step 5: When session ends, terminate&lt;/span&gt;
aws lambda terminate-microvm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--microvm-id&lt;/span&gt; mvm-abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user connects to the dedicated HTTPS endpoint returned by &lt;code&gt;run-microvm&lt;/code&gt; using their auth token. From there, they interact over HTTP/2, gRPC, or WebSockets depending on what your application exposes.&lt;/p&gt;

&lt;p&gt;For shell access (giving users a terminal inside the VM):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda create-microvm-shell-auth-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--microvm-id&lt;/span&gt; mvm-abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns credentials that connect directly to a pseudo-terminal inside the MicroVM. AI coding tools use this to provide real terminal experiences to end users.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Details That Matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Startup:&lt;/strong&gt; MicroVMs launch from pre-initialized snapshots, similar to how Lambda SnapStart works. This skips application initialization entirely your environment is already warm when it starts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Duration:&lt;/strong&gt; Up to 8 hours per session. After that, the MicroVM is terminated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Suspend/Resume:&lt;/strong&gt; When no traffic hits a MicroVM, it suspends automatically based on an idle timeout you configure (how long it waits with no inbound traffic before suspending). You stop paying for compute. When a request arrives, it resumes with memory and disk state fully intact. You can also trigger suspend and resume programmatically via &lt;code&gt;suspend-microvm&lt;/code&gt; and &lt;code&gt;resume-microvm&lt;/code&gt; APIs. Resume latency is not a full cold start it restores from a memory snapshot, which is significantly faster than booting from scratch. AWS has not published exact resume latency numbers at launch, so expect to benchmark this for your specific workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vertical scaling:&lt;/strong&gt; You configure a baseline (default is 2 GB memory / 1 vCPU, allocated in a 2:1 memory-to-CPU ratio). During peak activity, the MicroVM can burst to 4x that baseline automatically. For example, a 2 GB / 1 vCPU baseline can burst to 8 GB / 4 vCPU. An 8 GB / 4 vCPU baseline can burst to 32 GB / 16 vCPU. You only pay for the burst resources during the time they are actually consumed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shell access:&lt;/strong&gt; Full pseudo-terminal (&lt;code&gt;/dev/ptmx&lt;/code&gt;) support. The &lt;code&gt;CreateMicrovmShellAuthToken&lt;/code&gt; API lets you give users a real terminal inside their VM. This is how AI coding tools provide interactive terminal experiences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker inside:&lt;/strong&gt; You can run containers inside your MicroVM. Full OS capabilities are available installing system packages, mounting filesystems, running nested containers. One gotcha: outbound UDP is blocked by default, which breaks DNS resolution inside nested containers. Community workarounds exist for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture:&lt;/strong&gt; ARM64 (Graviton) only at launch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regions:&lt;/strong&gt; US East (N. Virginia), US East (Ohio), US West (Oregon), Europe (Ireland), Asia Pacific (Tokyo).&lt;/p&gt;




&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;Lambda MicroVMs pricing has three components: compute, snapshots, and data transfer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compute&lt;/strong&gt; (US East, ARM/Graviton):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;vCPU: $0.0000276944 per vCPU-second&lt;/li&gt;
&lt;li&gt;Memory: $0.0000036667 per GB-second&lt;/li&gt;
&lt;li&gt;Billed per second (not per millisecond like Lambda functions)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Snapshot write (on suspend): $0.0038 per GB&lt;/li&gt;
&lt;li&gt;Snapshot read (on launch or resume): $0.00155 per GB&lt;/li&gt;
&lt;li&gt;Image storage: $0.08 per GB-month (1-week minimum retention)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Concrete example - coding assistant platform:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;100 developers, each using a 2 GB / 1 vCPU MicroVM for 2.5 hours per day over 20 working days. Each environment suspends 6 times per day during idle periods. Monthly cost: approximately $1,241 total, or about $12.41 per developer per month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concrete example - CI/CD job runner:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;10,000 jobs per month, each running for 10 minutes in an 8 GB / 4 vCPU environment. No suspend/resume (jobs run to completion). Monthly cost: approximately $1,124 total, or about $0.11 per job.&lt;/p&gt;

&lt;p&gt;This pricing is closer to Fargate economics than Lambda function economics. You are paying for dedicated compute time, not per-invocation.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use This
&lt;/h2&gt;

&lt;p&gt;Use Lambda MicroVMs when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are building a coding assistant and need to execute AI-generated code safely&lt;/li&gt;
&lt;li&gt;You have a multi-tenant platform where each user runs custom scripts&lt;/li&gt;
&lt;li&gt;You need agent sandboxes where AI agents execute tools, install packages, and maintain state across steps&lt;/li&gt;
&lt;li&gt;You are building an interactive notebook or REPL that needs per-user isolation&lt;/li&gt;
&lt;li&gt;You run a vulnerability scanner that executes untrusted payloads&lt;/li&gt;
&lt;li&gt;You need game servers that run user-supplied scripts with isolation&lt;/li&gt;
&lt;li&gt;You are building reinforcement learning environments where each run needs a fresh, isolated sandbox&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not use Lambda MicroVMs when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have a standard API backend (use Lambda functions)&lt;/li&gt;
&lt;li&gt;You need automatic horizontal scaling without managing fleet logic (use Lambda functions or Fargate)&lt;/li&gt;
&lt;li&gt;You want a fully managed agent hosting platform (use Bedrock AgentCore Runtime)&lt;/li&gt;
&lt;li&gt;Your workload does not involve untrusted code execution (you probably do not need this level of isolation)&lt;/li&gt;
&lt;li&gt;You need x86 architecture (ARM64 only at launch)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How It Compares to Bedrock AgentCore Runtime
&lt;/h2&gt;

&lt;p&gt;Both services run on Firecracker. Both support 8-hour sessions. Both have shell access. But they solve different problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AgentCore Runtime&lt;/strong&gt; is a managed agent platform. You deploy your agent code, AWS handles session routing, scaling, teardown, agent communication protocols, and authentication. You do not think about VMs. Your users talk to your agent through a managed endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda MicroVMs&lt;/strong&gt; is a raw compute primitive. You get the VM. You manage which user gets which VM. You handle lifecycle, cleanup, and routing. You have full control of what runs inside.&lt;/p&gt;

&lt;p&gt;The analogy: AgentCore Runtime is to Lambda MicroVMs what Fargate is to EC2. Same isolation technology underneath, different level of abstraction on top.&lt;/p&gt;

&lt;p&gt;If you are building an AI agent and want managed hosting AgentCore. If you are building a platform where each user gets their own isolated environment to run whatever they want Lambda MicroVMs.&lt;/p&gt;




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

&lt;p&gt;You can provision Lambda MicroVMs through the AWS Console, CloudFormation, CDK, or the &lt;a href="https://github.com/aws/agent-toolkit-for-aws/tree/main/skills/specialized-skills/serverless-skills/aws-lambda-microvms" rel="noopener noreferrer"&gt;Agent Toolkit for AWS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The developer guide is here: &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-microvms-guide.html" rel="noopener noreferrer"&gt;Lambda MicroVMs Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;API reference: &lt;a href="https://docs.aws.amazon.com/lambda/latest/microvm-api/API_Operations.html" rel="noopener noreferrer"&gt;MicroVM API&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;Lambda MicroVMs fills a gap that has existed since serverless became mainstream. The question "how do I safely run someone else's code?" finally has a straightforward answer on AWS that does not involve stitching together three services and writing a custom orchestrator.&lt;/p&gt;

&lt;p&gt;It is not magic. You still have to manage your fleet of MicroVMs, handle routing, and build the lifecycle logic. But the hard part fast, isolated, stateful VMs without managing infrastructure is handled for you.&lt;/p&gt;

&lt;p&gt;For teams building AI-powered developer tools, this is probably the most relevant compute launch of 2026 so far.&lt;/p&gt;




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

&lt;p&gt;Thanks for reading! If this was helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value&lt;/li&gt;
&lt;li&gt;💾 Save for later&lt;/li&gt;
&lt;li&gt;🔄 Share with your team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow me for more on:&lt;/strong&gt; AWS architecture, FinOps, DevOps, and AI Infrastructure.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit my website&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;Connect on LinkedIn&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Learning&lt;/strong&gt; 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>You Don't Need an AWS Account to Learn AWS</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Thu, 25 Jun 2026 20:26:13 +0000</pubDate>
      <link>https://dev.to/aws-builders/you-dont-need-an-aws-account-to-learn-aws-4e6k</link>
      <guid>https://dev.to/aws-builders/you-dont-need-an-aws-account-to-learn-aws-4e6k</guid>
      <description>&lt;p&gt;👋 Hey there, Tech Enthusiasts!&lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect who loves turning complex tech problems into simple solutions. I've worked with AWS, Azure, DevOps, Data, Analytics, Generative-AI and Agentic-AI building real systems for real companies. In this article series, I'll share what I've learned in a way that's easy to follow, whether you're experienced or just getting started.&lt;/p&gt;

&lt;p&gt;Let's get into it! 🚀&lt;/p&gt;




&lt;p&gt;I once woke up to a $14 AWS bill because I forgot to stop an EC2 instance overnight. For a student, that felt like $1400. That one mistake made me afraid to touch anything in AWS for the next three weeks.&lt;/p&gt;

&lt;p&gt;I didn't come from a coding background. I didn't have anyone to guide me. I watched videos, read documentation, tried things on the AWS console, and every single time I was terrified of one thing the bill. After that incident, I stopped experimenting freely. I played it safe. And playing it safe is the worst thing you can do when you're trying to learn cloud.&lt;/p&gt;

&lt;p&gt;That fear slowed me down by months.&lt;/p&gt;

&lt;p&gt;In my first real project, I deployed a Lambda function that wrote to DynamoDB. During development, I never tested what happens when DynamoDB throttles writes because I was too afraid to generate real traffic on AWS. In production, messages started disappearing silently. I spent two days debugging something I could have caught in five minutes if I had a safe local environment to test against.&lt;/p&gt;

&lt;p&gt;Today, after years of working as a Cloud Architect across multiple companies and countries, I can tell you this with full confidence the single most important thing that separates a good cloud engineer from a mediocre one is hands-on practice. Not certifications. Not watching 40 hours of video. Hands-on. Breaking things. Fixing things. Understanding why something failed and how to bring it back.&lt;/p&gt;

&lt;p&gt;A few months ago I found a tool that solves all of this. It's called Floci an open-source local AWS emulator. You run it in Docker, it gives you &lt;a href="https://github.com/floci-io/floci#supported-services" rel="noopener noreferrer"&gt;58 AWS services&lt;/a&gt; at localhost, and it costs nothing. No AWS account. No credit card. No sign-up. No auth token.&lt;/p&gt;

&lt;p&gt;Let me show you how it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problems You're Probably Facing Right Now
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fear of billing.&lt;/strong&gt; Every time you try something new creating a VPC, launching an instance, testing Lambda there's this voice saying "what if I forget to delete this?" That fear kills curiosity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No safe playground.&lt;/strong&gt; The AWS Free Tier helps, but it has limits. Cross those limits once, and you get charged. For a student or a fresher, even a small unexpected bill feels massive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Theory without practice.&lt;/strong&gt; You watched hours of videos about S3 and DynamoDB. You can explain what they are. But can you actually use them? Can you create a bucket from the command line? Can you put data into a table and get it back? If not, you haven't really learned it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Starting over after every mistake.&lt;/strong&gt; When something breaks, most beginners delete everything and recreate it. They never learn to troubleshoot. They never learn why it broke. They just run away from the problem and start fresh. I did this for months.&lt;/p&gt;

&lt;p&gt;If any of this sounds familiar, keep reading.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before We Start: What You Need
&lt;/h2&gt;

&lt;p&gt;Two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; a tool that runs applications in isolated containers. Think of it as a lightweight virtual machine. Floci runs inside Docker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CLI&lt;/strong&gt; the command-line tool to interact with AWS services (optional, but I strongly recommend it)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. Here's how to install both:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Everything in this tutorial runs 100% offline on your own machine Mac, Windows, or Linux. I'm using an EC2 instance to demonstrate the steps, but that's just my setup. You don't need a cloud server. The exact same commands work on your laptop, completely offline, with no internet required after the initial install. That's the whole point.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Install Docker
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Windows / Mac:&lt;/strong&gt; Download and install &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;. Open it once installed that's all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ubuntu / Debian:&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;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &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; docker.io docker-compose-v2
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Amazon Linux / RHEL / Fedora:&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;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9g2e7gl61me3ug3os3w5.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%2F9g2e7gl61me3ug3os3w5.png" alt=" " width="800" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install Docker Compose plugin&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 mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /usr/local/lib/docker/cli-plugins
&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-SL&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/docker/compose/releases/latest/download/docker-compose-linux-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/lib/docker/cli-plugins/docker-compose
&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/lib/docker/cli-plugins/docker-compose
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ign1wj61910bzhmz182.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%2F4ign1wj61910bzhmz182.png" alt=" " width="800" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After running the Linux commands above, &lt;strong&gt;log out and log back in&lt;/strong&gt; (or run &lt;code&gt;newgrp docker&lt;/code&gt;) so the group change takes effect. Otherwise you'll get "permission denied" errors.&lt;/p&gt;
&lt;h3&gt;
  
  
  Install AWS CLI
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ubuntu / Debian / Amazon Linux:&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;curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.zip"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt;
unzip awscliv2.zip &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo&lt;/span&gt; ./aws/install
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fio4s8yfb80c2co9wrk1n.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%2Fio4s8yfb80c2co9wrk1n.png" alt=" " width="797" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(If &lt;code&gt;unzip&lt;/code&gt; is not found, install it first: &lt;code&gt;sudo apt-get install -y unzip&lt;/code&gt; or &lt;code&gt;sudo dnf install -y unzip&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mac:&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;curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/AWSCLIV2.pkg"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"AWSCLIV2.pkg"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;installer &lt;span class="nt"&gt;-pkg&lt;/span&gt; AWSCLIV2.pkg &lt;span class="nt"&gt;-target&lt;/span&gt; /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt; Download and run the installer from:&lt;br&gt;
&lt;a href="https://awscli.amazonaws.com/AWSCLIV2.msi" rel="noopener noreferrer"&gt;https://awscli.amazonaws.com/AWSCLIV2.msi&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Verify the installation on any platform:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws &lt;span class="nt"&gt;--version&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuvztqq3gad7lv1zmbnvb.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%2Fuvztqq3gad7lv1zmbnvb.png" alt=" " width="800" height="88"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Getting Floci Running
&lt;/h2&gt;

&lt;p&gt;Open your terminal, create a new folder, and create the compose file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;floci-playground &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;floci-playground
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fltzshfwgrdardhlviimn.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%2Fltzshfwgrdardhlviimn.png" alt=" " width="800" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now create a file called &lt;code&gt;docker-compose.yml&lt;/code&gt; inside this folder. You can use any text editor VS Code, nano, or even notepad. Paste this content:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;floci&lt;/span&gt;&lt;span class="pi"&gt;:&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;floci/floci:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4566:4566"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data:/app/data&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvf12bvkiflsat3uieuik.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%2Fvf12bvkiflsat3uieuik.png" alt=" " width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Start it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff0hyoahcxnp8tj8f0th1.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%2Ff0hyoahcxnp8tj8f0th1.png" alt=" " width="799" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you get &lt;code&gt;docker compose: command not found&lt;/code&gt;, try the older syntax: &lt;code&gt;docker-compose up -d&lt;/code&gt;. Both work the same way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wait a few seconds, then verify it's running:&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;# Linux / Mac&lt;/span&gt;
curl http://localhost:4566/_localstack/health
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ffv19h5l4fzn4tzwsex.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%2F2ffv19h5l4fzn4tzwsex.png" alt=" " width="800" height="127"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows (PowerShell)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://localhost:4566/_localstack/health&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You should see something like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"services"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"s3"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sqs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"dynamodb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1.5.25"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;All services showing &lt;code&gt;"running"&lt;/code&gt; you're good to go.&lt;/p&gt;

&lt;p&gt;Now tell your AWS CLI to talk to Floci instead of real AWS:&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;# Linux / Mac&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_ENDPOINT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:4566
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-east-1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test
export &lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqvy4d7j4f6s0rs68e5i3.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%2Fqvy4d7j4f6s0rs68e5i3.png" alt=" " width="800" height="114"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows (PowerShell)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;AWS_ENDPOINT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4566"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The credentials can be anything. I use &lt;code&gt;test&lt;/code&gt; because it's simple. Floci doesn't validate them it just needs non-empty values.&lt;/p&gt;


&lt;h2&gt;
  
  
  Your First Win: Create an S3 Bucket
&lt;/h2&gt;

&lt;p&gt;S3 is the storage backbone of AWS. Logs go to S3. Backups go to S3. Static websites live on S3. If you understand S3, you already understand 30% of how AWS works.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 mb s3://my-first-bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Output:&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;make_bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-first-bucket&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffu09zo6sx2mhgm5wv2fn.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%2Ffu09zo6sx2mhgm5wv2fn.png" alt=" " width="800" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. You just created a bucket. One command. No console. No waiting.&lt;/p&gt;

&lt;p&gt;Now upload a file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"hello from my laptop"&lt;/span&gt; | aws s3 &lt;span class="nb"&gt;cp&lt;/span&gt; - s3://my-first-bucket/greeting.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Check if it's there:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://my-first-bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;2026-06-16 11:43:58         21 greeting.txt
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1x6lfln0zftl44glvgbb.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%2F1x6lfln0zftl44glvgbb.png" alt=" " width="798" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read it back:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;s3://my-first-bucket/greeting.txt -
&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;hello from my laptop
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ko54638rghvyzu0po2g.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%2F0ko54638rghvyzu0po2g.png" alt=" " width="797" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That felt good, right? You just did exactly what production systems do store and retrieve data from S3. The same commands. The same behavior. When you move to real AWS someday, nothing changes except where the data lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Upload 3 different files to your bucket, then try to delete the bucket without emptying it first. What error do you get? Now figure out how to fix it using only the CLI. This exact scenario comes up in every cloud job.&lt;/p&gt;


&lt;h2&gt;
  
  
  Create a DynamoDB Table and Store Data
&lt;/h2&gt;

&lt;p&gt;DynamoDB confused me for weeks when I started. But once I actually created a table and put data into it, everything clicked. Let's make that happen for you right now.&lt;/p&gt;

&lt;p&gt;Create a table:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws dynamodb create-table &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--table-name&lt;/span&gt; Users &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--attribute-definitions&lt;/span&gt; &lt;span class="nv"&gt;AttributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;,AttributeType&lt;span class="o"&gt;=&lt;/span&gt;S &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key-schema&lt;/span&gt; &lt;span class="nv"&gt;AttributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;,KeyType&lt;span class="o"&gt;=&lt;/span&gt;HASH &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--billing-mode&lt;/span&gt; PAY_PER_REQUEST
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpblozgya4pudkw5lw0tm.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%2Fpblozgya4pudkw5lw0tm.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Put an item in 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="c"&gt;# Linux / Mac&lt;/span&gt;
aws dynamodb put-item &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--table-name&lt;/span&gt; Users &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--item&lt;/span&gt; &lt;span class="s1"&gt;'{"id":{"S":"user-001"},"name":{"S":"Sarvar"},"role":{"S":"Cloud Architect"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows (PowerShell) - use double quotes and escape inner quotes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;put-item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--table-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'{\"id\":{\"S\":\"user-001\"},\"name\":{\"S\":\"Sarvar\"},\"role\":{\"S\":\"Cloud Architect\"}}'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A quick note on the JSON format DynamoDB requires you to specify the data type for each value. &lt;code&gt;"S"&lt;/code&gt; means String, &lt;code&gt;"N"&lt;/code&gt; means Number. It looks verbose at first, but you get used to it quickly.&lt;/p&gt;

&lt;p&gt;Get it back:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws dynamodb get-item &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--table-name&lt;/span&gt; Users &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; &lt;span class="s1"&gt;'{"id":{"S":"user-001"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Item"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-001"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sarvar"&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;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cloud Architect"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&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%2Fumb8ezub2ioye58p88se.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%2Fumb8ezub2ioye58p88se.png" alt=" " width="799" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You just stored and retrieved structured data from a NoSQL database. That's real cloud development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Put another item with the same &lt;code&gt;id&lt;/code&gt; but a different name. Does it overwrite? Does it error? Now put 5 different users and try &lt;code&gt;aws dynamodb scan --table-name Users&lt;/code&gt; to get all of them. This is how you learn database behavior by seeing it happen, not reading about it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Send and Receive Messages with SQS
&lt;/h2&gt;

&lt;p&gt;Almost every production system uses message queues. Order processing, notifications, async workflows queues are everywhere.&lt;/p&gt;

&lt;p&gt;Create a queue:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sqs create-queue &lt;span class="nt"&gt;--queue-name&lt;/span&gt; orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Send a message:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sqs send-message &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--queue-url&lt;/span&gt; http://localhost:4566/000000000000/orders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--message-body&lt;/span&gt; &lt;span class="s1"&gt;'{"event":"order.placed","item":"cloud-book"}'&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzudp3o07r4cn6arphw5h.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%2Fzudp3o07r4cn6arphw5h.png" alt=" " width="799" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(That &lt;code&gt;000000000000&lt;/code&gt; is the default AWS account ID that Floci uses. In real AWS, it would be your actual 12-digit account number.)&lt;/p&gt;

&lt;p&gt;Receive it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sqs receive-message &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--queue-url&lt;/span&gt; http://localhost:4566/000000000000/orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"MessageId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ba4ea32c-cfdf-4c28-b705-8bcbb4d8a0d0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;event&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;order.placed&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;item&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;cloud-book&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Your message comes back exactly as you sent it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Receive the same message again. What happens? It disappears for about 30 seconds (this is called "visibility timeout" the time SQS hides a message after someone reads it, giving them time to process it). Wait 30 seconds and try again. It comes back. Now try deleting it after receiving. This is exactly how real applications process queues.&lt;/p&gt;


&lt;h2&gt;
  
  
  Create a Secret in Secrets Manager
&lt;/h2&gt;

&lt;p&gt;Every application has passwords, API keys, and database credentials. Secrets Manager is where you store them securely.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager create-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; my-app/db-password &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-string&lt;/span&gt; &lt;span class="s2"&gt;"super-secret-password-123"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Retrieve it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager get-secret-value &lt;span class="nt"&gt;--secret-id&lt;/span&gt; my-app/db-password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-app/db-password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SecretString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"super-secret-password-123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;p&gt;That's how real applications fetch database credentials at runtime instead of hardcoding them in code. Simple, but incredibly important in production.&lt;/p&gt;


&lt;h2&gt;
  
  
  Store Configuration in SSM Parameter Store
&lt;/h2&gt;

&lt;p&gt;Parameter Store is where you keep application configuration feature flags, environment URLs, settings that change between dev and production.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ssm put-parameter &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"/myapp/environment"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--value&lt;/span&gt; &lt;span class="s2"&gt;"development"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; String

aws ssm put-parameter &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"/myapp/max-retries"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--value&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; String
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fduwar9qfq3l500m83s1f.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%2Fduwar9qfq3l500m83s1f.png" alt=" " width="799" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get them back:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ssm get-parameter &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"/myapp/environment"&lt;/span&gt;
aws ssm get-parameters &lt;span class="nt"&gt;--names&lt;/span&gt; &lt;span class="s2"&gt;"/myapp/environment"&lt;/span&gt; &lt;span class="s2"&gt;"/myapp/max-retries"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqdclmpj9xhlxmgdvgkzb.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%2Fqdclmpj9xhlxmgdvgkzb.png" alt=" " width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how every well-architected application manages configuration. No more hardcoding values in your code.&lt;/p&gt;


&lt;h2&gt;
  
  
  Use It With Python
&lt;/h2&gt;

&lt;p&gt;If you want to go beyond CLI, here's how your application code talks to Floci. Make sure Python 3 is installed, then:&lt;br&gt;
&lt;/p&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;boto3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="n"&gt;s3&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;s3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;endpoint_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:4566&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;aws_access_key_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;test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_secret_access_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;test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-python-bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-python-bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;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;hello.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;it works!&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;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-python-bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;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;hello.txt&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;Body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Output: &lt;code&gt;b'it works!'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The only difference between this and production code is one line: &lt;code&gt;endpoint_url&lt;/code&gt;. When you deploy to real AWS, remove that line. Everything else stays the same. You're writing production-ready code from day one.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Way I Wish I Had Learned
&lt;/h2&gt;

&lt;p&gt;Looking back at my journey, here's what I would do differently starting today:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 1-2: S3 and IAM.&lt;/strong&gt; Create buckets, upload files, set permissions, try to access things you shouldn't. Break the permissions. Fix them. Understand what "Access Denied" actually means.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 3-4: DynamoDB.&lt;/strong&gt; Create tables with different key structures. Put data. Query it. Understand the difference between &lt;code&gt;get-item&lt;/code&gt; and &lt;code&gt;query&lt;/code&gt; and &lt;code&gt;scan&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 5-6: SQS, SNS, and Secrets Manager.&lt;/strong&gt; Build a simple message flow. Store secrets. Retrieve configuration. These are the building blocks of every real application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 7-8: Lambda.&lt;/strong&gt; Write a simple function. Trigger it. See the logs. Floci runs real Lambda containers not mocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Throughout: Break everything.&lt;/strong&gt; Delete tables while data is in them. Send malformed messages. Call APIs with wrong parameters. Read the error messages carefully. This is the real education.&lt;/p&gt;


&lt;h2&gt;
  
  
  Tips From Years of Cloud Work
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Don't just create. Troubleshoot.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The skill that got me promoted from L1 Cloud Support to Cloud Architect wasn't creating infrastructure. It was fixing it when it broke. Break things deliberately on Floci and fix them without starting over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Learn the CLI before the console.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In real jobs, you'll use CLI and infrastructure-as-code. Floci forces this habit because there's no console to click around in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Read error messages. Actually read them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most beginners see an error and panic. AWS error messages are surprisingly helpful if you actually read them. Practice generating errors on Floci so you learn to read them calmly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Document what you learn.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I filled four notebooks with cloud concepts. Later, I converted those notes into articles. That habit changed my career. Start writing about what you break and fix even if nobody reads it at first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. One concept per day is enough.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You don't need to learn all 200 AWS services. Focus on the core: storage, databases, messaging. One solid hour of hands-on practice beats five hours of video watching.&lt;/p&gt;


&lt;h2&gt;
  
  
  Common Mistakes Beginners Hit
&lt;/h2&gt;

&lt;p&gt;Before you get stuck, here are the issues I see most often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Command not found: aws"&lt;/strong&gt; - You haven't installed AWS CLI yet, or you need to restart your terminal after installation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commands hitting real AWS instead of Floci&lt;/strong&gt; - You forgot to set &lt;code&gt;AWS_ENDPOINT_URL&lt;/code&gt;. Always check with &lt;code&gt;echo $AWS_ENDPOINT_URL&lt;/code&gt; before running commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Cannot connect to the Docker daemon"&lt;/strong&gt; - Docker Desktop isn't running. Open it first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data disappeared after restart&lt;/strong&gt; - By default, Floci stores data in memory. Add &lt;code&gt;FLOCI_STORAGE_MODE=hybrid&lt;/code&gt; to keep data between restarts:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;floci&lt;/span&gt;&lt;span class="pi"&gt;:&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;floci/floci:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4566:4566"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FLOCI_STORAGE_MODE=hybrid&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data:/app/data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When You're Ready for Real AWS
&lt;/h2&gt;

&lt;p&gt;Here's the transition path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Learn and practice on Floci (no cost, no risk)&lt;/li&gt;
&lt;li&gt;When you feel confident, create an AWS Free Tier account&lt;/li&gt;
&lt;li&gt;Deploy the same commands on real AWS they work identically&lt;/li&gt;
&lt;li&gt;For your application code, just remove the &lt;code&gt;endpoint_url&lt;/code&gt; line&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is no rewrite. No migration. No new learning curve. The skills you build on Floci transfer directly to real AWS because it's the same API, the same CLI, the same SDK.&lt;/p&gt;


&lt;h2&gt;
  
  
  Cleaning Up
&lt;/h2&gt;

&lt;p&gt;When you're done for the day:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This stops everything. If you used &lt;code&gt;hybrid&lt;/code&gt; storage mode, your data stays in the &lt;code&gt;./data&lt;/code&gt; folder for next time.&lt;/p&gt;


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

&lt;p&gt;When I started learning cloud, I was scared of bills, confused by services, and paralyzed by the fear of breaking something expensive. I shipped broken code to production because I couldn't test properly locally. I wasted months playing it safe when I should have been experimenting aggressively.&lt;/p&gt;

&lt;p&gt;That was then. Today, you have Floci.&lt;/p&gt;

&lt;p&gt;If you're a fresher starting your cloud journey, a student preparing for certifications, or a working professional switching to cloud start here. Build things. Break things. Fix things. Do it a hundred times until the commands feel natural and the errors feel familiar.&lt;/p&gt;

&lt;p&gt;The cloud rewards people who practice. Not people who watch.&lt;/p&gt;

&lt;p&gt;In my next article, I'll show you how to deploy a complete serverless API Lambda + API Gateway + DynamoDB entirely on Floci, and then move it to real AWS with zero code changes. Follow me so you don't miss it.&lt;/p&gt;



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

&lt;ul&gt;
&lt;li&gt;Floci GitHub: 
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/floci-io" rel="noopener noreferrer"&gt;
        floci-io
      &lt;/a&gt; / &lt;a href="https://github.com/floci-io/floci" rel="noopener noreferrer"&gt;
        floci
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Light, fluffy, and always free - The AWS Local Emulator alternative
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/floci-io/floci/docs/assets/floci-black.svg#gh-light-mode-only"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ffloci-io%2Ffloci%2FHEAD%2Fdocs%2Fassets%2Ffloci-black.svg%23gh-light-mode-only" alt="Floci" width="500"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/floci-io/floci/docs/assets/floci-white.svg#gh-dark-mode-only"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ffloci-io%2Ffloci%2FHEAD%2Fdocs%2Fassets%2Ffloci-white.svg%23gh-dark-mode-only" alt="Floci" width="500"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;strong&gt;Light, fluffy, and always free&lt;/strong&gt;&lt;br&gt;
  No account. No auth token. No feature gates. Just &lt;code&gt;docker compose up&lt;/code&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://github.com/floci-io/floci/releases/latest" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/c892d7fecb7988b7b3deada1dce66fb04a5ce604f44c24d0bfef71588414b734/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f666c6f63692d696f2f666c6f63693f6c6162656c3d6c617465737425323072656c6561736526636f6c6f723d626c7565" alt="Latest Release"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/floci-io/floci/actions/workflows/release.yml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fe80e691bbc394ea1a41482161b9db7e89905b9f1af7d3eadf66e7bdaa756152/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f666c6f63692d696f2f666c6f63692f72656c656173652e796d6c3f6c6162656c3d6275696c64" alt="Build Status"&gt;&lt;/a&gt;
  &lt;a href="https://hub.docker.com/r/floci/floci" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a0baf22d81171625a19cba372e88169c313366f0397beb44693d4f1c3fc333de/68747470733a2f2f696d672e736869656c64732e696f2f646f636b65722f70756c6c732f666c6f63692f666c6f63693f6c6162656c3d646f636b657225323070756c6c73" alt="Docker Pulls"&gt;&lt;/a&gt;
  &lt;a href="https://hub.docker.com/r/floci/floci" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ed3a09325423d2d937e42a307261285f40d1d7d4d446977fb683fb039c157ca1/68747470733a2f2f696d672e736869656c64732e696f2f646f636b65722f696d6167652d73697a652f666c6f63692f666c6f63692f6c61746573743f6c6162656c3d696d61676525323073697a65" alt="Docker Image Size"&gt;&lt;/a&gt;
  &lt;a href="https://opensource.org/licenses/MIT" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e" alt="License: MIT"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/floci-io/floci/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/02cf08877e8fc8049ece2824ec2b956cec1fe0b330cfc3b0b8354f8f1e0c29b4/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f666c6f63692d696f2f666c6f63693f7374796c653d666c6174" alt="GitHub Stars"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://github.com/floci-io/floci#quick-start" rel="noopener noreferrer"&gt;Quick Start&lt;/a&gt; ·
  &lt;a href="https://github.com/floci-io/floci#features" rel="noopener noreferrer"&gt;Features&lt;/a&gt; ·
  &lt;a href="https://github.com/floci-io/floci#supported-services" rel="noopener noreferrer"&gt;Services&lt;/a&gt; ·
  &lt;a href="https://github.com/floci-io/floci#sdk-integration" rel="noopener noreferrer"&gt;SDKs&lt;/a&gt; ·
  &lt;a href="https://github.com/floci-io/floci#testcontainers" rel="noopener noreferrer"&gt;Testcontainers&lt;/a&gt; ·
  &lt;a href="https://github.com/floci-io/floci#migrating-from-localstack" rel="noopener noreferrer"&gt;Migration&lt;/a&gt; ·
  &lt;a href="https://floci.io/floci/" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is Floci?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Floci is a free, open-source local AWS emulator for development, testing, and CI.&lt;/p&gt;

&lt;p&gt;It gives you AWS-shaped services on your machine without requiring a cloud account, an auth token, or paid feature gates. Point your AWS SDK, CLI, Terraform, CDK, OpenTofu, or test suite at &lt;code&gt;http://localhost:4566&lt;/code&gt; and keep your existing workflows.&lt;/p&gt;

&lt;p&gt;Floci is named after &lt;a href="https://en.wikipedia.org/wiki/Cirrocumulus_floccus" rel="nofollow noopener noreferrer"&gt;floccus&lt;/a&gt;, the cloud formation that looks like popcorn.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick Start&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;The fastest way to run Floci is with the official &lt;a href="https://github.com/floci-io/floci-cli" rel="noopener noreferrer"&gt;CLI&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;floci start&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Export the AWS environment variables:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;eval&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;$(&lt;/span&gt;floci env&lt;span class="pl-pds"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Use your existing AWS tools normally:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;aws s3 mb s3://my-bucket
aws dynamodb create-table \
  --table-name demo-table \
  --attribute-definitions AttributeName=pk,AttributeType=S \
  --key-schema AttributeName=pk,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

aws&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/floci-io/floci" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;/li&gt;

&lt;li&gt;Floci Documentation: &lt;a href="https://floci.io/floci/" rel="noopener noreferrer"&gt;floci.io/floci/&lt;/a&gt;
&lt;/li&gt;

&lt;li&gt;Docker Hub: &lt;a href="https://hub.docker.com/r/floci/floci" rel="noopener noreferrer"&gt;hub.docker.com/r/floci/floci&lt;/a&gt;
&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;If this helped you, share it with someone who's starting their cloud journey. Drop a comment if you have questions I'll respond to every one.&lt;/p&gt;

&lt;p&gt;Connect with me on &lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; for more cloud architecture, DevOps, and career guidance.&lt;/p&gt;




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

&lt;p&gt;Thanks for reading! If this was helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value&lt;/li&gt;
&lt;li&gt;💾 Save for later&lt;/li&gt;
&lt;li&gt;🔄 Share with your team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow me for more on:&lt;/strong&gt; AWS architecture, FinOps, DevOps, and AI Infrastructure.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit my website&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;Connect on LinkedIn&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Learning&lt;/strong&gt; 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>beginners</category>
      <category>career</category>
      <category>learning</category>
    </item>
    <item>
      <title>My CDK Deploy Takes 7 Minutes: My Local Runner Takes 25ms</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Mon, 22 Jun 2026 13:45:18 +0000</pubDate>
      <link>https://dev.to/aws-builders/my-cdk-deploy-takes-7-minutes-my-local-runner-takes-25ms-4h9e</link>
      <guid>https://dev.to/aws-builders/my-cdk-deploy-takes-7-minutes-my-local-runner-takes-25ms-4h9e</guid>
      <description>&lt;p&gt;👋 Hey there, Tech Enthusiasts!&lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect who loves turning complex tech problems into simple solutions. I've worked with AWS, Azure, DevOps, Data, Analytics, Generative-AI and Agentic-AI building real systems for real companies. In this article series, I'll share what I've learned in a way that's easy to follow, whether you're experienced or just getting started.&lt;/p&gt;

&lt;p&gt;Let's get into it! 🚀&lt;/p&gt;




&lt;p&gt;I'm building something I've been excited about for months hosting a CrewAI-powered AI agent on AWS using Amazon Bedrock AgentCore, and exposing it through API Gateway + Lambda so external systems can talk to it over HTTP.&lt;/p&gt;

&lt;p&gt;The architecture is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client → API Gateway → Lambda → AgentCore Runtime → CrewAI Agent → Bedrock (Claude)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Lambda function is the bridge. It receives the HTTP request, formats it for my CrewAI agent running on AgentCore, waits for the response, and sends it back. Standard stuff.&lt;/p&gt;

&lt;p&gt;Except the Lambda is where everything is painful.&lt;/p&gt;

&lt;p&gt;I'm tweaking the request payload format for AgentCore. &lt;code&gt;cdk deploy&lt;/code&gt;. Wait 7 minutes. Hit the endpoint. Response shape is wrong AgentCore returns nested JSON, my Lambda isn't unpacking it correctly. Fix one line. &lt;code&gt;cdk deploy&lt;/code&gt;. Another 7 minutes. Realize I need to handle streaming responses differently. Another deploy.&lt;/p&gt;

&lt;p&gt;By lunch I'd deployed 9 times and shipped maybe 40 lines of actual logic.&lt;/p&gt;

&lt;p&gt;The CrewAI agent itself was working fine I'd already tested it locally with &lt;code&gt;crewai run&lt;/code&gt;. The Bedrock model was responding perfectly. But the Lambda layer in between? Every tiny change to the request/response mapping cost me 7 minutes. That's where all my time was going.&lt;/p&gt;

&lt;p&gt;Something had to change.&lt;/p&gt;


&lt;h2&gt;
  
  
  I Tried the Obvious Stuff
&lt;/h2&gt;
&lt;h3&gt;
  
  
  SAM CLI
&lt;/h3&gt;

&lt;p&gt;First thing I reached for. &lt;code&gt;sam local start-api&lt;/code&gt; that's literally what it's for, right?&lt;/p&gt;

&lt;p&gt;Problem is, SAM wants a &lt;code&gt;template.yaml&lt;/code&gt;. My infrastructure is CDK. I'm not maintaining two definitions of the same stack just to test locally. I tried passing &lt;code&gt;cdk.out/MyStack.template.json&lt;/code&gt; directly to SAM and it half-worked some routes loaded, some didn't, the asset references were broken. I spent 45 minutes debugging SAM instead of building my feature.&lt;/p&gt;

&lt;p&gt;Also, every request spins up a Docker container. On my MacBook that's 3-4 seconds of cold start per invocation. When I'm iterating on how my Lambda formats the AgentCore request testing different prompt structures, response parsing, error handling  that completely kills the feedback loop.&lt;/p&gt;

&lt;p&gt;And there's no hot reload. Change a file, stop SAM, run &lt;code&gt;sam build&lt;/code&gt;, start SAM again. For five functions that's a 30-second rebuild cycle. Better than 7 minutes but still way too slow.&lt;/p&gt;
&lt;h3&gt;
  
  
  LocalStack
&lt;/h3&gt;

&lt;p&gt;I've used LocalStack before for integration testing. It's impressive mocks basically all of AWS. But for this use case:&lt;/p&gt;

&lt;p&gt;It took 20 minutes just to get the Lambda hot reload working. You have to deploy to some magic S3 bucket with a specific naming convention. Then it still wasn't picking up my changes consistently.&lt;/p&gt;

&lt;p&gt;The Lambda hot reload features I needed are behind LocalStack's paid tier. I'm not paying monthly to test my API Gateway → Lambda mapping logic.&lt;/p&gt;

&lt;p&gt;For a full event-driven system with SQS and Step Functions, sure, LocalStack makes sense. For "I want to hit my API and see what comes back" it's way overkill.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Realization
&lt;/h2&gt;

&lt;p&gt;I was staring at my &lt;code&gt;cdk.out/&lt;/code&gt; directory one morning I'd just run &lt;code&gt;cdk synth&lt;/code&gt; to verify my stack before yet another deploy and it hit me:&lt;/p&gt;

&lt;p&gt;Everything I need is right here.&lt;/p&gt;

&lt;p&gt;The CloudFormation template has every route POST /agent/invoke, GET /agent/status, GET /agent/history, the whole thing. It has every Lambda function with its handler path and environment variables. It even has the authorizer config.&lt;/p&gt;

&lt;p&gt;When you use CDK's &lt;code&gt;NodejsFunction&lt;/code&gt;, esbuild metadata is written alongside the bundled assets in &lt;code&gt;cdk.out/&lt;/code&gt;. That metadata traces back to the original TypeScript source file. So the synth output gives us the full route map &lt;em&gt;and&lt;/em&gt; the entry points no extra config needed.&lt;/p&gt;

&lt;p&gt;I don't need SAM to interpret this. I don't need LocalStack to mock it. I just need to read the JSON and wire up an Express server.&lt;/p&gt;

&lt;p&gt;So that's what I built.&lt;/p&gt;


&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdk synth → cdk.out/MyStack.template.json
     ↓
  extract (parse the CF template → route manifest)
     ↓
  serve (Express + esbuild bundling + file watcher)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Extract&lt;/strong&gt; reads your CloudFormation template and figures out which API Gateway routes map to which Lambda functions. It resolves the &lt;code&gt;Fn::GetAtt&lt;/code&gt; references, walks the API Gateway resource tree to reconstruct full paths, and traces each handler back to its TypeScript source file.&lt;/p&gt;

&lt;p&gt;Entry points are resolved in priority order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;esbuild metadata&lt;/strong&gt; if your CDK project uses &lt;code&gt;NodejsFunction&lt;/code&gt;, the &lt;code&gt;.esbuild.meta.json&lt;/code&gt; in the asset directory traces back to the original source file. This is the zero-config path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback convention&lt;/strong&gt; if metadata isn't available, it falls back to &lt;code&gt;src/handlers/{logicalId}.ts&lt;/code&gt;. You can override this with a custom CDK aspect that annotates the template.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Serve&lt;/strong&gt; takes that manifest and boots Express. Each route gets registered. When a request comes in, esbuild bundles the handler on-demand (takes about 20ms the first time), constructs a proper &lt;code&gt;APIGatewayProxyEvent&lt;/code&gt; from the Express request, and invokes the handler. Response goes back to the client.&lt;/p&gt;

&lt;p&gt;That's it. No Docker. No template to maintain. No magic.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Hot Reload Part
&lt;/h2&gt;

&lt;p&gt;This is where it actually gets good.&lt;/p&gt;

&lt;p&gt;I've got chokidar watching my source directories. When I save a file, it checks: is this file a handler entry point? If yes, it invalidates just that one handler's cache. If it's a shared utility file, it clears all caches.&lt;/p&gt;

&lt;p&gt;The Express server never restarts. The routes stay registered. The next request to a changed handler re-bundles it with esbuild (about 5ms for a typical handler) and runs the fresh code.&lt;/p&gt;

&lt;p&gt;So my workflow for the AgentCore project became:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tweak how my Lambda formats the CrewAI request payload&lt;/li&gt;
&lt;li&gt;Save&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl -X POST localhost:3001/agent/invoke -d '{"task": "summarize this document"}'&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;See the result in under 50ms&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No deploy. No container spin-up. No rebuild step. Just save and curl.&lt;/p&gt;

&lt;p&gt;I went from "is my Lambda correctly parsing the AgentCore response?" being a 7-minute question to a 5-second question. That changed everything.&lt;/p&gt;

&lt;p&gt;Here's what I measured on my dev machine:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First hit (cold bundle)&lt;/td&gt;
&lt;td&gt;~25ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subsequent hits (cached)&lt;/td&gt;
&lt;td&gt;&amp;lt;2ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After file change (re-bundle)&lt;/td&gt;
&lt;td&gt;~5ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Versus 5-10 minutes per &lt;code&gt;cdk deploy&lt;/code&gt;. I genuinely can't go back.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Failure That Made Me Build This
&lt;/h2&gt;

&lt;p&gt;Let me show you the exact moment that pushed me over the edge.&lt;/p&gt;

&lt;p&gt;I had my Lambda calling AgentCore's invoke endpoint. The response was coming back as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Here's the analysis..."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stopReason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"end_turn"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But my Lambda was returning:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[object Object]"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Classic. I forgot &lt;code&gt;JSON.stringify()&lt;/code&gt; on a nested object. One line fix:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before (broken)&lt;/span&gt;
&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;

&lt;span class="c1"&gt;// After (fixed)  &lt;/span&gt;
&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That one-character-level fix cost me 7 minutes to discover because I had to deploy to see the output. With the local runner, I'd have seen &lt;code&gt;[object Object]&lt;/code&gt; instantly, fixed it, saved, curled again done in 10 seconds.&lt;/p&gt;

&lt;p&gt;After that, I spent a weekend building this tool. Never looked back.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Comparison Nobody Asked For
&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;SAM CLI&lt;/th&gt;
&lt;th&gt;LocalStack&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;This&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Extra config&lt;/td&gt;
&lt;td&gt;template.yaml&lt;/td&gt;
&lt;td&gt;Docker + deploy setup&lt;/td&gt;
&lt;td&gt;None*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker needed&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hot reload&lt;/td&gt;
&lt;td&gt;Nope&lt;/td&gt;
&lt;td&gt;Kinda works&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request latency&lt;/td&gt;
&lt;td&gt;3-5s&lt;/td&gt;
&lt;td&gt;~2s&lt;/td&gt;
&lt;td&gt;&amp;lt;25ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly cost&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Paid for hot reload&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drift risk&lt;/td&gt;
&lt;td&gt;High (second template)&lt;/td&gt;
&lt;td&gt;Medium (mock env)&lt;/td&gt;
&lt;td&gt;None (reads cdk synth)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*Requires &lt;code&gt;NodejsFunction&lt;/code&gt; (CDK's standard Lambda construct for TypeScript/JS) for zero-config entry point resolution.&lt;/p&gt;


&lt;h2&gt;
  
  
  Before/After
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEFORE (my AgentCore project):
  tweak Lambda → cdk deploy (7 min) → curl → wrong response → cdk deploy (7 min) → curl
  1 iteration = 14+ minutes

AFTER:
  tweak Lambda → save → curl (25ms) → wrong response → fix → save → curl (5ms) → done
  1 iteration = 10 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Rough math: 12 fewer deploys × 7 minutes each = &lt;strong&gt;~84 minutes saved per day&lt;/strong&gt;. Over a week that's nearly a full workday I got back.&lt;/p&gt;


&lt;h2&gt;
  
  
  Using It
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; cdk-local-lambda
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In your &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"synth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cdk synth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"local"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx cdk-local dev --cdk-out cdk.out --stack MyStack --port 3001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run synth &amp;amp;&amp;amp; npm run local"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Found 5 route(s)
  POST   /agent/invoke       → src/handlers/invokeAgent.ts
  GET    /agent/status/{id}  → src/handlers/getStatus.ts
  GET    /agent/history      → src/handlers/getHistory.ts
  POST   /agent/feedback     → src/handlers/submitFeedback.ts
  GET    /health             → src/handlers/health.ts

🚀 CDK Local Lambda running on http://localhost:3001
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Save a file. Hit the endpoint. Done.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you cloned the repo to contribute, run &lt;code&gt;npm run build&lt;/code&gt; first. As an installed dependency, the CLI is ready to use.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Real Example: Testing My AgentCore Handler
&lt;/h2&gt;

&lt;p&gt;Here's what my actual dev loop looks like now. My invoke handler takes a task, calls AgentCore, and returns the result:&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;# Test the invoke endpoint&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3001/agent/invoke &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;'{"task": "summarize the Q2 report", "context": "finance"}'&lt;/span&gt; | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requestId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a1b2c3d4-e5f6-7890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Here's the Q2 summary: Revenue increased 12% YoY..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-3.5-sonnet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;847&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;234&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now I change how the response is formatted maybe I want to add execution time:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/handlers/invokeAgent.ts - add timing&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callAgentCore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;executionMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;duration&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;Save. Curl again. Fresh response with &lt;code&gt;executionMs&lt;/code&gt; field. No deploy.&lt;/p&gt;


&lt;h2&gt;
  
  
  What It Doesn't Do
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend this replaces a full deployment pipeline. It doesn't.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Works&lt;/th&gt;
&lt;th&gt;Doesn't Work&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API Gateway → Lambda (REST)&lt;/td&gt;
&lt;td&gt;SQS / SNS / EventBridge triggers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom authorizers (TOKEN type)&lt;/td&gt;
&lt;td&gt;REQUEST authorizers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path params, query strings, headers&lt;/td&gt;
&lt;td&gt;WebSocket APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON request/response bodies&lt;/td&gt;
&lt;td&gt;API Gateway request validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment variables (literals)&lt;/td&gt;
&lt;td&gt;Cross-stack references (Fn::ImportValue)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hot reload&lt;/td&gt;
&lt;td&gt;Step Functions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Your handlers still call real AWS services. If your Lambda talks to AgentCore, it calls real AgentCore. If it writes to DynamoDB, it writes to real DynamoDB. This tool only mocks the API Gateway → Lambda invocation, not the services your code uses.&lt;/p&gt;

&lt;p&gt;For my project, that's actually what I want I need to test with real AgentCore responses to make sure my parsing logic handles the actual response shape. I just don't want to wait 7 minutes to test that parsing.&lt;/p&gt;


&lt;h2&gt;
  
  
  Who Should NOT Use This
&lt;/h2&gt;

&lt;p&gt;Being honest about where this doesn't fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You use Serverless Framework&lt;/strong&gt; → use &lt;code&gt;serverless-offline&lt;/code&gt; instead. It reads your &lt;code&gt;serverless.yml&lt;/code&gt; directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need SQS/EventBridge/Step Functions locally&lt;/strong&gt; → use LocalStack. This only handles API Gateway → Lambda.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You don't use CDK&lt;/strong&gt; → this reads &lt;code&gt;cdk.out/&lt;/code&gt;. No CDK, no use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your Lambdas are Python/Go/Java&lt;/strong&gt; → TypeScript/JavaScript only (esbuild is the bundler).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need to mock downstream AWS services&lt;/strong&gt; → this doesn't mock DynamoDB, S3, Bedrock, etc. Your handlers call real services.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  When You Still Deploy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Before merging a PR (integration test with real services)&lt;/li&gt;
&lt;li&gt;Validating IAM permissions actually work&lt;/li&gt;
&lt;li&gt;Load testing&lt;/li&gt;
&lt;li&gt;Anything async (SQS consumers, EventBridge rules)&lt;/li&gt;
&lt;li&gt;First-time AgentCore endpoint validation (does the IAM role have &lt;code&gt;bedrock-agentcore:InvokeAgent&lt;/code&gt; permission?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I deploy maybe 2-3 times a day now instead of 15+. The local runner handles the "am I returning the right JSON shape" iterations. Deploy handles the "does my IAM policy actually let me call AgentCore" questions.&lt;/p&gt;


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

&lt;p&gt;I'm actively using this daily on the AgentCore project and hitting edges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] HTTP API (v2) support different event format&lt;/li&gt;
&lt;li&gt;[ ] REQUEST authorizer support&lt;/li&gt;
&lt;li&gt;[ ] Lambda layers resolution&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;.env.local&lt;/code&gt; overrides for those &lt;code&gt;[REF:xxx]&lt;/code&gt; placeholders&lt;/li&gt;
&lt;li&gt;[ ] Multi-stack support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those are blocking you, open an issue or better, a PR.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Point
&lt;/h2&gt;

&lt;p&gt;Here's what bugged me about both SAM and LocalStack for this use case: they add things. Another template. Another environment. Another service to configure and keep in sync.&lt;/p&gt;

&lt;p&gt;CDK already knows everything about your API. It's sitting right there in &lt;code&gt;cdk.out/&lt;/code&gt;. Reading it directly means nothing can drift, because there's nothing &lt;em&gt;to&lt;/em&gt; drift.&lt;/p&gt;

&lt;p&gt;Tools like &lt;code&gt;serverless-offline&lt;/code&gt; and Architect's &lt;code&gt;arc sandbox&lt;/code&gt; offer something similar for their own frameworks, but nothing that reads CDK synth output directly. That's the key difference  your CDK code remains the single source of truth, locally and in production.&lt;/p&gt;

&lt;p&gt;I wish I'd built this before starting the AgentCore project. Would've saved me an entire week of deploy-wait-test cycles.&lt;/p&gt;


&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Here's how to try it yourself in under 5 minutes. The complete source code is on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/simplynadaf" rel="noopener noreferrer"&gt;
        simplynadaf
      &lt;/a&gt; / &lt;a href="https://github.com/simplynadaf/cdk-local-lambda" rel="noopener noreferrer"&gt;
        cdk-local-lambda
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      I Built a Local Lambda Runner for CDK - No Docker, No SAM, Sub-Second Reload
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;⚡ cdk-local-lambda&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Run your CDK Lambda functions locally. No Docker. No SAM. Sub-second hot reload.&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/cdk-local-lambda" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/34f60a83e3954b07ece346bc23553f05955c2b4da02d804685d076d01fac7eb2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6e706d2d76302e312e302d626c75652e737667" alt="npm version"&gt;&lt;/a&gt;
&lt;a href="https://nodejs.org" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/092e0007be85a7ff2897fc519179f0288db62202dfda73be27b46cb89cdaa3d6/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6e6f64652d25334525334431382e302e302d627269676874677265656e2e737667" alt="Node.js"&gt;&lt;/a&gt;
&lt;a href="https://www.typescriptlang.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/dc63baa72c8d42e246e791f4e625fa55d7eec24c1332fa5ce0e0d64b459f96c3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f547970655363726970742d352e782d626c75652e737667" alt="TypeScript"&gt;&lt;/a&gt;
&lt;a href="https://github.com/simplynadaf/cdk-local-lambda/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667" alt="License: MIT"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────┐
│                                                     │
│   cdk synth → extract → serve                       │
│                                                     │
│   Change code. Save. Hit endpoint. Done.            │
│   No deploy. No Docker. No waiting.                 │
│                                                     │
└─────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://github.com/simplynadaf/cdk-local-lambda#-quick-start" rel="noopener noreferrer"&gt;Quick Start&lt;/a&gt; •
&lt;a href="https://github.com/simplynadaf/cdk-local-lambda#-how-it-works" rel="noopener noreferrer"&gt;How It Works&lt;/a&gt; •
&lt;a href="https://github.com/simplynadaf/cdk-local-lambda#-cli-reference" rel="noopener noreferrer"&gt;CLI Reference&lt;/a&gt; •
&lt;a href="https://github.com/simplynadaf/cdk-local-lambda#-hot-reload" rel="noopener noreferrer"&gt;Hot Reload&lt;/a&gt; •
&lt;a href="https://github.com/simplynadaf/cdk-local-lambda#-examples" rel="noopener noreferrer"&gt;Examples&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🤔 The Problem&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Every CDK developer knows this loop:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;Change 1 line → cdk deploy → wait 5-10 min → test → realize it's wrong → repeat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Existing solutions aren't great for CDK:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAM CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires separate &lt;code&gt;template.yaml&lt;/code&gt;, Docker cold starts, no hot reload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LocalStack&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Heavy, Lambda hot reload behind paid tier, awkward setup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;💡 The Insight&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;After &lt;code&gt;cdk synth&lt;/code&gt;, everything you need is already in &lt;code&gt;cdk.out/&lt;/code&gt; — routes, handlers, env vars, authorizers. &lt;strong&gt;Why maintain a second config when the source of truth already exists?&lt;/strong&gt;&lt;/p&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/simplynadaf/cdk-local-lambda" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Step 1: Clone the Repository
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/simplynadaf/cdk-local-lambda.git
&lt;span class="nb"&gt;cd &lt;/span&gt;cdk-local-lambda
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the full project source code, example handlers, and a mock &lt;code&gt;cdk.out/&lt;/code&gt; template that simulates real CDK synth output.&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%2F4ack13323zzr41qfo62i.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%2F4ack13323zzr41qfo62i.png" alt=" " width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Install Dependencies
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;This installs Express, esbuild, chokidar, and commander everything the local runner needs. No global installs, no Docker, no AWS CLI required.&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%2Fw28cp3b314fmtjn4xml2.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%2Fw28cp3b314fmtjn4xml2.png" alt=" " width="790" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Start the Local Server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsx src/cli.ts dev &lt;span class="nt"&gt;--cdk-out&lt;/span&gt; examples/cdk.out &lt;span class="nt"&gt;--stack&lt;/span&gt; MyStack &lt;span class="nt"&gt;--port&lt;/span&gt; 3001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This uses &lt;code&gt;tsx&lt;/code&gt; for development. In a published package you'd use &lt;code&gt;npx cdk-local dev ...&lt;/code&gt; after building.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You should see the server boot with all routes listed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Found 3 route(s)
  GET  /users      → examples/src/handlers/listUsers.ts
  GET  /users/{id} → examples/src/handlers/getUser.ts
  POST /users      → examples/src/handlers/createUser.ts

🚀 CDK Local Lambda running on http://localhost:3001
&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%2F3mggdql90v7ygvblgaln.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%2F3mggdql90v7ygvblgaln.png" alt=" " width="796" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Hit the Endpoints
&lt;/h3&gt;

&lt;p&gt;Open a new terminal and test:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List all users:&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;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:3001/users | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bob@example.com"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Charlie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"charlie@example.com"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Get a specific user:&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;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:3001/users/1 | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Create a new user:&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;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3001/users &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;'{"name":"Alice","email":"alice@example.com"}'&lt;/span&gt; | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1749637842910"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-11T09:30:42.910Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Step 5: Test Hot Reload
&lt;/h3&gt;

&lt;p&gt;This is the magic part. With the server still running:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;examples/src/handlers/getUser.ts&lt;/code&gt; in any editor&lt;/li&gt;
&lt;li&gt;Add a new field to the response for example, add &lt;code&gt;"source": "hot-reloaded!"&lt;/code&gt; to the return object&lt;/li&gt;
&lt;li&gt;Save the file&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Watch the server terminal you'll see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;  ♻ Changed: examples/src/handlers/getUser.ts - invalidated 1 handler
&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%2Fquwbm6upi9m3ghjqrh89.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%2Fquwbm6upi9m3ghjqrh89.png" alt=" " width="800" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now curl the same endpoint again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:3001/users/1 | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response now includes your new field. No restart. No rebuild. Sub-second.&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%2F6sjxm47l790ds6jfz9ir.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%2F6sjxm47l790ds6jfz9ir.png" alt=" " width="797" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Try Breaking Things
&lt;/h3&gt;

&lt;p&gt;Test error handling:&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;# User that doesn't exist - should return 404&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:3001/users/999 | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User not found"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14eee8twfjecuman5upu.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%2F14eee8twfjecuman5upu.png" alt=" " width="800" height="83"&gt;&lt;/a&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;# Missing required field - should return 400&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3001/users &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;'{}'&lt;/span&gt; | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name is required"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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




&lt;p&gt;That's it. Six steps, no AWS account needed, no Docker, no deploy. The example uses mock handlers, but in a real project you'd point &lt;code&gt;--cdk-out&lt;/code&gt; at your actual &lt;code&gt;cdk synth&lt;/code&gt; output and it works the same way like I'm doing with my AgentCore project right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'm Building Next
&lt;/h2&gt;

&lt;p&gt;The AgentCore integration is still in progress. Once it's stable, I'm planning a follow-up article showing the full architecture how I'm hosting a CrewAI multi-agent system on AgentCore and exposing it through the API Gateway + Lambda layer that this tool helped me iterate on 100x faster.&lt;/p&gt;

&lt;p&gt;If you're working with AgentCore or any AI agent framework that needs an HTTP API layer, this same pattern applies. The Lambda is just a bridge and bridges need fast iteration.&lt;/p&gt;




&lt;p&gt;If this is useful, star the repo. If something's broken, open an issue. If you want a feature, tell me in the comments &lt;br&gt;
I'm building this for my own workflow anyway, might as well make it work for yours too.&lt;/p&gt;




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

&lt;p&gt;Thanks for reading! If this was helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value&lt;/li&gt;
&lt;li&gt;💾 Save for later&lt;/li&gt;
&lt;li&gt;🔄 Share with your team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow me for more on:&lt;/strong&gt; AWS architecture, FinOps, DevOps, and AI Infrastructure.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit my website&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;Connect on LinkedIn&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Learning&lt;/strong&gt; 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>programming</category>
      <category>productivity</category>
      <category>automation</category>
    </item>
    <item>
      <title>Going Serverless with Terraform - Deploying AWS Lambda Functions</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Fri, 19 Jun 2026 13:33:44 +0000</pubDate>
      <link>https://dev.to/aws-builders/going-serverless-with-terraform-deploying-aws-lambda-functions-3klp</link>
      <guid>https://dev.to/aws-builders/going-serverless-with-terraform-deploying-aws-lambda-functions-3klp</guid>
      <description>&lt;p&gt;👋 Hey there, tech enthusiasts! &lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect with a passion for transforming complex technological challenges into elegant solutions. With extensive experience spanning Cloud Operations (AWS &amp;amp; Azure), Data Operations, Analytics, DevOps, and Generative AI, I've had the privilege of architecting solutions for global enterprises that drive real business impact. Through this article series, I'm excited to share practical insights, best practices, and hands-on experiences from my journey in the tech world. Whether you're a seasoned professional or just starting out, I aim to break down complex concepts into digestible pieces that you can apply in your projects.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Servers are like pets you feed them, nurse them, and cry when they die. Lambda functions are like cattle spin them up, use them, forget them."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🎯 Welcome Back!
&lt;/h2&gt;

&lt;p&gt;Remember in &lt;a href="https://dev.to/aws-builders/deploy-web-servers-with-terraform-ec2-load-balancer-tutorial-304k"&gt;Article 7&lt;/a&gt; when you deployed web servers with a load balancer? You built EC2 instances running 24/7 behind an ALB. That's great for high-traffic web apps. But what about workloads that only run occasionally?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the reality:&lt;/strong&gt; You've been paying for EC2 instances 24/7 even when nobody's using them. That internal tool that processes files once a day? Running on a t3.medium at $30/month for 5 minutes of actual work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda changes the equation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pay only when code runs&lt;/li&gt;
&lt;li&gt;No servers to patch or maintain&lt;/li&gt;
&lt;li&gt;Scales from zero to thousands automatically&lt;/li&gt;
&lt;li&gt;IAM roles handle permissions (you know this now!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;By the end of this article, you'll:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Deploy Lambda functions with Terraform&lt;/li&gt;
&lt;li&gt;✅ Create API Gateway endpoints for HTTP access&lt;/li&gt;
&lt;li&gt;✅ Trigger Lambda from S3 file uploads&lt;/li&gt;
&lt;li&gt;✅ Configure IAM roles with least privilege&lt;/li&gt;
&lt;li&gt;✅ Set up CloudWatch logging automatically&lt;/li&gt;
&lt;li&gt;✅ Test everything with real invocations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Time Required:&lt;/strong&gt; 45 minutes (20 min read + 25 min practice)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; $0 (Lambda free tier)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Difficulty:&lt;/strong&gt; Intermediate&lt;/p&gt;

&lt;p&gt;Let's go serverless! 🚀&lt;/p&gt;


&lt;h2&gt;
  
  
  💔 The Problem: Paying for Idle Servers
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Wake-Up Call
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Monthly AWS bill review. Something doesn't add up:&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;EC2 Instances:
- file-processor (t3.medium)  → $30.37/month
  Actual compute time: 12 minutes/day
  Utilization: 0.8%

- api-backend (t3.small)      → $15.18/month
  Actual requests: 200/day
  Utilization: 2.1%

- cron-worker (t2.micro)      → $8.47/month
  Runs one script at midnight
  Utilization: 0.03%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Total: $54/month for services used less than 1% of the time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With Lambda, that same workload costs under $1/month. Not a typo. Under one dollar.&lt;/p&gt;
&lt;h3&gt;
  
  
  When EC2 Doesn't Make Sense:
&lt;/h3&gt;

&lt;p&gt;❌ &lt;strong&gt;Event-driven processing:&lt;/strong&gt; File uploaded → process it → done&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Low-traffic APIs:&lt;/strong&gt; Dozen requests per hour&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Scheduled tasks:&lt;/strong&gt; Run once a day or hour&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Webhook handlers:&lt;/strong&gt; Wait for external events&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Data transformations:&lt;/strong&gt; Input → transform → output  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sound familiar?&lt;/strong&gt; Let's move these to Lambda.&lt;/p&gt;


&lt;h2&gt;
  
  
  🌟 What is AWS Lambda?
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Simple Definition
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lambda&lt;/strong&gt; = Your code runs only when triggered. No servers. No patching. No scaling configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think of it like this:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EC2&lt;/strong&gt; is renting an apartment you pay monthly whether you're home or not&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt; is a hotel room you pay only for the nights you stay&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  How Lambda Works:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trigger (API call, S3 upload, schedule)
    ↓
AWS spins up your function (milliseconds)
    ↓
Your code runs
    ↓
Result returned
    ↓
Function shuts down (you stop paying)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Lambda + Terraform = Perfect Match
&lt;/h3&gt;

&lt;p&gt;Why Terraform for Lambda?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Version control&lt;/strong&gt; your function configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent deployments&lt;/strong&gt; across environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM roles&lt;/strong&gt; defined alongside functions (Article 9 skills!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connected resources&lt;/strong&gt; (API Gateway, S3, CloudWatch) in one config&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  📋 Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before starting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Terraform installed (&lt;a href="https://dev.tolink"&gt;Article 2&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;✅ AWS credentials configured&lt;/li&gt;
&lt;li&gt;✅ Understand VPCs and Security Groups (&lt;a href="https://dev.tolink"&gt;Article 6&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;✅ Basic Python knowledge (for Lambda code)&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🏗️ What We're Building
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────┐
│                    Our Architecture                 │
│                                                     │
│  ┌──────────┐       ┌──────────────┐                │
│  │   User   │──────▶│ API Gateway  │                │
│  └──────────┘       └──────┬───────┘                │
│                            │                        │
│                            ▼                        │
│                     ┌──────────────┐                │
│                     │ Hello Lambda │──▶ CloudWatch │
│                     └──────────────┘                │
│                                                     │
│  ┌──────────┐       ┌──────────────┐                │
│  │ S3 Upload│──────▶│S3 Processor  │──▶ CloudWatch │
│  │  (.txt)  │       │   Lambda     │                │
│  └──────────┘       └──────────────┘                │
└─────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Two Lambda functions:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hello API&lt;/strong&gt; - HTTP endpoint via API Gateway, returns JSON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Processor&lt;/strong&gt; - Triggered when &lt;code&gt;.txt&lt;/code&gt; files are uploaded to S3&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;The complete source code and Terraform configuration used in this article can be found on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/simplynadaf" rel="noopener noreferrer"&gt;
        simplynadaf
      &lt;/a&gt; / &lt;a href="https://github.com/simplynadaf/terraform-by-sarvar" rel="noopener noreferrer"&gt;
        terraform-by-sarvar
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Terraform Series
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🚀 Terraform By Sarvar&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Complete Terraform tutorial series from basics to advanced concepts&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://www.terraform.io/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/446abdaa5e617237549619105d10cbd99f693a057dc7b366edd5256283015a75/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5465727261666f726d2d76312e31342b2d3632334345343f7374796c653d666f722d7468652d6261646765266c6f676f3d7465727261666f726d266c6f676f436f6c6f723d7768697465" alt="Terraform"&gt;&lt;/a&gt;
&lt;a href="https://aws.amazon.com/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0b14c31424193ad1651b7422c0c3fd0f5339f3f2632f5ecc3d5313c83e8aeba0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4157532d436c6f75642d4646393930303f7374796c653d666f722d7468652d6261646765266c6f676f3d616d617a6f6e2d617773266c6f676f436f6c6f723d7768697465" alt="AWS"&gt;&lt;/a&gt;
&lt;a href="https://github.com/simplynadaf/terraform-by-sarvar/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9218332452902d9e542a100d0af126fd3174a116456614d2cf093546a13783db/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e7376673f7374796c653d666f722d7468652d6261646765" alt="License"&gt;&lt;/a&gt;
&lt;a href="https://dev.to/sarvar_04/series/36958" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/ad1f81014cac91cbb305e20c6fb83301ac4992821ede633a6a83bda4379ed118/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6465762e746f2d5365726965732d3041304130413f7374796c653d666f722d7468652d6261646765266c6f676f3d6465762e746f266c6f676f436f6c6f723d7768697465" alt="dev.to"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dev.to/sarvar_04/series/36963" rel="nofollow"&gt;📖 Read the Series&lt;/a&gt; • &lt;a href="https://sarvarnadaf.com" rel="nofollow noopener noreferrer"&gt;🌐 Portfolio&lt;/a&gt; • &lt;a href="https://www.linkedin.com/in/sarvar04/" rel="nofollow noopener noreferrer"&gt;💼 LinkedIn&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📚 Series Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This repository contains &lt;strong&gt;ONLY Terraform code examples&lt;/strong&gt; for the &lt;strong&gt;Terraform By Sarvar&lt;/strong&gt; tutorial series.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ IMPORTANT:&lt;/strong&gt; This repo contains only &lt;code&gt;.tf&lt;/code&gt; files and infrastructure code. Articles are published on dev.to, not stored here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;📖 &lt;strong&gt;Read the series on dev.to:&lt;/strong&gt; &lt;a href="https://dev.to/sarvar_04/series/36963" rel="nofollow"&gt;https://dev.to/sarvar_04/series/36963&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;🎯 What You'll Learn&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;✅ Infrastructure as Code (IaC) fundamentals&lt;/li&gt;
&lt;li&gt;✅ Terraform basics to advanced concepts&lt;/li&gt;
&lt;li&gt;✅ AWS resource provisioning&lt;/li&gt;
&lt;li&gt;✅ Best practices and real-world patterns&lt;/li&gt;
&lt;li&gt;✅ Production-ready configurations&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;📊 Series Progress&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/e7a8f995fcf62075df5b38ff48a03f1ee698e1cacdeede4e004b6b2434c8e77e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41727469636c65732d3825324631302d626c75653f7374796c653d666c61742d737175617265"&gt;&lt;img src="https://camo.githubusercontent.com/e7a8f995fcf62075df5b38ff48a03f1ee698e1cacdeede4e004b6b2434c8e77e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41727469636c65732d3825324631302d626c75653f7374796c653d666c61742d737175617265" alt="Progress"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/ab5bccc65fd0dfc759cb04ffe3446775f8674a09e936f473d7a51abcd3e02713/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5374617475732d4163746976652d737563636573733f7374796c653d666c61742d737175617265"&gt;&lt;img src="https://camo.githubusercontent.com/ab5bccc65fd0dfc759cb04ffe3446775f8674a09e936f473d7a51abcd3e02713/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5374617475732d4163746976652d737563636573733f7374796c653d666c61742d737175617265" alt="Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📖 Article Series&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;📘 Foundation (Articles 1-5) ✅ Complete&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/why-every-developer-should-learn-terraform-in-2026-and-how-to-start--4fk0" rel="nofollow"&gt;Introduction to Terraform &amp;amp; IaC&lt;/a&gt; - ✅ Published&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/installing-terraform-and-setting-up-your-environment-1j9b" rel="nofollow"&gt;Installation &amp;amp; Setup&lt;/a&gt; - ✅ Published&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/your-first-infrastructure-as-code-in-four-commands-46ep" rel="nofollow"&gt;Your First AWS Resource&lt;/a&gt; - ✅ Published&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/terraform-state-the-one-file-you-cant-afford-to-lose-33l4" rel="nofollow"&gt;Understanding Terraform State&lt;/a&gt; - ✅ Published&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/terraform-variables-and-outputs-making-your-infrastructure-flexible-3ebc" rel="nofollow"&gt;Variables and Outputs&lt;/a&gt; - ✅ Published&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;📗 Real Infrastructure (Articles 6-10)&lt;/h3&gt;

&lt;/div&gt;
&lt;ol start="6"&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/building-your-first-vpc-aws-networking-with-terraform-3h34" rel="nofollow"&gt;Building a VPC from Scratch&lt;/a&gt;…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/simplynadaf/terraform-by-sarvar" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;Follow these steps to run the project on your local machine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/simplynadaf/terraform-by-sarvar.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Navigate to the project directory:
&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;cd &lt;/span&gt;terraform-by-sarvar/articles/08-lambda-serverless
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔧 Step 1: Write the Lambda Functions
&lt;/h2&gt;

&lt;p&gt;Before Terraform, we need the actual code. Create a &lt;code&gt;lambda/&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; lambda
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;lambda/hello.py&lt;/code&gt;&lt;/strong&gt; - Simple API handler:&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;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Simple Hello World Lambda function
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="nf"&gt;print&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;Event received: &lt;/span&gt;&lt;span class="si"&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;event&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;span class="c1"&gt;# Get name from query parameters or use default
&lt;/span&gt;    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;World&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;event&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;queryStringParameters&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;queryStringParameters&lt;/span&gt;&lt;span class="sh"&gt;'&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;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;World&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_body&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&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;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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aws_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;function_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;memory_limit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_limit_in_mb&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;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&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;Content-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;application/json&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;Access-Control-Allow-Origin&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;*&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;body&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;response_body&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;&lt;code&gt;lambda/s3_processor.py&lt;/code&gt;&lt;/strong&gt; - Processes uploaded files:&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;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Process files uploaded to S3
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="nf"&gt;print&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;Event received: &lt;/span&gt;&lt;span class="si"&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;event&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;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Records&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&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;bucket&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="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unquote_plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&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;object&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;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&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;object&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;size&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;event_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;eventName&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Processing file: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&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="nf"&gt;print&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;Bucket: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bucket&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="nf"&gt;print&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;Size: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Event: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;event_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="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;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;processed&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;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;size&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Result: &lt;/span&gt;&lt;span class="si"&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;result&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;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;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;File processed successfully&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;Key points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;lambda_handler&lt;/code&gt; is the entry point AWS calls&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;event&lt;/code&gt; contains trigger data (HTTP request, S3 event, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;context&lt;/code&gt; has runtime info (request ID, function name, memory)&lt;/li&gt;
&lt;li&gt;Always return a proper response with statusCode&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔧 Step 2: Terraform Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;main.tf&lt;/code&gt;&lt;/strong&gt; - Here's the complete infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0"&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;archive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/archive"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.0"&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;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;archive&lt;/code&gt; provider Terraform uses this to zip your Python files into deployment packages. No manual zipping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Package the Lambda code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"archive_file"&lt;/span&gt; &lt;span class="s2"&gt;"hello_lambda"&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;"zip"&lt;/span&gt;
  &lt;span class="nx"&gt;source_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/lambda/hello.py"&lt;/span&gt;
  &lt;span class="nx"&gt;output_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/lambda/hello.zip"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"archive_file"&lt;/span&gt; &lt;span class="s2"&gt;"s3_processor_lambda"&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;"zip"&lt;/span&gt;
  &lt;span class="nx"&gt;source_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/lambda/s3_processor.py"&lt;/span&gt;
  &lt;span class="nx"&gt;output_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/lambda/s3_processor.zip"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;archive_file&lt;/code&gt; data source creates zip files at plan time. When you change the Python code, Terraform detects the hash change and redeploys automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 Step 3: IAM Role (Applying Article 9 Knowledge)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_role"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-lambda-role"&lt;/span&gt;

  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda.amazonaws.com"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_basic"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;policy_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-lambda-s3-policy"&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${aws_s3_bucket.lambda_trigger.arn}/*"&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;policy_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AWSLambdaBasicExecutionRole&lt;/code&gt; AWS managed policy for CloudWatch Logs access. Every Lambda needs this.&lt;/li&gt;
&lt;li&gt;Custom S3 policy Only &lt;code&gt;GetObject&lt;/code&gt; and &lt;code&gt;PutObject&lt;/code&gt; on our specific bucket. Least privilege from Article 9.&lt;/li&gt;
&lt;li&gt;The assume role policy says "only Lambda service can use this role." No human, no other service.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔧 Step 4: Lambda Functions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_function"&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filename&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archive_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_path&lt;/span&gt;
  &lt;span class="nx"&gt;function_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-hello"&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hello.lambda_handler"&lt;/span&gt;
  &lt;span class="nx"&gt;source_code_hash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archive_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_base64sha256&lt;/span&gt;
  &lt;span class="nx"&gt;runtime&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"python3.11"&lt;/span&gt;
  &lt;span class="nx"&gt;timeout&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="nx"&gt;memory_size&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;

  &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
      &lt;span class="nx"&gt;PROJECT&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;project_name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-hello"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_function"&lt;/span&gt; &lt;span class="s2"&gt;"s3_processor"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filename&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archive_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_processor_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_path&lt;/span&gt;
  &lt;span class="nx"&gt;function_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-s3-processor"&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3_processor.lambda_handler"&lt;/span&gt;
  &lt;span class="nx"&gt;source_code_hash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archive_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_processor_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_base64sha256&lt;/span&gt;
  &lt;span class="nx"&gt;runtime&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"python3.11"&lt;/span&gt;
  &lt;span class="nx"&gt;timeout&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="nx"&gt;memory_size&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;

  &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
      &lt;span class="nx"&gt;PROJECT&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;project_name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-s3-processor"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why these settings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;source_code_hash&lt;/code&gt; Terraform redeploys when code changes. Without this, it won't detect updates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;timeout = 10&lt;/code&gt; for Hello (API calls should be fast), &lt;code&gt;timeout = 30&lt;/code&gt; for S3 processor (file processing needs more time)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;memory_size = 128&lt;/code&gt; for Hello (minimal), &lt;code&gt;256&lt;/code&gt; for S3 processor (more processing power)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;handler = "hello.lambda_handler"&lt;/code&gt;  format is &lt;code&gt;filename.function_name&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔧 Step 5: CloudWatch Log Groups
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudwatch_log_group"&lt;/span&gt; &lt;span class="s2"&gt;"hello_lambda"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/aws/lambda/${aws_lambda_function.hello.function_name}"&lt;/span&gt;
  &lt;span class="nx"&gt;retention_in_days&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudwatch_log_group"&lt;/span&gt; &lt;span class="s2"&gt;"s3_processor_lambda"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/aws/lambda/${aws_lambda_function.s3_processor.function_name}"&lt;/span&gt;
  &lt;span class="nx"&gt;retention_in_days&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lambda creates log groups automatically, but Terraform-managed groups give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controlled retention (7 days, not infinite)&lt;/li&gt;
&lt;li&gt;Proper cleanup on &lt;code&gt;terraform destroy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Cost control (old logs don't pile up)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔧 Step 6: S3 Bucket with Lambda Trigger
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_trigger"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_bucket_name&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_bucket_name&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_permission"&lt;/span&gt; &lt;span class="s2"&gt;"s3_invoke"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;statement_id&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AllowS3Invoke"&lt;/span&gt;
  &lt;span class="nx"&gt;action&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda:InvokeFunction"&lt;/span&gt;
  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_name&lt;/span&gt;
  &lt;span class="nx"&gt;principal&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3.amazonaws.com"&lt;/span&gt;
  &lt;span class="nx"&gt;source_arn&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_notification"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_trigger"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;lambda_function&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;lambda_function_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
    &lt;span class="nx"&gt;events&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:ObjectCreated:*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;filter_suffix&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;".txt"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_lambda_permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_invoke&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;Critical detail:&lt;/strong&gt; The &lt;code&gt;aws_lambda_permission&lt;/code&gt; must come before the bucket notification. The &lt;code&gt;depends_on&lt;/code&gt; ensures this. Without it, S3 can't invoke your Lambda because the permission doesn't exist yet.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;filter_suffix = ".txt"&lt;/code&gt; means only &lt;code&gt;.txt&lt;/code&gt; file uploads trigger the function. Upload a &lt;code&gt;.jpg&lt;/code&gt;? Nothing happens. This prevents accidental infinite loops if your Lambda writes back to the same bucket.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 Step 7: API Gateway
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_rest_api"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-api"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"API Gateway for Lambda functions"&lt;/span&gt;

  &lt;span class="nx"&gt;endpoint_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;types&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"REGIONAL"&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_resource"&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rest_api_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_rest_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;parent_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_rest_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root_resource_id&lt;/span&gt;
  &lt;span class="nx"&gt;path_part&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_method"&lt;/span&gt; &lt;span class="s2"&gt;"hello_get"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rest_api_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_rest_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;resource_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;http_method&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GET"&lt;/span&gt;
  &lt;span class="nx"&gt;authorization&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"NONE"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_integration"&lt;/span&gt; &lt;span class="s2"&gt;"hello_lambda"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rest_api_id&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_rest_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;resource_id&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;http_method&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello_get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http_method&lt;/span&gt;
  &lt;span class="nx"&gt;integration_http_method&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"POST"&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;"AWS_PROXY"&lt;/span&gt;
  &lt;span class="nx"&gt;uri&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoke_arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_deployment"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rest_api_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_rest_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;triggers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;redeployment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sha1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;aws_api_gateway_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;aws_api_gateway_method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello_get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;aws_api_gateway_integration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;create_before_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_api_gateway_integration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello_lambda&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_stage"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;deployment_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_deployment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;rest_api_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_rest_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;stage_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_permission"&lt;/span&gt; &lt;span class="s2"&gt;"api_gateway"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;statement_id&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AllowAPIGatewayInvoke"&lt;/span&gt;
  &lt;span class="nx"&gt;action&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda:InvokeFunction"&lt;/span&gt;
  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_name&lt;/span&gt;
  &lt;span class="nx"&gt;principal&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apigateway.amazonaws.com"&lt;/span&gt;
  &lt;span class="nx"&gt;source_arn&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${aws_api_gateway_rest_api.main.execution_arn}/*/*"&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;API Gateway is verbose in Terraform.&lt;/strong&gt; That's 7 resources for one endpoint. Here's what each does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rest_api&lt;/code&gt; - The API itself&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resource&lt;/code&gt; - The &lt;code&gt;/hello&lt;/code&gt; path&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;method&lt;/code&gt; - GET on &lt;code&gt;/hello&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;integration&lt;/code&gt; - Connect method to Lambda (always POST for Lambda proxy)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deployment&lt;/code&gt; - Snapshot of the API config&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stage&lt;/code&gt; - Named deployment (&lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;prod&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lambda_permission&lt;/code&gt; - Allows API Gateway to invoke the function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;triggers&lt;/code&gt; block on deployment ensures redeployment when any resource changes. Without it, API Gateway serves stale configurations.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 Step 8: Variables and Outputs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;variables.tf&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"aws_region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS region"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"project_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Project name"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-lambda"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Environment"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"s3_bucket_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"S3 bucket name for Lambda triggers"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-lambda-demo-2026"&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;&lt;code&gt;outputs.tf&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"api_gateway_url"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"API Gateway URL"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${aws_api_gateway_stage.main.invoke_url}/hello"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"hello_lambda_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Hello Lambda function name"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"s3_bucket_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"S3 bucket name"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🚀 Deploy and Test
&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;# Initialize (downloads aws + archive providers)&lt;/span&gt;
terraform init
&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%2Fkhir9ywgamtv8izyse97.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%2Fkhir9ywgamtv8izyse97.png" alt=" " width="800" height="457"&gt;&lt;/a&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;# Preview&lt;/span&gt;
terraform plan
&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%2F8gq0lum7d0ar26sqgnsf.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%2F8gq0lum7d0ar26sqgnsf.png" alt=" " width="800" height="488"&gt;&lt;/a&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;# Deploy (creates ~18 resources)&lt;/span&gt;
terraform apply
&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%2Fbdptedcnr2f0sfx4yk0u.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%2Fbdptedcnr2f0sfx4yk0u.png" alt=" " width="800" height="312"&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%2Fivdd0hh0bsq4fsjbv2r0.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%2Fivdd0hh0bsq4fsjbv2r0.png" alt=" " width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Deployment takes about 2 minutes. Most of that is API Gateway.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Test 1: API Gateway → Hello Lambda
&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;# Get the API URL&lt;/span&gt;
&lt;span class="nv"&gt;API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; api_gateway_url&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Basic call&lt;/span&gt;
curl &lt;span class="nv"&gt;$API_URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected output:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123-def456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform-lambda-hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory_limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vjqymlkhv6ntvynafyx.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%2F3vjqymlkhv6ntvynafyx.png" alt=" " width="799" height="62"&gt;&lt;/a&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;# With query parameter&lt;/span&gt;
curl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$API_URL&lt;/span&gt;&lt;span class="s2"&gt;?name=Terraform"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected output:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, Terraform!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xyz789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform-lambda-hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory_limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Test 2: S3 Upload → Processor Lambda
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a test file&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello from Terraform Lambda demo"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; test.txt

&lt;span class="c"&gt;# Upload to S3 (triggers Lambda)&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;test.txt s3://&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; s3_bucket_name&lt;span class="si"&gt;)&lt;/span&gt;/test.txt


&lt;span class="o"&gt;![&lt;/span&gt; &lt;span class="o"&gt;](&lt;/span&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zliyk4zjtqmkcmszsncp.png&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="c"&gt;# Check CloudWatch logs (wait 10 seconds)&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;10
aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /aws/lambda/terraform-lambda-s3-processor &lt;span class="nt"&gt;--since&lt;/span&gt; 2m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected log output:&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;Processing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;test.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Bucket:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;terraform-lambda-demo&lt;/span&gt;&lt;span class="mi"&gt;-2026&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Size:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Event:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ObjectCreated:Put&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Result:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"processed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Test 3: Verify Non-.txt Files Don't Trigger
&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;# Upload a .jpg - should NOT trigger Lambda&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"not a text file"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; test.jpg
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;test.jpg s3://&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; s3_bucket_name&lt;span class="si"&gt;)&lt;/span&gt;/test.jpg

&lt;span class="o"&gt;![&lt;/span&gt; &lt;span class="o"&gt;](&lt;/span&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7a3sdyjpcrqhf1rnxej9.png&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="c"&gt;# Check logs - no new entries&lt;/span&gt;
aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /aws/lambda/terraform-lambda-s3-processor &lt;span class="nt"&gt;--since&lt;/span&gt; 1m
&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%2Ffecmrrz6k38mfz3plqhu.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%2Ffecmrrz6k38mfz3plqhu.png" alt=" " width="800" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing. The &lt;code&gt;.txt&lt;/code&gt; filter works.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 Understanding the Cost
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lambda pricing:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First 1 million requests/month: &lt;strong&gt;FREE&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;After that: $0.20 per million requests&lt;/li&gt;
&lt;li&gt;Compute: $0.0000166667 per GB-second&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Our usage estimate:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API: 1,000 requests/day = 30,000/month → &lt;strong&gt;FREE&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;S3 processor: 100 files/day = 3,000/month → &lt;strong&gt;FREE&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Compare to EC2:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;th&gt;Maintenance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EC2 t3.medium (24/7)&lt;/td&gt;
&lt;td&gt;$30.37&lt;/td&gt;
&lt;td&gt;Patch, monitor, scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda (30K requests)&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;Zero maintenance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's not a typo. For low-traffic workloads, Lambda is effectively free.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐛 Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue 1: "Unable to import module 'hello'"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error:&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;Runtime.ImportModuleError: Unable to import module 'hello': No module named 'hello'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Handler doesn't match filename. If your file is &lt;code&gt;hello.py&lt;/code&gt;, handler must be &lt;code&gt;hello.lambda_handler&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Verify &lt;code&gt;handler&lt;/code&gt; in Terraform matches &lt;code&gt;filename.function_name&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Issue 2: S3 Trigger Not Firing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptoms:&lt;/strong&gt; Upload file, nothing in logs.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Is the file suffix &lt;code&gt;.txt&lt;/code&gt;? Other files are filtered out.&lt;/li&gt;
&lt;li&gt;Did the permission deploy before the notification? Check &lt;code&gt;depends_on&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check Lambda permission: &lt;code&gt;aws lambda get-policy --function-name terraform-lambda-s3-processor&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Issue 3: API Gateway Returns 500
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Usually a Lambda execution error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debug:&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;# Check Lambda logs directly&lt;/span&gt;
aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /aws/lambda/terraform-lambda-hello &lt;span class="nt"&gt;--since&lt;/span&gt; 5m

&lt;span class="c"&gt;# Test Lambda directly (bypass API Gateway)&lt;/span&gt;
aws lambda invoke &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--function-name&lt;/span&gt; terraform-lambda-hello &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'{"queryStringParameters":{"name":"Test"}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  response.json

&lt;span class="nb"&gt;cat &lt;/span&gt;response.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Issue 4: "AccessDeniedException" on S3
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; IAM policy doesn't include the bucket or action needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Verify the custom policy references the correct bucket ARN with &lt;code&gt;/*&lt;/code&gt; suffix for object-level actions.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set &lt;code&gt;source_code_hash&lt;/code&gt;&lt;/strong&gt; - Without it, Terraform won't redeploy when code changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;archive_file&lt;/code&gt; data source&lt;/strong&gt; - Let Terraform handle zipping. Manual zip files drift.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set retention on log groups&lt;/strong&gt; - Default is infinite. At $0.03/GB stored, this adds up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Timeout appropriately&lt;/strong&gt; - API handlers: 10-30s. Processing: 30-300s. Never use the 900s max unless you know why.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory = CPU&lt;/strong&gt; - Lambda allocates CPU proportionally to memory. 128MB gets minimal CPU. 1024MB gets significantly more. If your function is slow, increase memory before optimizing code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment variables for config&lt;/strong&gt; - Never hardcode bucket names, table names, or URLs in your Lambda code. Pass them through environment variables in Terraform.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🧹 Cleanup
&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;# Remove S3 objects first (bucket must be empty)&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;rm &lt;/span&gt;s3://&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; s3_bucket_name&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt;

&lt;span class="c"&gt;# Destroy everything&lt;/span&gt;
terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;S3 buckets can't be deleted if they contain objects. Empty it first, then Terraform handles the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Summary
&lt;/h2&gt;

&lt;p&gt;Today you learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Deploy Lambda functions with Terraform&lt;/li&gt;
&lt;li&gt;✅ Package Python code with &lt;code&gt;archive_file&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✅ Create API Gateway endpoints&lt;/li&gt;
&lt;li&gt;✅ Trigger Lambda from S3 uploads&lt;/li&gt;
&lt;li&gt;✅ Apply least-privilege IAM (Article 9 skills!)&lt;/li&gt;
&lt;li&gt;✅ Manage CloudWatch logs with retention&lt;/li&gt;
&lt;li&gt;✅ Test functions via API and S3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The serverless mindset:&lt;/strong&gt; Stop paying for idle servers. Lambda runs your code only when needed, scales automatically, and costs nearly nothing for typical workloads.&lt;/p&gt;




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

&lt;p&gt;In the next article, we'll add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform modules for reusability&lt;/li&gt;
&lt;li&gt;Package your Lambda + API Gateway as a reusable module&lt;/li&gt;
&lt;li&gt;Share infrastructure patterns across projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Coming Up:&lt;/strong&gt; &lt;a href="https://dev.tolink"&gt;Article 9: Secure Database Deployment: RDS + Secrets Manager with Terraform&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;Thank you for reading. I hope this article provided practical insights and a clearer understanding of the topic.&lt;/p&gt;

&lt;p&gt;If you found this useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value
&lt;/li&gt;
&lt;li&gt;🦄 Unicorn if you’re applying it today
&lt;/li&gt;
&lt;li&gt;💾 Save it for your next optimization session
&lt;/li&gt;
&lt;li&gt;🔄 Share it with your team
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 What’s Next
&lt;/h2&gt;

&lt;p&gt;More deep dives are coming soon on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Operations&lt;/li&gt;
&lt;li&gt;GenAI &amp;amp; Agentic AI&lt;/li&gt;
&lt;li&gt;DevOps Automation&lt;/li&gt;
&lt;li&gt;Data &amp;amp; Platform Engineering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow along for weekly insights and hands-on guides.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 Portfolio &amp;amp; Work
&lt;/h2&gt;

&lt;p&gt;You can explore my full body of work, certifications, architecture projects, and technical articles here:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit My Website&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Services I Offer
&lt;/h2&gt;

&lt;p&gt;If you're looking for hands-on guidance or collaboration, I provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Architecture Consulting (AWS / Azure)&lt;/li&gt;
&lt;li&gt;DevSecOps &amp;amp; Automation Design&lt;/li&gt;
&lt;li&gt;FinOps Optimization Reviews&lt;/li&gt;
&lt;li&gt;Technical Writing (Cloud, DevOps, GenAI)&lt;/li&gt;
&lt;li&gt;Product &amp;amp; Architecture Reviews&lt;/li&gt;
&lt;li&gt;Mentorship &amp;amp; 1:1 Technical Guidance&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🤝 Let’s Connect
&lt;/h2&gt;

&lt;p&gt;I’d love to hear your thoughts. Feel free to drop a comment or connect with me on:&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For collaborations, consulting, or technical discussions, reach out at:&lt;/p&gt;

&lt;p&gt;📧 &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Found this helpful? Share it with your team.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;⭐ Star the repo • 📖 Follow the series • 💬 Ask questions  &lt;/p&gt;

&lt;p&gt;Made by &lt;strong&gt;Sarvar Nadaf&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
🌐 &lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;https://sarvarnadaf.com&lt;/a&gt;&lt;/p&gt;




</description>
      <category>aws</category>
      <category>terraform</category>
      <category>devops</category>
      <category>ai</category>
    </item>
    <item>
      <title>My AI Agent Hit a Login Wall: BrowserAct Let It Ask for Help and Resume</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Tue, 16 Jun 2026 13:42:35 +0000</pubDate>
      <link>https://dev.to/aws-builders/my-ai-agent-hit-a-login-wall-browseract-let-it-ask-for-help-and-resume-3mia</link>
      <guid>https://dev.to/aws-builders/my-ai-agent-hit-a-login-wall-browseract-let-it-ask-for-help-and-resume-3mia</guid>
      <description>&lt;p&gt;👋 Hey there, Tech Enthusiasts!&lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect who loves turning complex tech problems into simple solutions. I've worked with AWS, Azure, DevOps, Data, Analytics, Generative-AI and Agentic-AI building real systems for real companies. In this article series, I'll share what I've learned in a way that's easy to follow, whether you're experienced or just getting started.&lt;/p&gt;

&lt;p&gt;Let's get into it! 🚀&lt;/p&gt;




&lt;p&gt;I'm a cloud architect. I manage infrastructure across multiple AWS accounts, run CI/CD pipelines, and keep monitoring dashboards healthy for clients. A lot of my day involves checking web-based tools Grafana, GitHub, vendor portals, internal dashboards most of which sit behind login walls and anti-bot protection.&lt;/p&gt;

&lt;p&gt;But there was always a gap: the agent couldn't browse the web. It couldn't check a dashboard, read a protected page, or handle a login flow.&lt;/p&gt;

&lt;p&gt;That changed when I integrated &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; into my workflow. It's a browser layer that gives AI agents the ability to browse real websites with anti-detection, session management, and human handoff built in.&lt;/p&gt;

&lt;p&gt;If you missed the first article where I covered the full setup, start there: &lt;a href="https://dev.to/aws-builders/i-gave-my-ai-agent-a-real-browser-heres-what-actually-happened-4ipk"&gt;I Gave My AI Agent a Real Browser - Here's What Actually Happened&lt;/a&gt;. This article focuses on the headless + human handoff pattern I've been running in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Tooling
&lt;/h2&gt;

&lt;p&gt;I'm using &lt;strong&gt;Kiro&lt;/strong&gt; as my AI agent it's free during preview and can execute CLI commands directly. But BrowserAct works with anything that can run shell commands: Claude Code, Cursor, Codex, CrewAI, LangChain, or even a simple bash script. The pattern is the same regardless of agent.&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%2Fimdfqan9swe8ixpctzqn.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%2Fimdfqan9swe8ixpctzqn.png" alt=" " width="799" height="126"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;I run BrowserAct on a Linux server no desktop, no display, just a terminal. This is how it runs in production for my client: headless on a server, triggered by cron or the agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before getting started, make sure the following components are installed on your system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify Installed Versions
&lt;/h3&gt;

&lt;p&gt;Run the following commands:&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;--version&lt;/span&gt;
&lt;span class="c"&gt;# Python 3.12+&lt;/span&gt;

node &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# v18+&lt;/span&gt;

google-chrome &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# Google Chrome 149.x.x.x&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%2Faqjihrw33bn2z7qgyqh5.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%2Faqjihrw33bn2z7qgyqh5.png" alt=" " width="736" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Install UV (If Not Already Installed)
&lt;/h3&gt;

&lt;p&gt;BrowserAct uses Python tooling, and &lt;code&gt;uv&lt;/code&gt; is the recommended package manager.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh | sh
&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%2Flxw7szahj173py29zqoa.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%2Flxw7szahj173py29zqoa.png" alt=" " width="800" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Google Chrome (If Not Already Installed)
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Ubuntu / Debian
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
&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; ./google-chrome-stable_current_amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Amazon Linux
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum localinstall &lt;span class="nt"&gt;-y&lt;/span&gt; google-chrome-stable_current_x86_64.rpm
&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%2F1bwb4rs2eh4kczkcxeiy.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%2F1bwb4rs2eh4kczkcxeiy.png" alt=" " width="799" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a BrowserAct API Key
&lt;/h2&gt;

&lt;p&gt;To allow your AI agent to control a real browser, you'll need a BrowserAct API key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Sign In
&lt;/h3&gt;

&lt;p&gt;Log in to your &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct &lt;/a&gt;account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Open API Key Management
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Click your profile email address in the top-right corner.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;API Keys&lt;/strong&gt; from the dropdown menu.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Manage Keys&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3: Create a New API Key
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Create Key&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enter a descriptive name such as:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Amazon-Q&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MCP-Server&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Development&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Save the API Key
&lt;/h3&gt;

&lt;p&gt;Copy the generated API key and store it securely. For security reasons, you may not be able to view the complete key again after leaving the page.&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%2Ff05z3wluzzpbgr2z7xdg.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%2Ff05z3wluzzpbgr2z7xdg.png" alt=" " width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Treat your API key like a password. Never share it publicly or commit it to source code repositories.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Configure BrowserAct Authentication
&lt;/h2&gt;

&lt;p&gt;Once you have your API key, authenticate BrowserAct using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act auth &lt;span class="nb"&gt;set&lt;/span&gt; &amp;lt;your-api-key&amp;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%2F7bgow0rn50pbc7cc4gq4.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%2F7bgow0rn50pbc7cc4gq4.png" alt=" " width="796" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Successful authentication will return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API key saved.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, BrowserAct is connected and ready to provide browser access to your AI agent. The integration takes less than a minute and requires no additional configuration.&lt;/p&gt;

&lt;p&gt;After that, the agent has a browser. One more step create a stealth browser instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act browser create &lt;span class="nt"&gt;--type&lt;/span&gt; stealth &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"research"&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%2F9a3boaj3pl7mzav9xdpc.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%2F9a3boaj3pl7mzav9xdpc.png" alt=" " width="797" height="56"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;101758963005571124 name="research" type=stealth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;id&lt;/code&gt; is your browser ID you'll use it every time you open a session. Think of it like a browser profile: it keeps its own fingerprint, cookies, and anti-detection settings. You create it once and reuse it across sessions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The browser ID shown in this article (&lt;code&gt;101758963005571124&lt;/code&gt;) is from my account. When you run &lt;code&gt;browser create&lt;/code&gt;, you'll get your own unique ID. Use that in place of mine throughout the examples.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Managing Sessions
&lt;/h3&gt;

&lt;p&gt;Before starting new sessions, check if any are already running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;browser-act session list
&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%2Ffiljkuvfpvuzoteqsrak.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%2Ffiljkuvfpvuzoteqsrak.png" alt=" " width="732" height="501"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;session_name: research-gh
browser_type: stealth
browser_id: 101758963005571124
title: Trending repositories on GitHub today · GitHub
url: https://github.com/trending

session_name: research-hn
browser_type: stealth
browser_id: 101758963005571124
title: news.ycombinator.com
url: https://news.ycombinator.com/

session_name: research-ph
browser_type: stealth
browser_id: 101758963005571124
title: Product Hunt – The best new products in tech.
url: https://www.producthunt.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To close a specific session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-hn session close
&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;session_name=research-hn closed=true
&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%2Fp9u6zismbxfct83mzbsh.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%2Fp9u6zismbxfct83mzbsh.png" alt=" " width="800" height="70"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Always close sessions when you're done. Open sessions keep the browser running and consume resources. If you hit a "session already in use" error, it means that session name is still active either close it or use a different name.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Scenario: Morning Tech Research
&lt;/h2&gt;

&lt;p&gt;One of the things I do for a client is compile a daily tech digest what's trending, what's launching, what competitors are shipping. Used to take me 30 minutes of tab-switching every morning.&lt;/p&gt;

&lt;p&gt;Now my agent does it. Here's what that looks like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Extract One Session, One Page
&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;# Open a stealth browser session on the target page&lt;/span&gt;
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-hn browser open 101758963005571124 https://news.ycombinator.com
&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%2Fbzogq79nze0a32e2g7ls.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%2Fbzogq79nze0a32e2g7ls.png" alt=" " width="800" height="80"&gt;&lt;/a&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;# Get the page state&lt;/span&gt;
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-hn state
&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%2Fjd6wvxxt8kmgztmnc8cd.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%2Fjd6wvxxt8kmgztmnc8cd.png" alt=" " width="799" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent got back clean, structured content page title, URL, and all interactive elements. From there it can extract exactly what it needs using JS eval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-hn &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s1"&gt;'JSON.stringify(Array.from(document.querySelectorAll(".athing")).slice(0,3).map(el =&amp;gt; ({title: el.querySelector(".titleline a")?.textContent, points: el.nextElementSibling?.querySelector(".score")?.textContent})))'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI agent bankrupted their operator while trying to scan DN42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"points"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"171 points"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nobody ever gets credit for fixing problems that never happened"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"points"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"348 points"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"If you are asking for human attention, demonstrate human effort"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"points"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"537 points"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Show HN: Homebrew 6.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"points"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1145 points"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Two commands to open, one to extract. The agent can summarize this, filter by topic, or flag anything relevant to the client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where this fits:&lt;/strong&gt; Any team that needs a daily briefing tech trends, industry news, competitor launches. The agent grabs it, the team reads a summary instead of spending 30 minutes browsing.&lt;/p&gt;




&lt;h3&gt;
  
  
  Parallel Research - Three Sites at Once
&lt;/h3&gt;

&lt;p&gt;For the full morning digest, the agent opens three parallel sessions on the same browser.&lt;/p&gt;

&lt;p&gt;You can use the browser you already created, or create a separate one to keep research isolated from other workflows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act browser create
&lt;span class="c"&gt;# Returns: id=101764340218654773&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%2Fi87ppowjm1td9bt2p217.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%2Fi87ppowjm1td9bt2p217.png" alt=" " width="796" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then open sessions on 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="c"&gt;# Session 1: GitHub Trending&lt;/span&gt;
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-gh browser open 101764340218654773 https://github.com/trending
&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%2Fd5y9xl14ihi3at297bkw.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%2Fd5y9xl14ihi3at297bkw.png" alt=" " width="795" height="81"&gt;&lt;/a&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;# Session 2: Hacker News&lt;/span&gt;
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-hn browser open 101764340218654773 https://news.ycombinator.com
&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%2Fgh5hvgfzxtk6xp2blpgh.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%2Fgh5hvgfzxtk6xp2blpgh.png" alt=" " width="800" height="82"&gt;&lt;/a&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;# Session 3: Product Hunt&lt;/span&gt;
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-ph browser open 101764340218654773 https://www.producthunt.com
&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%2Fjmbbue8nn2f4ov9d42au.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%2Fjmbbue8nn2f4ov9d42au.png" alt=" " width="796" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All three run independently. No conflicts. The agent works through each one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act session list
&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;session_name: research-gh
browser_type: stealth
browser_id: 101764340218654773
title: Trending repositories on GitHub today · GitHub
url: https://github.com/trending

session_name: research-hn
browser_type: stealth
browser_id: 101764340218654773
title: news.ycombinator.com
url: https://news.ycombinator.com/

session_name: research-ph
browser_type: stealth
browser_id: 101764340218654773
title: Product Hunt – The best new products in tech.
url: https://www.producthunt.com/
&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%2Fr7iv6trlm1e06i502xd5.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%2Fr7iv6trlm1e06i502xd5.png" alt=" " width="742" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where this fits:&lt;/strong&gt; Product teams that need multi-source intelligence before standup. Marketing teams tracking launches. DevOps engineers checking status pages across providers. Anything where you'd normally open 5+ tabs.&lt;/p&gt;




&lt;h3&gt;
  
  
  Structured Data Extraction
&lt;/h3&gt;

&lt;p&gt;Instead of parsing full page HTML, the agent runs targeted JavaScript and gets clean JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-gh &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"JSON.stringify(Array.from(document.querySelectorAll('article.Box-row')).slice(0,3).map(r =&amp;gt; ({repo: r.querySelector('h2 a')?.textContent.trim(), stars: r.querySelector('span.d-inline-block.float-sm-right')?.textContent.trim()})))"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"repo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"iptv-org /&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;      iptv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"stars"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2,650 stars today"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"repo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"teslamate-org /&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;      teslamate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"stars"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"35 stars today"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"repo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Panniantong /&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;      Agent-Reach"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"stars"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1,045 stars today"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



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

&lt;p&gt;The agent navigated within the same session Python trending, then TypeScript without opening a new browser. Took a screenshot for the report. I covered extraction patterns in depth in previous &lt;a href="https://dev.to/aws-builders/i-gave-my-ai-agent-a-real-browser-heres-what-actually-happened-4ipk"&gt;article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where this fits:&lt;/strong&gt; Competitor monitoring prices, features, reviews. The agent extracts exactly the data points you need as structured JSON. No scraping framework. No maintenance when the page layout changes. BrowserAct isn't a standalone scraping tool  it's a browser layer. Your AI agent is the brain that decides what to do. BrowserAct is the eyes and hands that execute on the web.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then the Agent Hits a Wall
&lt;/h2&gt;

&lt;p&gt;Everything was going smoothly. The agent had data from three sources, screenshots saved, research compiling nicely. Then it tried to check my GitHub profile settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-gh navigate https://github.com/settings/profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&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;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fsettings%2Fprofile&lt;/span&gt;
&lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Sign in to GitHub · GitHub&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%2Fz6punibd6dch79ch048p.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%2Fz6punibd6dch79ch048p.png" alt=" " width="798" height="77"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Redirected to login. The agent checked the page state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;[2]&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    Username or email address
[3]&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;login&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
[5]&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
[7]&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;submit&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;Sign&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
[8]&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    Continue with Google
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With any other automation setup, this is where the workflow dies. Script crashes. Logs an error. Someone restarts it manually tomorrow.&lt;/p&gt;

&lt;p&gt;Here's what my agent did instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Agent Asks for Help
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-gh remote-assist &lt;span class="nt"&gt;--objective&lt;/span&gt; &lt;span class="s2"&gt;"Sign in to GitHub to access profile settings"&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;Remote assist session created.

Share this URL with the user:
  https://www.browseract.com/remote-cli/1c08b0f3e0cb46168c9dd836ead748d2
expires in 1h 0m

Human assist is now active - the browser is under user control.
Do not send browser commands until the user finishes the assist session.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent recognized it couldn't solve this. It generated a live URL and asked me for help.&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%2Fw8l5hdxjbrenebd04fy1.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%2Fw8l5hdxjbrenebd04fy1.png" alt="Remote assist session created with shareable URL" width="800" height="79"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1: Open the Remote Assist URL
&lt;/h3&gt;

&lt;p&gt;I opened that URL on my phone. I saw the actual browser the GitHub login page, exactly as the agent left it.&lt;/p&gt;

&lt;p&gt;After opening the link in your browser, you'll see the remote session interface. Click "Take Control" to interact with the browser directly. The session remains active for up to 1 hour.&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%2F5qmcixa7f0il9c286qdr.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%2F5qmcixa7f0il9c286qdr.png" alt="Remote assist interface showing Take Control button with 1 hour timer" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: Complete the Login
&lt;/h3&gt;

&lt;p&gt;Once you click Take Control, you'll see the GitHub login UI. Enter your credentials and complete the OTP/2FA to sign in.&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%2Fuzjxtuoef0jrjoqe5c4y.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%2Fuzjxtuoef0jrjoqe5c4y.png" alt="GitHub login page rendered inside the remote assist browser" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: Confirm Access
&lt;/h3&gt;

&lt;p&gt;Once you log in successfully, you'll see the GitHub profile page confirming the session is now authenticated.&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%2Foufsrtd0yc3rqj21ecy6.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%2Foufsrtd0yc3rqj21ecy6.png" alt="GitHub profile page loaded after successful login" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4: Hand Control Back to the Agent
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;"Complete"&lt;/strong&gt; in the top-right corner to end the human assist session and return control to the agent.&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%2Frpwz1y2y0a12jdgxtcfv.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%2Frpwz1y2y0a12jdgxtcfv.png" alt="Complete button in top-right corner of remote assist interface" width="799" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you click Done, the step is completed. The agent is now rerouted back to the BrowserAct terminal to continue its work.&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%2Fdsoqsi0fpfp8r98tib8d.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%2Fdsoqsi0fpfp8r98tib8d.png" alt="Session completed confirmation screen" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Agent Resumes Same Session, No Restart
&lt;/h2&gt;

&lt;p&gt;After the human signs in and closes the remote-assist session, the agent picks up exactly where it left off:&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;# Verify: agent checks where it is now&lt;/span&gt;
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-gh state
&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;url=https://github.com/settings/profile
title=Your profile

[14]&amp;lt;a class=color-fg-default /&amp;gt;
    Sarvar's (simplynadaf)
[15]&amp;lt;a class=btn btn-sm /&amp;gt;
    Go to your personal profile
[16]&amp;lt;a /&amp;gt;  Public profile
[17]&amp;lt;a /&amp;gt;  Account
[18]&amp;lt;a /&amp;gt;  Appearance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No login prompt. The agent now has full access to the authenticated GitHub session.&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%2Fver4tl65q3ofch85t3j6.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%2Fver4tl65q3ofch85t3j6.png" alt="Agent state showing authenticated GitHub profile page" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Proof: Navigating Authenticated Content
&lt;/h3&gt;

&lt;p&gt;The agent can now access any authenticated resource without interruption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; research-gh navigate https://github.com/simplynadaf/devsecops-pipeline-demo
&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;url=https://github.com/simplynadaf/devsecops-pipeline-demo
title=simplynadaf/devsecops-pipeline-demo: DevSecOps Pipeline Demo with Security Scanning
new_tab=False
&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%2Fdrl83nvoi2dhphe7ph61.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%2Fdrl83nvoi2dhphe7ph61.png" alt="Agent successfully accessing private repo after authentication" width="796" height="68"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It shows the repo title instead of redirecting to &lt;code&gt;/login&lt;/code&gt; proving the authenticated session is active and persisted through the handoff.&lt;/p&gt;

&lt;p&gt;The agent continued from where it left off. Same session. Same browser state. No restart. No lost context.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Handoff Flow
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;BrowserAct Action&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;navigate github.com/settings/profile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Detects login redirect&lt;/td&gt;
&lt;td&gt;Login wall identified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remote-assist --objective "Sign in to GitHub"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generates live URL, pauses agent&lt;/td&gt;
&lt;td&gt;URL sent to human via Slack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human signs in + 2FA on phone&lt;/td&gt;
&lt;td&gt;Session state preserved&lt;/td&gt;
&lt;td&gt;Agent resumes with authenticated session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads authenticated page&lt;/td&gt;
&lt;td&gt;Profile settings data extracted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Morning Report Output
&lt;/h3&gt;

&lt;p&gt;After the agent completes all checks (including the ones that needed human login), it posts this to &lt;code&gt;#team-status&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;DAILY INFRASTRUCTURE REPORT - Mon Jun 15, 2026 06:04 UTC

Grafana (prod):     All dashboards green. No alerts in 24h.
GitHub (org):       3 PRs merged overnight. 1 pending review.
AWS Health:         No scheduled maintenance. All regions healthy.
Vendor Portal:     SSL cert expires in 12 days. Ticket created.
Uptime Monitor:     99.97% across all endpoints (7-day avg).

Auth events:       1 remote-assist triggered (GitHub session expired).
                   Resolved in 38 seconds by on-call.

Next run: Tomorrow 06:00 UTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why This Is a Design Pattern
&lt;/h2&gt;

&lt;p&gt;Most automation falls into two camps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fully automated (breaks when anything unexpected happens)&lt;/li&gt;
&lt;li&gt;Fully manual (defeats the purpose of automation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The human handoff is a third option: the agent does 95% of the work autonomously. When it hits the 5% that requires a human a login, a 2FA prompt, a CAPTCHA it can't solve it pauses, asks for help, and resumes.&lt;/p&gt;

&lt;p&gt;I've been building automation for years. Every time I tried to make something "fully automated" that involved login-protected tools, it would break within a week. Session expired. MFA rotated. Cookie invalidated.&lt;/p&gt;

&lt;p&gt;The answer was always "just add a human step" but there was never a clean way to do that without killing the whole automation. This is the clean way.&lt;/p&gt;




&lt;h2&gt;
  
  
  It Runs Headless - That's What Makes It Production Ready
&lt;/h2&gt;

&lt;p&gt;This entire test ran on a Linux server with no display:&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;echo&lt;/span&gt; &lt;span class="nv"&gt;$DISPLAY&lt;/span&gt;
&lt;span class="c"&gt;# (empty - no GUI)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No screen. No desktop. The agent and BrowserAct run completely headless. But when &lt;code&gt;remote-assist&lt;/code&gt; triggers, it gives the human a visual interface to that headless browser through a URL.&lt;/p&gt;

&lt;p&gt;You see the browser as if it were on your desktop even though it's running on a server with no monitor attached.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your agent runs on any server, any cloud, any CI pipeline&lt;/li&gt;
&lt;li&gt;No VNC, no desktop environment, no display needed&lt;/li&gt;
&lt;li&gt;When human help is needed, the URL works from any device - phone, laptop, tablet&lt;/li&gt;
&lt;li&gt;After the human is done, the headless agent continues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where this fits:&lt;/strong&gt; DevOps and SRE teams running agents on headless servers or in containers. When the agent needs a human, tap the link from your phone on a train, in a meeting, or at 2 AM.&lt;/p&gt;




&lt;h2&gt;
  
  
  How This Runs in Production
&lt;/h2&gt;

&lt;p&gt;Here's the actual workflow I built for my client's infrastructure monitoring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6:00 AM - Cron triggers the agent

Agent (headless, on Linux Server):
  → Opens parallel sessions on 5 dashboards
  → Extracts status data, takes screenshots
  → Hits a login wall on one dashboard (session expired overnight)
  → Sends remote assist URL to Slack

6:01 AM - Slack notification on the on call engineer's phone

Engineer (half awake):
  → Taps the URL
  → Sees the login page
  → Signs in, taps MFA approve
  → Closes

6:02 AM - Agent resumes, finishes remaining checks, posts morning report to #team-status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authentication happens maybe once or twice a week. The agent handles everything else every day. That's the ratio 95% automated, 5% human, zero broken pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Review
&lt;/h2&gt;

&lt;p&gt;What worked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Human handoff works exactly as described. URL generates instantly, state persists after.&lt;/li&gt;
&lt;li&gt;The agent-to-browser integration is clean. Commands are simple, outputs are agent-friendly.&lt;/li&gt;
&lt;li&gt;Anti-detection gets through Cloudflare without the agent doing anything special.&lt;/li&gt;
&lt;li&gt;Headless mode on a server with no display works perfectly with remote assist.&lt;/li&gt;
&lt;li&gt;Parallel sessions are stable and independent.&lt;/li&gt;
&lt;li&gt;JS eval gives the agent precision extraction without any scraping libraries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What could be better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentation is dense. The skill reference is thorough but overwhelming the first time.&lt;/li&gt;
&lt;li&gt;Error messages aren't always helpful. "Connection closed" doesn't tell you much.&lt;/li&gt;
&lt;li&gt;Speed is slower than raw Puppeteer. The anti-detection adds a few seconds per session.&lt;/li&gt;
&lt;li&gt;You need an API key for the stealth features.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can AI agents handle login walls?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not on their own. When an agent hits a login page, it can't type your password or tap your MFA prompt. It just gets stuck. BrowserAct solves this with remote assist the agent pauses, sends you a link, you handle the login, and the agent picks up where it left off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is BrowserAct remote assist?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's a feature that lets your agent ask a human for help mid workflow. The agent generates a URL that opens the live browser in your phone or laptop. You do the human step (login, 2FA, CAPTCHA), close it, and the agent continues automatically. No restart, no lost state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does BrowserAct need an API key?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, for the stealth browser features (anti-detection, fingerprint masking, proxy rotation). You can get one at &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;browseract.com&lt;/a&gt;. There are free credits on signup to test with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can this run on a headless server?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. That's how I run it on an Linux Server with no display, no desktop environment. The browser runs headless. When the agent needs a human, the remote-assist URL gives you a visual interface to that headless browser from any device.&lt;/p&gt;




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

&lt;p&gt;If you want to try this with your own agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install: &lt;code&gt;npx skills add browser-act/skills --skill browser-act --yes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install CLI: &lt;code&gt;uv tool install browser-act-cli --python 3.12&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Get an API key from &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;browseract.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Set it: &lt;code&gt;browser-act auth set &amp;lt;your-key&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Your agent can now browse.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Works with Kiro, Claude Code, Cursor, Codex, CrewAI, or any tool that can run shell commands. The agent doesn't need to be special it just needs to call the CLI.&lt;/p&gt;




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

&lt;p&gt;The browser was always the gap in agent automation. Not because agents can't reason about web content they can. But because the web is built for humans, and the moment authentication enters the picture, pure automation dies. The human handoff pattern fixes this: the agent does 95% of the work, asks for help on the 5% it can't handle, and resumes without missing a beat. It's practical, it runs in production, and it replaced a workflow that used to break every other week.&lt;/p&gt;

&lt;p&gt;If you're a DevOps engineer, SRE, or cloud architect running AI agents and your agents can't touch the web this is worth 30 minutes of your time to test.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/browser-act/skills" rel="noopener noreferrer"&gt;GitHub - BrowserAct Skills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/browser-act/skills/tree/main/docs" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Thanks for reading! If this was helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value&lt;/li&gt;
&lt;li&gt;💾 Save for later&lt;/li&gt;
&lt;li&gt;🔄 Share with your team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow me for more on:&lt;/strong&gt; AWS architecture, FinOps, DevOps, and AI Infrastructure.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit my website&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;Connect on LinkedIn&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Learning&lt;/strong&gt; 🚀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>automation</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Introducing AWS FinOps Agent - An AI That Investigates Your Cloud Costs</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Thu, 11 Jun 2026 19:10:23 +0000</pubDate>
      <link>https://dev.to/aws-builders/introducing-aws-finops-agent-an-ai-that-investigates-your-cloud-costs-2j3i</link>
      <guid>https://dev.to/aws-builders/introducing-aws-finops-agent-an-ai-that-investigates-your-cloud-costs-2j3i</guid>
      <description>&lt;p&gt;👋 Hey there, Tech Enthusiasts!&lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect who loves turning complex tech problems into simple solutions. I've worked with AWS, Azure, DevOps, Data, Analytics, Generative-AI and Agentic-AI building real systems for real companies. In this article series, I'll share what I've learned in a way that's easy to follow, whether you're experienced or just getting started.&lt;/p&gt;

&lt;p&gt;Let's get into it! 🚀&lt;/p&gt;




&lt;p&gt;If you have managed AWS costs across multiple accounts, you know the pain. You get an alert at 9 AM that something spiked overnight. You open Cost Explorer, filter by service, then by account, then by region. Then it is over to CloudTrail to figure out what changed. By the time you trace it to a developer who launched a new instance type in a test account, two hours have passed. And that was just one anomaly.&lt;/p&gt;

&lt;p&gt;I have been doing this for years across organizations running 50 to 500 AWS accounts. Monthly cost reviews, chasing engineers for explanations, building reports in spreadsheets that nobody reads until budget meetings. When AWS announced the FinOps Agent in public preview on June 9, 2026, I cleared my afternoon and set it up.&lt;/p&gt;

&lt;p&gt;This article is my honest take after spending real time with it what it does well, where it fits in an enterprise setup, and how it changes the daily work for architects and FinOps teams.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is AWS FinOps Agent, in Plain Terms?
&lt;/h2&gt;

&lt;p&gt;It is an AI agent that sits on top of your existing AWS cost tools Cost Explorer, Cost Anomaly Detection, Cost Optimization Hub, Compute Optimizer, and CloudTrail and does the manual work you have been doing yourself.&lt;/p&gt;

&lt;p&gt;You ask it questions in plain English. It pulls data from those services, correlates events, and gives you answers. You can also tell it to run tasks on a schedule or react when a cost anomaly fires.&lt;/p&gt;

&lt;p&gt;It runs in us-east-1 during the preview, but it can see cost data from all your regions (except GovCloud and China). It is free during the preview period with a monthly usage cap.&lt;/p&gt;

&lt;p&gt;The underlying engine uses Amazon Bedrock foundation models, but you do not need to know or care about that. You just talk to it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting It Up Genuinely Simple
&lt;/h2&gt;

&lt;p&gt;I was expecting a 30-minute setup with IAM policy headaches. It took about 5 minutes.&lt;/p&gt;

&lt;p&gt;Here is the actual flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the AWS Console, switch to us-east-1, find FinOps Agent&lt;/li&gt;
&lt;li&gt;Click "Get Started" and give your agent a name&lt;/li&gt;
&lt;li&gt;It creates an IAM role for you with one click read-only access to billing and cost management services&lt;/li&gt;
&lt;li&gt;Optionally connect Jira and Slack&lt;/li&gt;
&lt;li&gt;Open the web application and start asking questions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. No CloudFormation stack, no Terraform module, no custom Lambda functions. The whole process felt like it was designed for people who have better things to do than fight with IAM policies.&lt;/p&gt;

&lt;p&gt;One thing to note: if you have multi-session enabled in your AWS Console, disable it first. I hit a permissions error during Slack setup that traced back to this. The documentation does not make this obvious upfront.&lt;/p&gt;

&lt;p&gt;Also, if you are setting up Slack, you need to add the FinOps Agent app as a member of the channel before configuring the integration in the console. Miss that step and you will get an error with no helpful message. The AWS docs mark it as "Important" but it is easy to skip over.&lt;/p&gt;




&lt;h2&gt;
  
  
  The First Question I Asked
&lt;/h2&gt;

&lt;p&gt;I opened the chat and typed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What were my top 5 cost drivers last month, grouped by service and account?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Within about 15 seconds, it came back with a table showing exactly that. It pulled from Cost Explorer, broke it down by service, showed the account names, and included the percentage change from the previous month.&lt;/p&gt;

&lt;p&gt;No dashboard to build. No saved report to configure. Just a question and an answer.&lt;/p&gt;

&lt;p&gt;I then asked a follow-up about why EC2 costs went up in one of my accounts. The agent went deeper it identified the instance type that caused the increase, surfaced the relevant CloudTrail event showing when those instances were launched, and pointed to the IAM role that made the API call.&lt;/p&gt;

&lt;p&gt;That kind of investigation jumping between Cost Explorer and CloudTrail, matching timestamps, finding the right principal usually takes me 20 to 30 minutes. The agent did it in under a minute.&lt;/p&gt;

&lt;p&gt;One thing I learned quickly: by default, costs shown include credit adjustments. If you want pre-credit numbers (which is what most enterprises care about for budgeting), you need to say that in your prompt. I told the agent to always exclude credits, and it remembered that preference in my next session without me repeating it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where This Solves Real Enterprise Pain
&lt;/h2&gt;

&lt;p&gt;Let me map this to the problems I have seen in every organization I have worked with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 1: Cost Anomalies Go Uninvestigated
&lt;/h3&gt;

&lt;p&gt;Every enterprise I know has AWS Cost Anomaly Detection turned on. Most of them also have a shared email inbox or Slack channel where those alerts land. And in most cases, those alerts sit there for hours or days before someone looks at them.&lt;/p&gt;

&lt;p&gt;The reason is simple investigation is manual and time-consuming. AWS FinOps Agent automates this. You set up an automation that says: "When a cost anomaly is detected above $500, investigate it, find the root cause, and post the findings to our #finops-alerts Slack channel."&lt;/p&gt;

&lt;p&gt;From that point, every anomaly gets investigated the moment it arrives. The output includes what changed, the CloudTrail event that caused it, the IAM principal responsible, and a summary of the likely root cause.&lt;/p&gt;

&lt;p&gt;For a small FinOps team supporting many accounts, this is the difference between catching problems in hours versus catching them in days.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 2: Engineers Cannot Self-Serve Cost Information
&lt;/h3&gt;

&lt;p&gt;In most enterprises, if a developer wants to understand their team's cost, they either need Cost Explorer access (which many organizations restrict) or they file a request with the central FinOps team.&lt;/p&gt;

&lt;p&gt;This creates a bottleneck. In my experience, the FinOps team ends up spending a significant chunk of their time answering basic questions like "How much did our staging environment cost last month?" or "Which of our Lambda functions is the most expensive?"&lt;/p&gt;

&lt;p&gt;With FinOps Agent, engineers ask their question, the agent answers it using real data, and the FinOps team never gets involved.&lt;/p&gt;

&lt;p&gt;You can also upload context files a CSV that maps accounts to team owners, a document that explains your tagging conventions, a list of which cost centers map to which business units. The agent uses this context to understand questions like "What is Team Alpha spending?" without anyone having to explain which accounts belong to Team Alpha.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 3: Monthly Reports Are a Time Sink
&lt;/h3&gt;

&lt;p&gt;Every FinOps team I have worked with produces some version of a monthly cost report. It goes to finance, to engineering leadership, sometimes to the CTO. Building it involves exporting data from Cost Explorer, formatting it in a slide deck or spreadsheet, adding commentary about what changed, and sending it out.&lt;/p&gt;

&lt;p&gt;This takes 4 to 8 hours per month in my experience. Some teams spend even longer because different stakeholders want different views.&lt;/p&gt;

&lt;p&gt;FinOps Agent lets you schedule these. You tell it: "Every Monday at 8 AM, generate a cost report for last week broken down by business unit, highlight any changes over 10%, and deliver it as a PDF to the #finops-weekly Slack channel."&lt;/p&gt;

&lt;p&gt;It runs on schedule. No human involvement. The report shows up in Slack every Monday. You can set up different reports for different audiences a detailed one for engineering leads, a summary for finance, an executive view for the CTO.&lt;/p&gt;

&lt;p&gt;The output formats are HTML, PDF, or PPT all presentation-ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 4: Optimization Recommendations Sit in a Dashboard Nobody Checks
&lt;/h3&gt;

&lt;p&gt;AWS Cost Optimization Hub and Compute Optimizer both produce solid recommendations rightsizing opportunities, idle resources, Savings Plans suggestions. The problem is that these recommendations live in AWS dashboards that engineers rarely check.&lt;/p&gt;

&lt;p&gt;FinOps Agent can pull these recommendations, summarize them, and create Jira tickets assigned to the team that owns each resource. Instead of a FinOps analyst manually reviewing the dashboard and creating tickets, the agent does it on a schedule.&lt;/p&gt;

&lt;p&gt;Example automation: "Every Thursday, check Cost Optimization Hub for new recommendations over $100/month in potential savings. For each one, create a Jira ticket in the INFRA project with the recommendation details, the affected resource, and the estimated savings."&lt;/p&gt;

&lt;p&gt;Now optimization work flows into engineering sprints naturally, without someone manually copying data between tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Fits Into an Enterprise Architecture
&lt;/h2&gt;

&lt;p&gt;Let me describe how I would deploy this in a typical multi-account enterprise setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Management Account Setup
&lt;/h3&gt;

&lt;p&gt;You create the FinOps Agent in your management account (or the account that has the consolidated billing payer role). This gives it visibility across all member accounts. The IAM role it creates is read-only by default it cannot make changes to your infrastructure.&lt;/p&gt;

&lt;p&gt;Member account owners can also create their own agent scoped to just their account if they want independent cost visibility without management account access. This means you can have multiple agents with different scopes a central one for the FinOps team and individual ones for teams that want their own view without seeing the full organization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Patterns
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;How They Use It&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FinOps Team&lt;/td&gt;
&lt;td&gt;Set up automations for anomaly investigation, scheduled reports, optimization ticket creation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engineering Leads&lt;/td&gt;
&lt;td&gt;Ask ad-hoc cost questions about their team's spend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finance&lt;/td&gt;
&lt;td&gt;Receive scheduled reports in their preferred format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform Team&lt;/td&gt;
&lt;td&gt;Monitor shared infrastructure costs, investigate spikes in shared services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Individual Engineers&lt;/td&gt;
&lt;td&gt;Self-serve answers about their service's cost impact&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  How the Pieces Connect
&lt;/h3&gt;

&lt;p&gt;The agent reads from your existing AWS cost services Cost Explorer for spend data, Cost Anomaly Detection for alerts, Cost Optimization Hub and Compute Optimizer for recommendations, and CloudTrail for change history. It writes only to Jira and Slack when you configure those integrations and trigger a task. It does not modify any AWS resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Model
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;IAM role-based access (you control what it can read)&lt;/li&gt;
&lt;li&gt;Read-only by default against billing services&lt;/li&gt;
&lt;li&gt;Write actions (Jira tickets, Slack posts) only happen when you configure and trigger them&lt;/li&gt;
&lt;li&gt;All activity logged in CloudTrail for audit&lt;/li&gt;
&lt;li&gt;No cross-account write permissions&lt;/li&gt;
&lt;li&gt;The agent uses Amazon Bedrock foundation models to process your queries your cost data is accessed through IAM roles within your own account and does not leave AWS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing worth noting for architects evaluating risk: this service has zero infrastructure dependency. Your actual cost tools, accounts, and resources are unchanged. If the agent were deprecated tomorrow, you would be back to manual work not rebuilding systems. That makes it a low-risk addition to your environment during preview.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Noticed During Hands-On Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Good
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Speed of answers.&lt;/strong&gt; Asking a cost question and getting a real answer in 15-30 seconds changes your workflow. I found myself asking follow-up questions I would never have bothered investigating manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause correlation.&lt;/strong&gt; The fact that it connects Cost Anomaly Detection alerts to CloudTrail events automatically is genuinely useful. This is the step that takes the most time in manual investigation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduled tasks actually work.&lt;/strong&gt; I set up a weekly report for Thursday morning. It fired within 2 minutes of the scheduled time, and the PDF it produced was clean and readable. Results also get stored in an "Artifacts" tab in the web app for later access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context files are powerful.&lt;/strong&gt; After uploading an account-to-team mapping, the agent started answering team-level questions correctly without any additional configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory across sessions.&lt;/strong&gt; I told it to always exclude credits when showing cost totals. In my next session, it remembered that preference without me repeating it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Rough Edges (It Is Preview After All)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Slack messages are links, not inline content.&lt;/strong&gt; When the agent posts to Slack, it sends a link back to the FinOps Agent web app rather than the full analysis inline. This means you still need to click through to see the details. For a quick glance during a busy morning, this adds friction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sending to Slack is not automatic.&lt;/strong&gt; After the agent completes an analysis in a chat session, you still need to explicitly prompt "Please send this to Slack." Event-triggered automations handle routing on their own, but for ad-hoc queries you need that extra step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;English only.&lt;/strong&gt; Prompts in other languages sometimes work, but responses are always in English. Some prompts get a flat "Only English is supported" response. Global enterprises will need multi-language support at GA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single region availability.&lt;/strong&gt; The agent only runs in us-east-1 during preview. It can see multi-region cost data, but the console experience requires you to be in that region.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No IaC support yet.&lt;/strong&gt; You cannot create the agent via CloudFormation or Terraform. Console-only setup for now. For enterprises that mandate infrastructure as code for everything, this is a blocker until GA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compute Optimizer queries are region-scoped.&lt;/strong&gt; When I asked for rightsizing recommendations, it only checked us-east-1 by default. You need to specify other regions explicitly in your prompt. Easy to miss if you have workloads spread across multiple regions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust but verify.&lt;/strong&gt; Like any AI system, the agent can occasionally misidentify a root cause or produce an incomplete analysis. In the early days, treat its output as a strong starting point that still needs a human eye before you act on it or forward it to engineering. Once you have seen enough accurate results to build confidence, you can reduce oversight.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Workflow: Before and After
&lt;/h2&gt;

&lt;p&gt;Let me lay out what a typical week looks like for a FinOps team before and after adopting this. These numbers assume automations are tuned and the agent is producing reliable output getting there takes a week or two of initial validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before FinOps Agent
&lt;/h3&gt;

&lt;p&gt;Monday morning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check Cost Anomaly Detection alerts from the weekend (5 alerts) - 10 minutes&lt;/li&gt;
&lt;li&gt;Open Cost Explorer for each alert, filter by account/service/time - 15 minutes per alert&lt;/li&gt;
&lt;li&gt;Open CloudTrail, search for relevant API calls - 10 minutes per alert&lt;/li&gt;
&lt;li&gt;Write up findings and post to Slack - 5 minutes per alert&lt;/li&gt;
&lt;li&gt;Total: about 2.5 hours for 5 anomalies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wednesday:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull Cost Optimization Hub, review new recommendations - 30 minutes&lt;/li&gt;
&lt;li&gt;Create Jira tickets for engineering teams - 45 minutes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Friday:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build weekly cost report for leadership - 1.5 hours&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Weekly total for routine FinOps work: roughly 5-6 hours&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  After FinOps Agent (once automations are stable)
&lt;/h3&gt;

&lt;p&gt;Monday morning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check Slack - 5 anomaly investigations already posted over the weekend with root causes identified&lt;/li&gt;
&lt;li&gt;Review findings, confirm no false positives - 15 minutes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wednesday:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check Jira - optimization tickets already created for engineering teams&lt;/li&gt;
&lt;li&gt;Review for accuracy - 10 minutes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Friday:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Weekly report already delivered to Slack on Thursday evening&lt;/li&gt;
&lt;li&gt;Quick review for anything unusual - 5 minutes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Weekly total: roughly 30 minutes of review instead of 5-6 hours of manual work&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That freed-up time goes to strategic work: Savings Plans analysis, architecture reviews for cost efficiency, building chargeback models, negotiating Enterprise Discount Programs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who Should Try This Today
&lt;/h2&gt;

&lt;p&gt;If you are in one of these situations, set it up this week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FinOps team of 1-3 people supporting many accounts.&lt;/strong&gt; The automation alone justifies the setup time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineering organization where developers do not have Cost Explorer access.&lt;/strong&gt; Give them the agent instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any team that produces recurring cost reports manually.&lt;/strong&gt; Automate them immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organizations where cost anomaly alerts go uninvestigated.&lt;/strong&gt; Event-triggered investigation fixes this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are a solo developer with one account and minimal spend, this is probably more than you need right now. But if your monthly bill is complex enough that you spend time understanding it, it is worth trying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A word of caution:&lt;/strong&gt; This is a preview service. There is no SLA, features may change before GA, and you should not wire it into critical production workflows without a plan for what happens if the service changes or pricing is introduced. Use it to learn and validate, and be ready to adjust when GA arrives.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Early Customers Are Reporting
&lt;/h2&gt;

&lt;p&gt;Several companies shared their experience during the preview period. Here is what stood out to me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workday&lt;/strong&gt; runs their AI platform across many AWS accounts. Their team was spending hours every month chasing cost outliers and building leadership reports. They said that anomaly detection and reporting now happens from one natural-language interface, replacing what used to be hours of manual dashboard work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Convera&lt;/strong&gt;, a global commercial payments company in a regulated environment, focused on catching small cost changes before they compound. They described the agent completing the full loop detecting anomalies, investigating root cause, and creating Jira tickets for the right engineer so issues reach the owner directly instead of sitting in a shared queue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitre 10&lt;/strong&gt;, a New Zealand retailer with a lean platform team, was splitting time between reliability work and cost governance. They now define investigation workflows once and have them run continuously, instead of relying on someone remembering to check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AVIV Group&lt;/strong&gt; operates digital marketplaces across France, Germany, and Belgium with hundreds of AWS accounts. As they shift to a hybrid FinOps model, the agent answers engineer questions directly, freeing the central team for strategy and leadership reporting.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Am Watching For at GA
&lt;/h2&gt;

&lt;p&gt;A few things I want to see before this moves from "useful preview" to "enterprise standard":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pricing at GA.&lt;/strong&gt; It is free during preview. Enterprise adoption depends on what it costs relative to the time it saves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IaC support.&lt;/strong&gt; We need CloudFormation and Terraform support to deploy this in governed environments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline Slack responses.&lt;/strong&gt; The current link-based approach adds friction. Full findings posted inline would make the Slack integration much more natural.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-language support.&lt;/strong&gt; Global enterprises need this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Identity Center integration.&lt;/strong&gt; For SSO-based access control in organizations that have moved away from IAM users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deeper Slack interaction.&lt;/strong&gt; Currently delivery-only engineers cannot ask the agent questions from within Slack. That would complete the loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-region Compute Optimizer queries by default.&lt;/strong&gt; Should not require specifying regions manually.&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;AWS FinOps Agent does not replace your FinOps team. It replaces the repetitive, manual investigation work that consumes most of their time. It takes the data that already exists across Cost Explorer, CloudTrail, Cost Optimization Hub, and Compute Optimizer, and makes it accessible through conversation instead of dashboards.&lt;/p&gt;

&lt;p&gt;For architects, it means getting cost answers during design reviews without waiting for someone to run a report. For FinOps teams, it means spending time on strategy instead of triage. For engineering teams, it means getting cost context delivered to them in Jira and Slack instead of learning yet another AWS console.&lt;/p&gt;

&lt;p&gt;It took me 5 minutes to set up, and within the first hour I had it answering questions, investigating anomalies, and scheduling weekly reports. For a preview service, that is a strong start.&lt;/p&gt;

&lt;p&gt;The setup guide is at: &lt;a href="https://docs.aws.amazon.com/finops-agent/latest/userguide/what-is.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/finops-agent/latest/userguide/what-is.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The console is at: &lt;a href="https://us-east-1.console.aws.amazon.com/finops-agent/home" rel="noopener noreferrer"&gt;https://us-east-1.console.aws.amazon.com/finops-agent/home&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try it while it is free. Worst case, you spend 10 minutes setting it up and decide it is not for you yet. Best case, you automate away hours of weekly grunt work and spend that time on work that actually needs a human brain.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written after hands-on testing during the public preview period (June 2026). All features and behaviors described reflect the preview state and may change at general availability.&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;Thanks for reading! If this was helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value&lt;/li&gt;
&lt;li&gt;💾 Save for later&lt;/li&gt;
&lt;li&gt;🔄 Share with your team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow me for more on:&lt;/strong&gt; AWS architecture, FinOps, DevOps, and AI Infrastructure.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit my website&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;Connect on LinkedIn&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Learning&lt;/strong&gt; 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>agents</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Can AI Agents Browse Login-Protected Dashboards? I Tested It</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Tue, 09 Jun 2026 11:35:23 +0000</pubDate>
      <link>https://dev.to/aws-builders/i-gave-my-ai-agent-a-real-browser-heres-what-actually-happened-4ipk</link>
      <guid>https://dev.to/aws-builders/i-gave-my-ai-agent-a-real-browser-heres-what-actually-happened-4ipk</guid>
      <description>&lt;p&gt;👋 Hey there, Tech Enthusiasts!&lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect who loves turning complex tech problems into simple solutions. I've worked with AWS, Azure, DevOps, Data, Analytics, Generative-AI and Agentic-AI building real systems for real companies. In this article series, I'll share what I've learned in a way that's easy to follow, whether you're experienced or just getting started.&lt;/p&gt;

&lt;p&gt;Let's get into it! 🚀&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Your agent can write Terraform, deploy infrastructure, and debug pipelines. But ask it to check a dashboard behind Cloudflare? It's useless."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;If you read my previous article &lt;a href="https://dev.to/aws-builders/i-let-an-ai-agent-become-my-devops-engineer-529"&gt;I Let an AI Agent Become My DevOps Engineer&lt;/a&gt; you know I've been pushing AI agents into real operational workflows. Code generation, CI/CD pipelines, infrastructure provisioning agents handle all of that now.&lt;/p&gt;

&lt;p&gt;But there's one area where every agent I've used just... stops working. The browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the reality:&lt;/strong&gt; Most of the tools I monitor daily Grafana dashboards, AWS Console, CI pipelines, internal wikis live behind login walls, CAPTCHAs, and anti-bot protection. My agents can't touch them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's where &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; comes in.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I found this tool that claims to give AI agents real browser control not headless puppeteer scripts that get blocked instantly, but actual anti-detection browsing with CAPTCHA handling and human handoff built in.&lt;/p&gt;

&lt;p&gt;I spent a week testing it. Here's what I found.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;By the end of this article, you'll:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand why AI agents fail at real browser tasks&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; and run your first extraction&lt;/li&gt;
&lt;li&gt;See how anti-detection browsing actually works&lt;/li&gt;
&lt;li&gt;Test human handoff for 2FA/login scenarios&lt;/li&gt;
&lt;li&gt;Run parallel browser sessions without cross-contamination&lt;/li&gt;
&lt;li&gt;Turn a repeated workflow into a reusable Skill&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Time Required:&lt;/strong&gt; 30 minutes (15 min read + 15 min hands-on)&lt;br&gt;
&lt;strong&gt;Difficulty:&lt;/strong&gt; Intermediate&lt;br&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt; Python 3.12+, Node.js 18+, Google Chrome, terminal access&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem: Agents Can't Browse the Real Web
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Reality of AI Agent Automation in 2026
&lt;/h3&gt;

&lt;p&gt;I manage infrastructure across multiple AWS accounts. Every morning I check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudWatch dashboards&lt;/li&gt;
&lt;li&gt;GitHub notifications and PR reviews&lt;/li&gt;
&lt;li&gt;CI/CD pipeline status&lt;/li&gt;
&lt;li&gt;Internal monitoring tools&lt;/li&gt;
&lt;li&gt;Competitor product pages (for research)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's easily 30-40 minutes of tab-switching, scrolling, and context-gathering before I even start real work.&lt;/p&gt;

&lt;p&gt;I thought my agent can write Terraform and deploy entire VPCs. Surely it can open a webpage and read some data?&lt;/p&gt;

&lt;p&gt;Nope.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 1: Basic web fetch&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;curl https://monitoring-dashboard.internal.com
&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;403 Forbidden - Access Denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Attempt 2: Headless Puppeteer&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://dashboard.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Blocked by Cloudflare challenge&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;Error: Page stuck on verification challenge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Attempt 3: Selenium&lt;/strong&gt;&lt;br&gt;
Same story. Detected as bot within 2 seconds.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Anti-bot systems detect headless browsers instantly&lt;/li&gt;
&lt;li&gt;CAPTCHAs stop automation cold&lt;/li&gt;
&lt;li&gt;2FA/login flows need a human but there's no way to hand off&lt;/li&gt;
&lt;li&gt;Running multiple accounts in one browser gets all of them flagged&lt;/li&gt;
&lt;li&gt;Every script breaks when the site changes layout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My daily routine before &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open 6 tabs manually&lt;/li&gt;
&lt;li&gt;Log into GitHub, AWS Console, Grafana MFA for each&lt;/li&gt;
&lt;li&gt;Scroll through notifications, check pipeline status&lt;/li&gt;
&lt;li&gt;Copy-paste data into Slack for the team&lt;/li&gt;
&lt;li&gt;Repeat next morning&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;40 minutes. Every. Day.&lt;/p&gt;

&lt;p&gt;Sound familiar? I needed something built specifically for this a browser that agents can actually use on the real web.&lt;/p&gt;


&lt;h2&gt;
  
  
  What is &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt;?
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Simple Version
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; is a CLI that gives your AI agent a real Chrome browser with anti-detection, CAPTCHA solving, and human handoff built in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think of it like this:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Puppeteer/Playwright&lt;/strong&gt; = giving your agent a browser that screams "I'M A BOT"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt;&lt;/strong&gt; = giving your agent a browser that looks and behaves like a real person&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The Five Things It Actually Does
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gets past anti-bot walls:&lt;/strong&gt; Real fingerprints, proxy rotation, stealth browsing. Sites don't know it's automated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handles CAPTCHAs automatically:&lt;/strong&gt; Cloudflare, DataDome, reCAPTCHA. Solves what it can, escalates what it can't.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hands off to humans when stuck:&lt;/strong&gt; Hit a QR code login? SMS verification? It generates a URL. You (or a teammate) open it on your phone, do the human step, and the agent continues from where it stopped.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Runs parallel tasks without interference:&lt;/strong&gt; Three sessions checking three different things under the same account. They don't step on each other.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Turns workflows into reusable Skills:&lt;/strong&gt; Did something once? Package it. Run it again without the agent having to figure it out from scratch.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;Python 3.12+ (&lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct &lt;/a&gt;CLI is Python-based)&lt;/li&gt;
&lt;li&gt;Node.js 18+ (for &lt;code&gt;npx skills&lt;/code&gt; Skill installation)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uv&lt;/code&gt; package manager (or &lt;code&gt;pip&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Google Chrome installed&lt;/li&gt;
&lt;li&gt;Terminal/CLI access&lt;/li&gt;
&lt;li&gt;An AI agent that can run shell commands (Claude Code, Cursor, Kiro, Codex or just run commands yourself)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Quick Check
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# Python 3.12+&lt;/span&gt;

node &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# v18+&lt;/span&gt;

google-chrome &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# Google Chrome 149.x.x.x&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%2Faqjihrw33bn2z7qgyqh5.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%2Faqjihrw33bn2z7qgyqh5.png" alt=" " width="736" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don't have &lt;code&gt;uv&lt;/code&gt; (fast Python package manager):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh | sh
&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%2Flxw7szahj173py29zqoa.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%2Flxw7szahj173py29zqoa.png" alt=" " width="800" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don't have Chrome:&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;# Ubuntu/Debian&lt;/span&gt;
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
&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; ./google-chrome-stable_current_amd64.deb

&lt;span class="c"&gt;# Amazon Linux&lt;/span&gt;
wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum localinstall &lt;span class="nt"&gt;-y&lt;/span&gt; google-chrome-stable_current_x86_64.rpm
&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%2F1bwb4rs2eh4kczkcxeiy.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%2F1bwb4rs2eh4kczkcxeiy.png" alt=" " width="799" height="224"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;Here's what we're going to test today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────┐
│                 AI Agent (You/Claude/Cursor)    │
└─────────────────────┬───────────────────────────┘
                      │ CLI commands
                      ▼
┌─────────────────────────────────────────────────┐
│              BrowserAct CLI                     │
│  ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│  │ Stealth  │ │ Sessions │ │  Human Handoff   │ │
│  │ Browser  │ │ Manager  │ │  (remote-assist) │ │
│  └──────────┘ └──────────┘ └──────────────────┘ │
└─────────────────────┬───────────────────────────┘
                      │ Real Chrome
                      ▼
┌─────────────────────────────────────────────────┐
│  Protected Websites (Cloudflare, Login Walls)   │
└─────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 1: Install &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Two parts here the Skill definition (for AI agents) and the actual CLI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install the Skill (for agent integration):&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;npx skills add browser-act/skills &lt;span class="nt"&gt;--skill&lt;/span&gt; browser-act &lt;span class="nt"&gt;--yes&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%2F0ttzxwdc5wvq71pj8pwm.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%2F0ttzxwdc5wvq71pj8pwm.png" alt=" " width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;◇  Installation complete

│  ✓ ~/.agents/skills/browser-act
│    universal: Amp, Antigravity, Antigravity CLI, Cline, Codex +12 more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install the CLI itself:&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;uv tool &lt;span class="nb"&gt;install &lt;/span&gt;browser-act-cli &lt;span class="nt"&gt;--python&lt;/span&gt; 3.12
&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%2F3ililpofsj7wtf9i7amy.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%2F3ililpofsj7wtf9i7amy.png" alt=" " width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Resolved 90 packages in 437ms
Installed 90 packages in 2.09s
Installed 1 executable: browser-act
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verify it works (required on first install):&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;browser-act get-skills core &lt;span class="nt"&gt;--skill-version&lt;/span&gt; 2.0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns the environment state, available browsers, and command reference. Run this before anything else it completes the version handshake.&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%2Fxvcp1ssgkn4m6ok180r3.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%2Fxvcp1ssgkn4m6ok180r3.png" alt=" " width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Get Your &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct &lt;/a&gt;API Key
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; and sign in to your account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on your profile email address in the top-right corner.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;From the dropdown menu, select &lt;strong&gt;API Keys&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Manage Keys&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;strong&gt;Create Key&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter a name for your API key (for example, &lt;code&gt;Amazon-Q&lt;/code&gt;, &lt;code&gt;MCP-Server&lt;/code&gt;, or &lt;code&gt;Development&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the generated API key and store it securely. You may not be able to view the full key again after leaving the page.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Treat your API key like a password. Do not share it publicly or commit it to source code repositories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set your API key&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;browser-act auth &lt;span class="nb"&gt;set&lt;/span&gt; &amp;lt;your-api-key&amp;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%2F7bgow0rn50pbc7cc4gq4.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%2F7bgow0rn50pbc7cc4gq4.png" alt=" " width="796" height="80"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API key saved.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Your First Extraction (Zero Config)
&lt;/h2&gt;

&lt;p&gt;The simplest thing &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; can do extract content from a page without any setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act stealth-extract https://httpbin.org/ip
&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%2Fmllan8126qts9qv894sh.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%2Fmllan8126qts9qv894sh.png" alt=" " width="800" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Output from my test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100.54.212.44"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, rendered content. No HTML tags, no noise. Just the data.&lt;/p&gt;

&lt;p&gt;Let's try something with actual content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act stealth-extract https://example.com
&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%2Faaxzyblr372608sjr3fl.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%2Faaxzyblr372608sjr3fl.png" alt=" " width="798" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Example Domain&lt;/span&gt;
This domain is for use in documentation examples without needing permission. Avoid use in operations.
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Learn more&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://iana.org/domains/example&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Already in markdown format. My agent can read this directly without any parsing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What &lt;code&gt;stealth-extract&lt;/code&gt; does under the hood:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spins up a lightweight stealth browser&lt;/li&gt;
&lt;li&gt;Visits the URL with anti-detection fingerprint&lt;/li&gt;
&lt;li&gt;Renders JavaScript (unlike curl)&lt;/li&gt;
&lt;li&gt;Returns content in markdown&lt;/li&gt;
&lt;li&gt;Tears down the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important note from testing:&lt;/strong&gt; &lt;code&gt;stealth-extract&lt;/code&gt; works great for quick reads on most sites. For heavily protected sites (like nowsecure.nl with aggressive Cloudflare), you'll need a full browser session (Step 3) it has stronger anti-detection because it maintains a persistent fingerprint and proxy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Full Browser Control
&lt;/h2&gt;

&lt;p&gt;Now let's do something more interesting interactive browser automation. First, you need a stealth browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act browser create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"test-stealth"&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; stealth &lt;span class="nt"&gt;--desc&lt;/span&gt; &lt;span class="s2"&gt;"Testing for article"&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%2F0vlnrq8msg6wpt23trh6.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%2F0vlnrq8msg6wpt23trh6.png" alt=" " width="795" height="76"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;99703194156616493 name="test-stealth" type=stealth&lt;/span&gt;
  &lt;span class="py"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Testing for article"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open a session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; my-research browser open 99703194156616493 https://github.com/trending
&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%2Fisi23ocl53hwbohvqaph.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%2Fisi23ocl53hwbohvqaph.png" alt=" " width="799" height="74"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;session_name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;my-research&lt;/span&gt;
&lt;span class="py"&gt;browser_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;stealth&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://github.com/trending&lt;/span&gt;
&lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;github.com/trending&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;See what's on the page (indexed elements):&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;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; my-research state
&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%2Fizavir6p4ckb4awfveqb.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%2Fizavir6p4ckb4awfveqb.png" alt=" " width="799" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Real output from my test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;url=https://github.com/trending
title=Trending repositories on GitHub today · GitHub

|SCROLL|&amp;lt;html class=js-focus-visible /&amp;gt; (0.0 pages above, 1.2 pages below)

  [1]&amp;lt;a aria-label=Homepage /&amp;gt;
  [3]&amp;lt;button type=button aria-expanded=false /&amp;gt;
      Platform
  [4]&amp;lt;button type=button aria-expanded=false /&amp;gt;
      Solutions
  [8]&amp;lt;a class=NavLink-module__link__EG3d4 /&amp;gt;
      Pricing
  [9]&amp;lt;qbsearch-input class=search-input /&amp;gt;
      [10]&amp;lt;div class=search-input-container search-with-dialog /&amp;gt;
  [12]&amp;lt;a /&amp;gt;
      Sign in
  [15]&amp;lt;a class=js-selected-navigation-item /&amp;gt;
      Explore
  [17]&amp;lt;a class=js-selected-navigation-item selected /&amp;gt;
      Trending
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See those numbers? That's how the agent interacts. No DOM parsing, no CSS selectors. Just:&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;# Click the search input (element 10)&lt;/span&gt;
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; my-research click 10
&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%2Fkpksstby31qiqlye2o4k.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%2Fkpksstby31qiqlye2o4k.png" alt=" " width="800" height="64"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;clicked&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The page updates search box opens. Now type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; my-research input 10 &lt;span class="s2"&gt;"browser automation AI"&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%2Fhttwtswt8qawbxi8lp5b.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%2Fhttwtswt8qawbxi8lp5b.png" alt=" " width="794" height="49"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;input&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"browser automation AI"&lt;/span&gt; &lt;span class="s"&gt;element=10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This is what they mean by "designed for agent reasoning."&lt;/strong&gt; The output is compact, indexed, and token-efficient. My agent doesn't waste tokens parsing HTML.&lt;/p&gt;

&lt;p&gt;You can also grab the full page as markdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; my-research get markdown
&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%2Fzn38qfhuc67cdm5eephl.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%2Fzn38qfhuc67cdm5eephl.png" alt=" " width="799" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Returns the entire page content in clean markdown format. Useful when you want to extract data rather than interact.&lt;/p&gt;

&lt;p&gt;When you're done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act session close my-research
&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%2Fc3n9lotoka0oq5eil7mm.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%2Fc3n9lotoka0oq5eil7mm.png" alt=" " width="800" height="73"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;session_name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;my-research closed=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Anti-Bot in Action
&lt;/h2&gt;

&lt;p&gt;Here's where it gets real. I pointed &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; at &lt;a href="https://nowsecure.nl" rel="noopener noreferrer"&gt;nowsecure.nl&lt;/a&gt; a site specifically designed to test anti-bot detection. It runs Cloudflare challenges.&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;# Full browser session on a Cloudflare-protected site&lt;/span&gt;
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; antibot browser open 99703194156616493 https://nowsecure.nl
&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%2F6qyme4vwia38pnlz2c2i.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%2F6qyme4vwia38pnlz2c2i.png" alt=" " width="796" height="82"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;session_name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;antibot&lt;/span&gt;
&lt;span class="py"&gt;browser_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;stealth&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://nowsecure.nl/&lt;/span&gt;
&lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;nowsecure.nl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It got through. Let me verify by pulling the content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; antibot get markdown
&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%2Fc0wfkqm2n15yndmb7een.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%2Fc0wfkqm2n15yndmb7een.png" alt=" " width="799" height="187"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nowsecure.nl
NOWSECURE
---------
### by nodriver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And checking the network traffic shows exactly what happened Cloudflare's turnstile challenge was handled automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; antibot network requests
&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%2Fh8axz4tawr4llz9nzxiz.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%2Fh8axz4tawr4llz9nzxiz.png" alt=" " width="800" height="207"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# format: csv
...
GET,200,Script,application/javascript,...,https://challenges.cloudflare.com/turnstile/v0/g/8fc8ed1d8752/api.js
GET,200,Document,text/html,...,https://challenges.cloudflare.com/cdn-cgi/challenge-platform/h/g/turnstile/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stealth browser handled the Cloudflare verification without any manual intervention. No CAPTCHA prompt, no block.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; uses three layers to get through:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Environment layer&lt;/strong&gt; - Stealth fingerprint, TLS config, proxy switching. Most blocks never trigger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution layer&lt;/strong&gt; - If a CAPTCHA appears, &lt;code&gt;solve-captcha&lt;/code&gt; handles it automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human layer&lt;/strong&gt; - If auto-solve fails, it can hand off to you (more on this next).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'm not going to claim it works on every site. It doesn't. No tool does. But for the monitoring dashboards and research pages I tested, it got through where Puppeteer and Selenium couldn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Human Handoff (This Is the Killer Feature)
&lt;/h2&gt;

&lt;p&gt;This is the part that sold me. Here's the scenario:&lt;/p&gt;

&lt;p&gt;Your agent is automating a workflow. It hits a login page that requires SMS verification or QR code scan. In Puppeteer world, the automation just dies. Game over.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; does something different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; login-task remote-assist &lt;span class="nt"&gt;--objective&lt;/span&gt; &lt;span class="s2"&gt;"complete 2FA verification"&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%2Fkfg46qg6x5yb5az6zd0z.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%2Fkfg46qg6x5yb5az6zd0z.png" alt=" " width="800" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Real output from my test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Remote assist session created.

Share this URL with the user:
  https://www.browseract.com/remote-cli/d83544c39e4a4e6ba1cc98f95050e615
expires in 1h 0m

Human assist is now active - the browser is under user control.
Do not send browser commands until the user finishes the assist session.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You open that URL on your phone or another device. You see the browser the actual live browser state. You complete the SMS verification, scan the QR code, whatever. Then you close it.&lt;/p&gt;

&lt;p&gt;The agent gets notified and continues from the exact same browser state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters for DevOps:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal tools with SSO that requires periodic re-auth&lt;/li&gt;
&lt;li&gt;AWS Console with MFA&lt;/li&gt;
&lt;li&gt;Third-party dashboards with 2FA&lt;/li&gt;
&lt;li&gt;Any workflow where "fully automated" isn't realistic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent doesn't crash. It doesn't restart. It waits, you help, it continues. That's practical automation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Parallel Sessions (Multi-Task Without Conflicts)
&lt;/h2&gt;

&lt;p&gt;Here's a real use case from my work: I want an agent to check three things simultaneously under the same account.&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;# Session 1: Check GitHub trending&lt;/span&gt;
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; check-trending browser open 99703194156616493 https://github.com/trending
&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%2Feg2jz7d6zl90hr6dzg02.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%2Feg2jz7d6zl90hr6dzg02.png" alt=" " width="800" height="72"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;session_name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;check-trending&lt;/span&gt;
&lt;span class="py"&gt;browser_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;stealth&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://github.com/trending&lt;/span&gt;
&lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;github.com/trending&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;&lt;span class="c"&gt;# Session 2: Check GitHub topics (same browser, parallel session)&lt;/span&gt;
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; check-topics browser open 99703194156616493 https://github.com/topics
&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%2Fbqoil1hcav9grkfslh0a.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%2Fbqoil1hcav9grkfslh0a.png" alt=" " width="797" height="78"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;session_name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;check-topics&lt;/span&gt;
&lt;span class="py"&gt;browser_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;stealth&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://github.com/topics&lt;/span&gt;
&lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;github.com/topics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two sessions. One browser. They don't interfere with each other.&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;# See all active sessions&lt;/span&gt;
browser-act session list
&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%2Frbezsr58mt43evgomy00.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%2Frbezsr58mt43evgomy00.png" alt=" " width="800" height="472"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;session_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-trending&lt;/span&gt;
&lt;span class="na"&gt;browser_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stealth&lt;/span&gt;
&lt;span class="na"&gt;browser_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;99703194156616493&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Trending repositories on GitHub today · GitHub&lt;/span&gt;
&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/trending&lt;/span&gt;

&lt;span class="na"&gt;session_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-topics&lt;/span&gt;
&lt;span class="na"&gt;browser_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stealth&lt;/span&gt;
&lt;span class="na"&gt;browser_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;99703194156616493&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Topics on GitHub · GitHub&lt;/span&gt;
&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/topics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each session has its own navigation state but shares the login cookies. So you log in once, and all tasks can work in parallel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The three concurrency models:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;What's shared&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cross-browser parallel&lt;/td&gt;
&lt;td&gt;Nothing (independent identity)&lt;/td&gt;
&lt;td&gt;Multi-account monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same-browser multi-session&lt;/td&gt;
&lt;td&gt;Login state&lt;/td&gt;
&lt;td&gt;Parallel tasks, one account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privacy mode&lt;/td&gt;
&lt;td&gt;Nothing (fresh each time)&lt;/td&gt;
&lt;td&gt;One-off scraping, anonymity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act session close check-trending
browser-act session close check-topics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;session_name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;check-trending closed=true&lt;/span&gt;
&lt;span class="py"&gt;session_name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;check-topics closed=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sessions auto-reclaim after 8 hours if you forget. But clean up after yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Skill Forge Make It Reusable
&lt;/h2&gt;

&lt;p&gt;Let's say Step 6 worked great. I want to run that "check my GitHub morning routine" every day without the agent figuring it out from scratch each time.&lt;/p&gt;

&lt;p&gt;First, install Skill Forge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add browser-act/skills &lt;span class="nt"&gt;--skill&lt;/span&gt; browser-act-skill-forge &lt;span class="nt"&gt;--yes&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%2Fbznezwtyppiziksx2vz8.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%2Fbznezwtyppiziksx2vz8.png" alt=" " width="799" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;◇  Installation complete

│  ✓ ~/.agents/skills/browser-act-skill-forge
│    universal: Amp, Antigravity, Antigravity CLI, Cline, Codex +12 more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then tell your agent:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Forge a Skill that checks GitHub notifications, extracts unread count and top 5 notification titles."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Skill Forge will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Explore the page structure&lt;/li&gt;
&lt;li&gt;Discover the best extraction path&lt;/li&gt;
&lt;li&gt;Generate a reusable Skill file&lt;/li&gt;
&lt;li&gt;Test it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next time, the agent just runs the Skill. No re-exploration, no token waste. Same stable path every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where I'd use this:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daily dashboard checks&lt;/li&gt;
&lt;li&gt;Competitor price monitoring&lt;/li&gt;
&lt;li&gt;Pull request summary generation&lt;/li&gt;
&lt;li&gt;CI/CD status aggregation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where I wouldn't:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sites that change layout constantly&lt;/li&gt;
&lt;li&gt;One-off tasks that'll never repeat&lt;/li&gt;
&lt;li&gt;Anything that needs real-time interaction (chat, live support)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Bonus: How I'd Use This in Production
&lt;/h2&gt;

&lt;p&gt;I ran a few extra tests to see how &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; fits into a real cloud operations workflow. Here's what I found.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring AWS Health Dashboard
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; aws-health browser open 99703194156616493 https://status.aws.amazon.com
&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%2F7obglp5sl5apgowyoosg.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%2F7obglp5sl5apgowyoosg.png" alt=" " width="798" height="78"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;session_name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;aws-health&lt;/span&gt;
&lt;span class="py"&gt;browser_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;stealth&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://health.aws.amazon.com/health/status&lt;/span&gt;
&lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;health.aws.amazon.com/health/status&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then extract the status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; aws-health get markdown
&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%2F0mw1y66m7ejch6yr0o7a.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%2F0mw1y66m7ejch6yr0o7a.png" alt=" " width="799" height="202"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS Health Dashboard
====================
Service health - Jun 09, 2026
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take a screenshot for your Slack channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; aws-health screenshot /tmp/aws-health.png
&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%2Fwq55up7o324eqdluan0q.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%2Fwq55up7o324eqdluan0q.png" alt=" " width="793" height="50"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;saved: /tmp/aws-health.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real screenshot, 315KB, exactly what the dashboard looks like. Ship that to a Slack webhook and your team gets a visual status check every morning.&lt;/p&gt;




&lt;h3&gt;
  
  
  Checking GitHub Status (CI Monitoring)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; aws-health navigate https://www.githubstatus.com
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; aws-health get markdown
&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%2F60v02b7zxwjhwvlsu29o.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%2F60v02b7zxwjhwvlsu29o.png" alt=" " width="800" height="252"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;All Systems Operational
-----------------------
Git Operations   99.85% uptime  Normal
Webhooks         99.96% uptime  Normal
API Requests     99.99% uptime  Normal
Issues           99.97% uptime  Normal
Pull Requests    99.61% uptime  Normal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now my agent has real uptime data it can act on. If Pull Requests drops below 99%, alert me.&lt;/p&gt;




&lt;h3&gt;
  
  
  Browsing Cloudflare's Own Product Page (Their Anti-Bot)
&lt;/h3&gt;

&lt;p&gt;The ultimate test can &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; get through Cloudflare's own website?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; aws-health navigate https://www.cloudflare.com/products/
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; aws-health get markdown
&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%2Ftuxjqzrl0jwu53fdljw2.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%2Ftuxjqzrl0jwu53fdljw2.png" alt=" " width="799" height="216"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cloudflare Products
-------------------
Everything you need to build, deploy, and scale applications...
Workers Global serverless functions
D1 - Serverless SQL database
R2 - Object storage...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes. It extracted their full product catalog through their own protection. That's something.&lt;/p&gt;




&lt;h3&gt;
  
  
  JavaScript Evaluation (Custom Data Extraction)
&lt;/h3&gt;

&lt;p&gt;Need something specific that the markdown extraction doesn't give you? Run JavaScript directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; aws-health &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"document.title"&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%2Ftcdp7h3sk4jcbhctwbsb.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%2Ftcdp7h3sk4jcbhctwbsb.png" alt=" " width="795" height="75"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Products | Cloudflare
&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;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; aws-health &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"document.querySelectorAll('a').length"&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%2Fsgy9xs5cpn0nm2slyk4z.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%2Fsgy9xs5cpn0nm2slyk4z.png" alt=" " width="800" height="30"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This lets you write precise extraction logic without relying on the markdown parser.&lt;/p&gt;




&lt;h3&gt;
  
  
  My Production Architecture (What I'd Actually Build)
&lt;/h3&gt;

&lt;p&gt;Here's the setup I'm planning for my AWS monitoring workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────────┐
│                    Cron Job (Every Morning 8 AM)                │
└──────────────────────────┬─────────────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────────────┐
│                   AI Agent (Kiro / Claude Code)                 │
│                                                                │
│  1. browser-act --session grafana browser open &amp;lt;id&amp;gt; &amp;lt;url&amp;gt;      │
│  2. browser-act --session grafana get markdown                 │
│  3. browser-act --session grafana screenshot ./grafana.png     │
│  4. browser-act --session cloudwatch browser open &amp;lt;id&amp;gt; &amp;lt;url&amp;gt;   │
│  5. browser-act --session cloudwatch get markdown              │
│  6. Parse data → Generate summary                             │
│  7. Post to Slack with screenshots                            │
│  8. Close all sessions                                         │
└──────────────────────────┬─────────────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────────────┐
│                 Slack Channel: #morning-status                  │
│                                                                │
│  Morning Infra Report - Jun 04, 2026                           │
│  All AWS services operational                                  │
│  GitHub: 99.85% uptime                                         │
│  Grafana: CPU alert on prod-api-3                              │
│  [dashboard-screenshot.png]                                    │
└────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works better than API-only monitoring:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some dashboards don't expose APIs (internal Grafana, vendor portals)&lt;/li&gt;
&lt;li&gt;Screenshots give visual context that JSON data can't&lt;/li&gt;
&lt;li&gt;Human handoff means SSO re-auth doesn't break the whole pipeline&lt;/li&gt;
&lt;li&gt;One browser session stays logged in no re-auth every day&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Run on a dedicated EC2 instance (t3.medium is enough tested)&lt;/li&gt;
&lt;li&gt;Use static proxy for stable identity (avoids triggering "new device" alerts)&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;--desc&lt;/code&gt; on browsers with site/account info so future sessions know what's what&lt;/li&gt;
&lt;li&gt;Monitor credit usage each session costs credits, budget accordingly&lt;/li&gt;
&lt;li&gt;Store screenshots in S3, link in Slack messages&lt;/li&gt;
&lt;li&gt;If login expires, &lt;code&gt;remote-assist&lt;/code&gt; lets you re-auth from your phone without SSH-ing in&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Before vs After: How This Changed My Morning
&lt;/h2&gt;

&lt;p&gt;Let me be real about what my daily routine looked like before and after testing &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; (My Old Morning)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8:00 AM - Open laptop
8:02 AM - Open GitHub, hit MFA prompt on phone, wait
8:04 AM - Check notifications (12 unread), scan PRs (3 need review)
8:08 AM - Open AWS Console, MFA again, navigate to CloudWatch
8:12 AM - Check 3 dashboards across 2 accounts, screenshot for team
8:18 AM - Open Grafana, login, scroll through panels looking for alerts
8:23 AM - Check GitHub Actions 2 repos have failing builds
8:27 AM - Open competitor's changelog page (Cloudflare protected)
8:30 AM - Copy-paste a summary into Slack for the team
8:35 AM - Realize I missed one account, go back to AWS...
8:40 AM - Actually start working
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;40 minutes of monkey work. Every morning. On good days.&lt;/p&gt;

&lt;p&gt;On bad days a session expires mid-check, or MFA doesn't arrive, or Cloudflare blocks my automation script. Then it's 50+ minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  After &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; (What I'm Building Now)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8:00 AM - Agent runs automatically (cron trigger)

Agent:
  → Opens stealth session on GitHub
  → Extracts: 12 notifications, 3 PRs pending review
  → Navigates to GitHub Actions: 2 failing builds (api-server, docs-deploy)
  → Opens parallel session: AWS Health Dashboard
  → Extracts: "All Systems Operational"
  → Screenshots CloudWatch dashboard → saves to S3
  → Navigates to competitor changelog: extracts new features list
  → Posts summary to Slack #morning-status

8:01 AM - Slack notification pops up with the full morning report.

8:02 AM - I read the summary with my coffee. Actually start working.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Time saved: ~38 minutes/day. That's 3+ hours/week.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the thing is it's not just the time. It's the mental context switching. Opening six different login-protected dashboards pulls you out of focus. Having a one-page summary waiting in Slack means I start my day knowing exactly what needs attention.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Honest Part
&lt;/h3&gt;

&lt;p&gt;It's not fully there yet. Here's my current reality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What works today:&lt;/strong&gt; The extraction, screenshots, and parallel sessions all work as shown in this article. I tested every command above on a real EC2 instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What I'm still setting up:&lt;/strong&gt; The cron automation + Slack integration pipeline. That's a week of wiring. I'll write a follow-up when it's running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The one catch:&lt;/strong&gt; First login to each site needs me to &lt;code&gt;remote-assist&lt;/code&gt; from my phone (handle MFA manually). After that, the session stays authenticated for days. So it's "almost" fully automated I handle MFA once a week, the agent handles the other 4 days.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Where I Wouldn't Use This
&lt;/h3&gt;

&lt;p&gt;Being honest not everything needs browser automation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS resource monitoring&lt;/strong&gt; - Use CloudWatch alarms + SNS. APIs exist. Don't browser-scrape what you can API-call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple uptime checks&lt;/strong&gt; - Use UptimeRobot or similar. Don't overkill it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data that changes every second&lt;/strong&gt; - &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; isn't real-time. It's periodic checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Banking or highly sensitive portals&lt;/strong&gt; - Too risky. Keep that manual.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where It Shines
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dashboards without APIs&lt;/strong&gt; - Grafana (free tier), internal tools, vendor portals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-account visual checks&lt;/strong&gt; - AWS Console across 3 accounts, screenshot each&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitor monitoring&lt;/strong&gt; - Product pages behind Cloudflare, pricing pages that block scrapers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD status aggregation&lt;/strong&gt; - Pull data from GitHub Actions, CircleCI, Jenkins (web UI) into one summary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any "check and report" workflow&lt;/strong&gt; that you do manually more than twice a week&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What Worked and What Didn't
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Worked Well
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stealth browser sessions&lt;/strong&gt; - get through Cloudflare where Puppeteer/Selenium can't. Tested on nowsecure.nl and cloudflare.com itself both passed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real dashboard extraction&lt;/strong&gt; - pulled structured data from AWS Health Dashboard, GitHub Status, and Cloudflare product pages. Real production use cases, all worked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screenshots&lt;/strong&gt; - &lt;code&gt;screenshot&lt;/code&gt; command saves PNGs directly. 315KB for a full-page capture. Great for visual monitoring and Slack reports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indexed interaction&lt;/strong&gt; - the state/click/input model is clean. Way better than parsing DOM. Real element indices, real clicks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human handoff&lt;/strong&gt; - generates a live URL instantly. This solves a real problem I've had for years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session isolation&lt;/strong&gt; - ran two parallel sessions on the same browser, completely independent. No conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation within sessions&lt;/strong&gt; - &lt;code&gt;navigate&lt;/code&gt; command lets you move across sites within one session. Open AWS Health, then navigate to GitHub Status, same session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript eval&lt;/strong&gt; - &lt;code&gt;eval&lt;/code&gt; lets you run custom extraction when markdown isn't precise enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network capture&lt;/strong&gt; - &lt;code&gt;network requests&lt;/code&gt; shows exactly what's happening under the hood. Great for debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install was clean&lt;/strong&gt; - &lt;code&gt;uv tool install&lt;/code&gt; pulled 90 packages, had it running in under a minute on a t3.medium.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Mixed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;stealth-extract&lt;/code&gt; vs full sessions&lt;/strong&gt; - &lt;code&gt;stealth-extract&lt;/code&gt; works for quick reads on normal sites, but for heavily protected sites you need a full stealth browser session. Not obvious from the docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CAPTCHA solving&lt;/strong&gt; - &lt;code&gt;solve-captcha&lt;/code&gt; returned "no compatible captcha found" when I tested (page had already loaded past it). Couldn't trigger a scenario where it was needed during my testing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt; - stealth browsing is slower than raw Puppeteer. Takes a few seconds to open sessions. Makes sense (it's doing more work) but worth noting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Could Be Better
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt; - the &lt;code&gt;get-skills&lt;/code&gt; command dumps a massive guide. Useful but overwhelming on first read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error messages&lt;/strong&gt; - "Browser launch failed: Connection closed" doesn't tell you much. Took trial and error to figure out I needed a full session for protected sites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requires API key for stealth&lt;/strong&gt; - can't use the anti-detection features without signing up first. Fair, but the free tier is limited.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Pricing &amp;amp; Credits
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; runs on a credit system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free credits:&lt;/strong&gt; 100 credits on signup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paid plans:&lt;/strong&gt; Check &lt;a href="https://www.browseract.com/pricing" rel="noopener noreferrer"&gt;browseract.com/pricing&lt;/a&gt; for current rates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For testing and writing this article, the free credits were enough. For production monitoring workflows running daily, you'd need a paid plan.&lt;/p&gt;




&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue 1: "browser-act: command not found"
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash: browser-act: &lt;span class="nb"&gt;command &lt;/span&gt;not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Install via uv (not npm):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh | sh
uv tool &lt;span class="nb"&gt;install &lt;/span&gt;browser-act-cli &lt;span class="nt"&gt;--python&lt;/span&gt; 3.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The binary installs to &lt;code&gt;~/.local/bin/&lt;/code&gt; make sure it's in your PATH.&lt;/p&gt;




&lt;h3&gt;
  
  
  Issue 2: "Browser launch failed: Connection closed"
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Browser launch failed: Browser.close: Connection closed while reading from the driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; This happens when &lt;code&gt;stealth-extract&lt;/code&gt; can't handle a heavily protected site. Use a full browser session instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act browser create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"my-stealth"&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; stealth &lt;span class="nt"&gt;--desc&lt;/span&gt; &lt;span class="s2"&gt;"browsing"&lt;/span&gt;
browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; task1 browser open &amp;lt;browser-id&amp;gt; &amp;lt;url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Issue 3: "api_key: not configured"
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLI:
  api_key: not configured
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; You need an API key for stealth browser features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act auth &lt;span class="nb"&gt;set&lt;/span&gt; &amp;lt;your-api-key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get one at &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;browseract.com&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Issue 4: Chrome not found
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Chrome executable not found&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Install Chrome:&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;# Ubuntu&lt;/span&gt;
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
&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; ./google-chrome-stable_current_amd64.deb

&lt;span class="c"&gt;# Amazon Linux&lt;/span&gt;
wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum localinstall &lt;span class="nt"&gt;-y&lt;/span&gt; google-chrome-stable_current_x86_64.rpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Issue 5: Session already exists
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Session name 'my-task' already in use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Close it first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act session close my-task
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;As a cloud architect, here's where I see this fitting:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Infrastructure Monitoring&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check multiple dashboards (Grafana, CloudWatch, Datadog) through a single agent&lt;/li&gt;
&lt;li&gt;Generate daily summaries from web-based monitoring tools&lt;/li&gt;
&lt;li&gt;Alert on visual changes in dashboards that don't have API access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. DevOps Workflows&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automate PR review summaries across repos&lt;/li&gt;
&lt;li&gt;Check CI/CD status across multiple platforms&lt;/li&gt;
&lt;li&gt;Monitor deployment pipelines with login-protected UIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Multi-Account Operations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manage multiple AWS accounts through Console (when CLI isn't enough)&lt;/li&gt;
&lt;li&gt;Monitor multiple SaaS dashboards&lt;/li&gt;
&lt;li&gt;Cross-account compliance checks on web portals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Research &amp;amp; Data Collection&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track competitor features and pricing pages&lt;/li&gt;
&lt;li&gt;Aggregate release notes from multiple vendor sites&lt;/li&gt;
&lt;li&gt;Collect public data from protected listing pages&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Concepts Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Sessions = Task Workspaces
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;browser-act &lt;span class="nt"&gt;--session&lt;/span&gt; &amp;lt;name&amp;gt; &amp;lt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every task gets its own session. Sessions don't interfere. Name them descriptively.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Browsers = Identities
&lt;/h3&gt;

&lt;p&gt;Different browsers = different fingerprints, proxies, cookies. Use separate browsers for separate accounts. Three types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;chrome&lt;/strong&gt; - imports your local Chrome login state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chrome-direct&lt;/strong&gt; - controls your running Chrome directly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;stealth&lt;/strong&gt; - anti-detection browser with fingerprint masking (needs API key)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Skills = Reusable Workflows
&lt;/h3&gt;

&lt;p&gt;Once something works, package it as a Skill. Next time it runs without re-exploration.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Three-Layer Anti-Blocking
&lt;/h3&gt;

&lt;p&gt;Environment (fingerprint) → Execution (auto-solve) → Human (handoff). Progressive escalation.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Two Extraction Modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;stealth-extract&lt;/code&gt;&lt;/strong&gt; - quick, zero-config, good for simple reads. Lightweight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full browser session&lt;/strong&gt; - persistent, stronger anti-detection, needed for heavily protected sites.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;I'm building the full pipeline I described above. Here's my roadmap:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 1 (done):&lt;/strong&gt; Test &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; on real sites this article&lt;br&gt;
&lt;strong&gt;Week 2:&lt;/strong&gt; Wire up the morning monitoring agent (cron + BrowserAct + Slack webhook)&lt;br&gt;
&lt;strong&gt;Week 3:&lt;/strong&gt; Add Skill Forge package the workflow so it's stable across page layout changes&lt;br&gt;
&lt;strong&gt;Week 4:&lt;/strong&gt; Run for 30 days, track: time saved, credits consumed, failures, manual interventions&lt;/p&gt;

&lt;p&gt;If there's interest, I'll write a follow-up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actual production usage data (cost per month, reliability %)&lt;/li&gt;
&lt;li&gt;The Skill file I built for GitHub monitoring&lt;/li&gt;
&lt;li&gt;How many times &lt;code&gt;remote-assist&lt;/code&gt; saved me from a broken pipeline&lt;/li&gt;
&lt;li&gt;Token usage comparison vs doing the same thing with raw Playwright&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple: I want my mornings back. 40 minutes of tab-switching replaced by a 2-minute Slack read. &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; is the missing piece between "my agent can code" and "my agent can actually see what's happening in production."&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/browser-act/skills/tree/main/browser-act" rel="noopener noreferrer"&gt;GitHub browser-act Skill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/browser-act/skills/tree/main/browser-act-skill-forge" rel="noopener noreferrer"&gt;GitHub Skill Forge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/browser-act/skills/tree/main/docs" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/browser-act/skills/blob/main/docs/concurrency.md" rel="noopener noreferrer"&gt;Concurrency &amp;amp; Isolation Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My Previous Article:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/i-let-an-ai-agent-become-my-devops-engineer-529"&gt;I Let an AI Agent Become My DevOps Engineer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Here's what I found after testing &lt;a href="https://browseract.com?fpr=sarvar04" rel="noopener noreferrer"&gt;BrowserAct&lt;/a&gt; on an EC2 instance (Amazon Linux, t3.medium):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stealth browser sessions get through Cloudflare tested on nowsecure.nl and cloudflare.com itself&lt;/li&gt;
&lt;li&gt;Extracted real data from AWS Health Dashboard, GitHub Status  actual production monitoring working&lt;/li&gt;
&lt;li&gt;Screenshots save as PNG ready to pipe to Slack/S3 for visual dashboards&lt;/li&gt;
&lt;li&gt;Human handoff generates a live remote URL in seconds actually works&lt;/li&gt;
&lt;li&gt;Parallel sessions run independently on the same browser no conflicts&lt;/li&gt;
&lt;li&gt;Navigation + eval let you build complex multi-step extraction workflows&lt;/li&gt;
&lt;li&gt;Indexed interaction (state, click, input) is agent-friendly and token-efficient&lt;/li&gt;
&lt;li&gt;Skill Forge makes repeated workflows reusable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stealth-extract&lt;/code&gt; has limits use full sessions for protected sites&lt;/li&gt;
&lt;li&gt;Needs API key for anti-detection features&lt;/li&gt;
&lt;li&gt;Error messages could be clearer when things fail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; If you're running AI agents and they need to interact with real websites not just APIs this is worth testing. I'm already planning a daily monitoring pipeline with it. It's not magic. It won't bypass everything. But it solves real problems that I haven't seen other tools handle this cleanly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connect &amp;amp; Share
&lt;/h2&gt;

&lt;p&gt;If this was useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Star the &lt;a href="https://github.com/browser-act/skills" rel="noopener noreferrer"&gt;BrowserAct GitHub repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drop a comment what workflows would you automate?&lt;/li&gt;
&lt;li&gt;Share with your team&lt;/li&gt;
&lt;li&gt;Follow me for the follow-up article&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Thanks for reading! If this was helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value&lt;/li&gt;
&lt;li&gt;💾 Save for later&lt;/li&gt;
&lt;li&gt;🔄 Share with your team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow me for more on:&lt;/strong&gt; AWS architecture, FinOps, DevOps, and AI Infrastructure.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit my website&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;Connect on LinkedIn&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Learning&lt;/strong&gt; 🚀&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>ai</category>
      <category>automation</category>
      <category>agents</category>
    </item>
    <item>
      <title>I Built an AI Agent That Tailors My Resume - Here's How Agents Actually Work</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Mon, 25 May 2026 13:32:31 +0000</pubDate>
      <link>https://dev.to/aws-builders/i-built-an-ai-agent-that-tailors-my-resume-heres-how-agents-actually-work-5733</link>
      <guid>https://dev.to/aws-builders/i-built-an-ai-agent-that-tailors-my-resume-heres-how-agents-actually-work-5733</guid>
      <description>&lt;p&gt;👋 Hey there, tech enthusiasts!&lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect who loves turning complex tech problems into simple solutions. I've worked with AWS, Azure, DevOps, Data, Analytics, Generative-AI and Agentic-AI building real systems for real companies. In this article series, I'll share what I've learned in a way that's easy to follow, whether you're experienced or just getting started.&lt;/p&gt;

&lt;p&gt;Let's get into it! 🚀&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is for beginners who've heard about AI agents but haven't built one yet.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem That Started It All
&lt;/h2&gt;

&lt;p&gt;You're looking for a job. You open LinkedIn, Naukri, Indeed search "Senior Python Developer." Hundreds of results pop up. Every company wants something slightly different. One wants AWS + Django. Another wants FastAPI + Kubernetes. The third one wants Flask + microservices.&lt;/p&gt;

&lt;p&gt;You have all these skills. That's not the problem.&lt;/p&gt;

&lt;p&gt;The problem is time. To stand out, you need to customize your resume for each role. Read the job description carefully. Figure out what they care about. Rewrite your summary. Reorder skills. Tweak bullet points. Write a cover letter. Hit submit.&lt;/p&gt;

&lt;p&gt;That's ONE application. Now do that 10 more times.&lt;/p&gt;

&lt;p&gt;It's not a skill problem. It's a time problem. You're doing the same thing over and over read, match, rewrite, submit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if you could build something that handles all of this for you?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You give it your resume and say "Find Senior Python roles in Bangalore, 20-35 LPA (Lakhs Per Annum ₹20-35 lakh salary), remote-friendly" and it goes to work. It reads job descriptions, matches your skills, finds gaps, rewrites your resume for each role, writes cover letters, and ranks everything by how well you fit.&lt;/p&gt;

&lt;p&gt;Same you. Same resume. Same job market. But the boring repetitive part? Done automatically.&lt;/p&gt;

&lt;p&gt;That's what got me into agentic AI. Not the hype. The simple thought: "I can automate this annoying thing."&lt;/p&gt;




&lt;h2&gt;
  
  
  ChatGPT vs AI Agents What's the Difference?
&lt;/h2&gt;

&lt;p&gt;People ask me this all the time. "Isn't ChatGPT already an agent?"&lt;/p&gt;

&lt;p&gt;Not really. Here's the simple difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt; - you ask a question, you get text back. Yes, it can browse the web and run code now. But YOU are still driving. You ask, it responds. You ask again, it responds again. It waits for you at every step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An AI Agent&lt;/strong&gt; - you give it a goal and it goes and does the work. Multiple steps. Multiple tools. Without you guiding each step. It decides what to do next on its own.&lt;/p&gt;

&lt;p&gt;Here's a simple comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You ask: "Find me Senior Python jobs in Bangalore"&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Here are some tips: update your LinkedIn, use relevant keywords..." (gives advice, waits for your next question)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI Agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Actually searches job boards, finds 47 matches, filters to 12 relevant ones, tailors your resume for each (does the work start to finish)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;One waits for you. The other goes and does the work.&lt;/strong&gt; That's the whole difference.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Agents Think?
&lt;/h2&gt;

&lt;p&gt;Every agent follows the same basic pattern:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think → Do → Check → Repeat&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's what that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────┐     ┌─────────┐     ┌─────────┐
│  THINK  │───▶│   DO    │────▶│  CHECK  │
│ What's  │     │ Take    │     │ Did it  │
│ next?   │     │ action  │     │ work?   │
└─────────┘     └─────────┘     └─────┬───┘
     ▲                                │
     │          Not done yet          │
     └────────────────────────────────┘
              ✅ Done? → Stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The agent looks at the goal, figures out the next step, does it, checks if it worked, and keeps going until the job is done.&lt;/p&gt;

&lt;p&gt;(You might hear people call this "ReAct" which stands for Reason + Act. It's a formal way of saying the agent thinks about what to do, then does it, then thinks again. But at its core, it's just the loop above.)&lt;/p&gt;

&lt;p&gt;Let me show you how this works with the resume example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; "Tailor this resume for an Amazon SDE role."&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent reads the job description. Amazon wants Python, AWS, and distributed systems.&lt;/li&gt;
&lt;li&gt;Agent reads your resume. Python and AWS are there. But distributed systems? It's buried in one bullet point that nobody would notice.&lt;/li&gt;
&lt;li&gt;Agent rewrites that bullet point to make distributed systems stand out.&lt;/li&gt;
&lt;li&gt;Agent checks again are all the important keywords from the job description covered? Not yet. Kafka is missing.&lt;/li&gt;
&lt;li&gt;Agent looks at your experience. You actually used Kafka at your last job but never put it on the resume. (We all do this.)&lt;/li&gt;
&lt;li&gt;Agent adds Kafka to the relevant section.&lt;/li&gt;
&lt;li&gt;Final check. Everything important is covered. Done.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Seven steps. No human needed. The agent figured out what was missing, fixed it, checked its work, and moved on.&lt;/p&gt;

&lt;p&gt;This is how every agent works whether it's tailoring resumes, monitoring servers, or analyzing data. Same loop, different tasks. (Okay, I'm oversimplifying a bit real agents sometimes have more complex decision trees. But this mental model will get you through 90% of what you'll build.)&lt;/p&gt;




&lt;h2&gt;
  
  
  The Autonomy Spectrum How Much Freedom Should an Agent Have?
&lt;/h2&gt;

&lt;p&gt;Before we look at how to build an agent, let's understand how much freedom you can give one. "Agent" isn't all-or-nothing. It's a range.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;What Happens&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fully Manual&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Human does everything&lt;/td&gt;
&lt;td&gt;You tailor your resume by hand&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Assisted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI suggests, human does&lt;/td&gt;
&lt;td&gt;ChatGPT suggests bullet points, you copy-paste&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Orchestrated&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Human defines steps, agent follows them&lt;/td&gt;
&lt;td&gt;You say "Step 1: read JD, Step 2: rewrite summary" agent does exactly that&lt;/td&gt;
&lt;td&gt;Low-Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Semi-Autonomous&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent decides steps, pauses for approval&lt;/td&gt;
&lt;td&gt;Agent figures out what to do, asks before submitting&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fully Autonomous&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent does everything, no human involved&lt;/td&gt;
&lt;td&gt;Agent searches, tailors, submits you just get a report&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most real-world agents today are in the middle some human control, some agent freedom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My advice:&lt;/strong&gt; Start with orchestrated you define the workflow, the agent executes it. Once you trust it, give it more freedom gradually. Add human checkpoints for important steps. Fully autonomous? Maybe someday. Not yet for anything that matters.&lt;/p&gt;

&lt;p&gt;The resume tailor agent we're building in this series? Semi-autonomous. It figures out what to fix and does the work, but shows you the result before anything gets submitted.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Building Blocks of Every Agent
&lt;/h2&gt;

&lt;p&gt;Now that you know how much freedom an agent can have, let's look at what you actually configure to make one work. Most frameworks I've used have these same pieces. Let's group them by what they do.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Core: Who does what?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;LLM (The Brain)&lt;/strong&gt; - The AI model that does the thinking. GPT-4, Claude, Amazon Nova, Llama. Don't always use the most expensive model. Use cheap models for easy tasks, powerful models for hard tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Role (The Identity)&lt;/strong&gt; - Tell the agent WHO it is. Example: "You are a Senior Tech Recruiter with 15 years of experience." The more specific the role, the better the output. Vague roles give vague results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goal (The Finish Line)&lt;/strong&gt; - When should the agent stop?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Bad: "Help with job search"&lt;/li&gt;
&lt;li&gt;✅ Good: "Find 5 Senior Python roles in Bangalore, 20-35 LPA, remote-friendly, from companies older than 3 years"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Task (The Instruction)&lt;/strong&gt; - The specific work to do right now. "Read this job description. Compare it against the resume. List the top 5 skill gaps." Be as specific as possible the more you leave unclear, the more the agent makes up.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Context: What does the agent know?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Backstory (The Personality)&lt;/strong&gt; - Shapes HOW the agent approaches work. Same task, different backstory, different style:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Seasoned recruiter" → practical, beats resume-scanning software&lt;/li&gt;
&lt;li&gt;"Career coach" → encouraging, focused on growth&lt;/li&gt;
&lt;li&gt;"Brutally honest reviewer" → tears your resume apart (but you learn the most)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Knowledge Base (The Facts)&lt;/strong&gt; - Information you hand the agent upfront. "The candidate is a Senior Python Developer. Based in Bangalore. Prefers remote. Salary: 20-35 LPA." Without this, the agent guesses. And it guesses wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory (What It Remembers)&lt;/strong&gt; - Two types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Short-term:&lt;/strong&gt; Remembers what happened earlier in the current task. "I already found Python listed in the resume."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-term:&lt;/strong&gt; Remembers across sessions. Run it next week, it knows what worked last time.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The Safety Net: What keeps it under control?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Tools (The Hands)&lt;/strong&gt; - What the agent can actually DO beyond generating text: search the web, read files, call APIs, run code. Without tools, it just writes. With tools, it takes action. Not every agent needs tools a resume rewriter just processes text, but a job searcher needs API access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guardrails (The Limits)&lt;/strong&gt; - Rules that keep the agent in check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Max attempts → stop after X tries (otherwise it loops forever and burns your budget)&lt;/li&gt;
&lt;li&gt;Allowed actions → limit which tools it can use&lt;/li&gt;
&lt;li&gt;Output checks → verify the result makes sense&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I always set guardrails. Always. I learned this at 2 AM watching my bill climb because an agent got stuck in a loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human-in-the-Loop (The Pause Button)&lt;/strong&gt; - For important decisions, make the agent stop and ask. The agent tailored your resume and is about to submit it. Instead of auto-submitting, it shows you the final version. "Should I submit this?" You review, approve, it continues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automate the boring parts. Keep human judgment for the important decisions.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Quick Reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Pieces&lt;/th&gt;
&lt;th&gt;One-line summary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The Core&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LLM, Role, Goal, Task&lt;/td&gt;
&lt;td&gt;Who is this agent and what's it doing?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The Context&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Backstory, Knowledge Base, Memory&lt;/td&gt;
&lt;td&gt;What does it know?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The Safety Net&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tools, Guardrails, Human-in-the-Loop&lt;/td&gt;
&lt;td&gt;What can it do and what stops it?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ten pieces, three groups. Now let's see how multiple agents work together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multiple Agents Working Together
&lt;/h2&gt;

&lt;p&gt;One agent is useful. But what if the job is too big for one?&lt;/p&gt;

&lt;p&gt;You split the work. Just like a real team each person handles what they're best at.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think of it like a restaurant:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One chef doesn't do everything. There's someone who preps ingredients, someone who cooks, and someone who plates the dish.&lt;/li&gt;
&lt;li&gt;Each person does one thing well. Together, they serve a full meal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agents work the same way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our resume example with 3 agents:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Job Analyzer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reads the job description, pulls out what matters most&lt;/td&gt;
&lt;td&gt;"They want Python, AWS, and distributed systems experience"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resume Writer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Takes that analysis and rewrites the resume to match&lt;/td&gt;
&lt;td&gt;A tailored resume highlighting the right skills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cover Letter Writer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Uses both outputs to write a personalized letter&lt;/td&gt;
&lt;td&gt;A cover letter that connects your experience to their needs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each agent does one thing. Together, they handle the full workflow.&lt;/p&gt;




&lt;h3&gt;
  
  
  How Do They Pass Work to Each Other?
&lt;/h3&gt;

&lt;p&gt;Three patterns. You only need to know the first one for now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Sequential - Like a relay race A→B→C&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agent 1 finishes → hands output to Agent 2 → hands to Agent 3.&lt;/p&gt;

&lt;p&gt;This is what we'll use. It's the simplest. Easy to understand, easy to fix when something breaks you know exactly which agent caused the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Hierarchical - Like a manager assigning tasks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One "boss" agent gives work to others and collects results. Useful when you need coordination, but more complex to set up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Parallel - Everyone works at the same time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Multiple agents work on independent tasks simultaneously. Faster, but the tasks can't depend on each other. Example: three agents searching three different job boards at the same time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For this series, we're using sequential.&lt;/strong&gt; Agent 1 passes to Agent 2 passes to Agent 3. Simple, predictable, easy to debug. You can explore the other patterns later once you're comfortable.&lt;/p&gt;

&lt;p&gt;Here's how our 3 agents work together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────┐       ┌───────────────┐       ┌─────────────────────┐
│ Job Analyzer │──────▶│ Resume Writer │──────▶│ Cover Letter Writer │
│              │       │               │       │                     │
│ "They want   │       │ Rewrites your │       │ Writes a letter     │
│  Python, AWS,│       │ resume to     │       │ connecting your     │
│  distributed │       │ highlight     │       │ experience to       │
│  systems"    │       │ those skills  │       │ their needs         │
└──────────────┘       └───────────────┘       └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  CrewAI - The Framework We'll Use
&lt;/h2&gt;

&lt;p&gt;I tried several agent frameworks. I picked CrewAI because it's simple and respects your time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why CrewAI?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free and open source&lt;/strong&gt; - active community, regular updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready-made templates&lt;/strong&gt; - gives you a working project structure immediately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain English configuration&lt;/strong&gt; - define agents and tasks in YAML (simple text files), not complex code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast to start&lt;/strong&gt; - first working agent in about 10 minutes once your environment is set up&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Structure
&lt;/h3&gt;

&lt;p&gt;When you create a CrewAI project, you get this folder structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-agent-project/
├── src/
│   └── my_agent_project/
│       ├── config/
│       │   ├── agents.yaml      ← WHO: define your agents here
│       │   └── tasks.yaml       ← WHAT: define tasks here
│       ├── tools/
│       │   └── custom_tool.py   ← HOW: custom tools agents can use
│       ├── crew.py              ← WIRING: connects agents to tasks
│       └── main.py              ← START: entry point, kicks everything off
├── knowledge/                   ← FACTS: static files agents can read
├── .env                         ← SECRETS: API keys, model settings
└── pyproject.toml               ← DEPENDENCIES: Python packages needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;You only need to write three things:&lt;/strong&gt; agent definitions, task definitions, and the wiring between them. Everything else is scaffolding that CrewAI handles.&lt;/p&gt;




&lt;h3&gt;
  
  
  CrewAI vs LangChain - When to Use Which
&lt;/h3&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;CrewAI&lt;/th&gt;
&lt;th&gt;LangChain/LangGraph&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Approach&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Define agents in simple YAML files&lt;/td&gt;
&lt;td&gt;Build custom chains and graphs in code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Learning time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A weekend&lt;/td&gt;
&lt;td&gt;A week or more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~10 minutes&lt;/td&gt;
&lt;td&gt;30+ minutes of boilerplate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clear roles, clear handoffs&lt;/td&gt;
&lt;td&gt;Complex branching, conditional logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Feels like&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Writing job descriptions for a team&lt;/td&gt;
&lt;td&gt;Building a flowchart with many "if/else" paths&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use CrewAI&lt;/strong&gt; when you know your agents and the flow is straightforward. Agent A does X, passes to Agent B which does Y. Done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use LangChain/LangGraph&lt;/strong&gt; when you need complex logic like "if confidence is above 80% go to Agent A, otherwise retry with Agent B, and if that fails ask a human."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My take:&lt;/strong&gt; Start with CrewAI. You can always add complexity later. Removing complexity? That's painful.&lt;/p&gt;




&lt;h2&gt;
  
  
  MCP - How Agents Talk to the Outside World
&lt;/h2&gt;

&lt;p&gt;Your agent can think. But can it actually reach out and grab information from somewhere else? Like a database full of job listings? Or a file stored in the cloud?&lt;/p&gt;

&lt;p&gt;That's what MCP does. It gives your agent a way to connect to outside services.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Simplest Way to Understand MCP
&lt;/h3&gt;

&lt;p&gt;Imagine you buy a new phone. You need to charge it. Now imagine every phone brand had a completely different charger one for Samsung, one for Apple, one for OnePlus, all different shapes.&lt;/p&gt;

&lt;p&gt;That's what life was like before USB-C. Now? One cable works for almost everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP is the USB-C of AI agents.&lt;/strong&gt; One standard way to connect to any service databases, file storage, APIs, messaging apps. You don't write different connection code for each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Back to Our Resume Agent
&lt;/h3&gt;

&lt;p&gt;Our agent needs job listings. Where do those come from?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maybe a database with thousands of job posts&lt;/li&gt;
&lt;li&gt;Maybe files stored in the cloud (like AWS S3)&lt;/li&gt;
&lt;li&gt;Maybe a job board API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Without MCP:&lt;/strong&gt; You write separate connection code for each one. Different code for the database. Different code for S3. Different code for the API. Every new service = more custom code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With MCP:&lt;/strong&gt; You configure the connection once in a simple settings file. The agent uses the same standard method to talk to all of them. New service? Just add another configuration. No new code.&lt;/p&gt;

&lt;p&gt;The first time I set this up, it took me about 5 minutes. Writing custom database connection code used to take me an hour. That's the difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Else Can Agents Connect To?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Databases&lt;/strong&gt; - fetch job listings, store results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File Storage (S3)&lt;/strong&gt; - read resumes, save tailored versions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; - read code, create pull requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack&lt;/strong&gt; - send notifications, read messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Search&lt;/strong&gt; - find information online&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One standard way to connect to all of these. That's MCP.&lt;/p&gt;

&lt;p&gt;We'll set this up hands-on in the next article. For now, just remember: MCP is how your agent reaches out to the world beyond its own code.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Agents Can't Do Well (Yet)
&lt;/h2&gt;

&lt;p&gt;Let me be honest about the limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They make things up.&lt;/strong&gt; I've had an agent tell me it "successfully completed" something it definitely didn't do. Always check important outputs yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They cost money.&lt;/strong&gt; One task = 5-20 AI model calls. A chatbot costs pennies. An agent can cost dollars per run. Multiply by hundreds of runs and your bill gets serious.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They get stuck in loops.&lt;/strong&gt; Without a maximum attempt limit, an agent will happily retry the same failing approach forever. (I learned this the hard way at 2 AM.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same input, slightly different output.&lt;/strong&gt; This makes testing tricky because you can't always predict the exact result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They're slow.&lt;/strong&gt; Multiple AI calls + tool usage = seconds to minutes per task. Not milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They "fix" things you didn't ask about.&lt;/strong&gt; When fixing one issue, an agent sometimes decides to also change nearby code. In a demo, fine. In production, you get unexpected changes. Set tight boundaries.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  How I Handle These Problems
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use cheap models for simple tasks.&lt;/strong&gt; Amazon Nova Lite for easy stuff, Nova Pro for complex reasoning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep prompts short.&lt;/strong&gt; Every word costs money.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always set max attempts.&lt;/strong&gt; Non-negotiable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log everything.&lt;/strong&gt; I send all agent activity to CloudWatch Logs token counts, step durations, success/failure at each step. When something breaks at step 5 of 7, I can trace exactly what went wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate outputs.&lt;/strong&gt; For critical tasks, I add a checking step either another agent that verifies the work, or structured checks that confirm the output meets requirements.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When NOT to Use Agents
&lt;/h2&gt;

&lt;p&gt;Not everything needs an agent. I've seen people over-engineer simple problems because "agents are cool." Skip agents when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One AI call is enough&lt;/strong&gt; - Summarize this text? Translate this paragraph? That's one API call, not an agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The logic never changes&lt;/strong&gt; - If there are no decisions to make and the steps are always the same, a simple script is cheaper, faster, and more reliable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need instant responses&lt;/strong&gt; - Agents take seconds to minutes. If you need millisecond responses for a real-time API, agents are too slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High volume + simple task&lt;/strong&gt; - 5-20 AI calls per task × 10,000 tasks per day = a very expensive month. If the task is simple and repetitive, write a rule-based system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mistakes are unacceptable&lt;/strong&gt; - Agents make errors. If a wrong answer has serious consequences (medical, legal, financial), don't rely on an agent without heavy human oversight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's just a database query&lt;/strong&gt; - Reading from a database and returning results? That's a simple API, not an agent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My rule:&lt;/strong&gt; If you can draw the logic as a simple flowchart with no "it depends" decisions, you don't need an agent. Use a script. Agents are valuable when there's ambiguity, judgment calls, and multi-step reasoning involved.&lt;/p&gt;




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

&lt;p&gt;In the next article, I'm building the resume tailor agent step by step using CrewAI on an EC2 instance with Amazon Nova Pro as the AI model. From setting up the server to running the agent against a real job description.&lt;/p&gt;

&lt;p&gt;Stay tuned.&lt;/p&gt;




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

&lt;p&gt;Thanks for reading! If this was helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value&lt;/li&gt;
&lt;li&gt;💾 Save for later&lt;/li&gt;
&lt;li&gt;🔄 Share with your team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow me for more on:&lt;/strong&gt; AWS architecture, FinOps, DevOps, and AI Infrastructure.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit my website&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;Connect on LinkedIn&lt;/a&gt;&lt;/strong&gt; | &lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Learning&lt;/strong&gt; 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>agents</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I Installed Kiro CLI on EC2: Here's Why My First Attempt Failed</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Fri, 15 May 2026 04:37:18 +0000</pubDate>
      <link>https://dev.to/aws-builders/i-installed-kiro-cli-on-ec2-heres-why-my-first-attempt-failed-38hb</link>
      <guid>https://dev.to/aws-builders/i-installed-kiro-cli-on-ec2-heres-why-my-first-attempt-failed-38hb</guid>
      <description>&lt;p&gt;👋 Hey there, tech enthusiasts! &lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect with a passion for transforming complex technological challenges into elegant solutions. With extensive experience spanning Cloud Operations (AWS &amp;amp; Azure), Data Operations, Analytics, DevOps, and Generative AI, I've had the privilege of architecting solutions for global enterprises that drive real business impact. Through this article series, I'm excited to share practical insights, best practices, and hands-on experiences from my journey in the tech world. Whether you're a seasoned professional or just starting out, I aim to break down complex concepts into digestible pieces that you can apply in your projects.&lt;/p&gt;

&lt;p&gt;Let's dive in and explore the fascinating world of cloud technology together! 🚀&lt;/p&gt;




&lt;p&gt;I've been using Amazon Q CLI for nearly two years. It became part of my daily workfloquick infrastructure lookups, generating boilerplate, debugging at odd hours. It was reliable, familiar, and I never thought about replacing it.&lt;/p&gt;

&lt;p&gt;Then the end-of-support announcement dropped.&lt;/p&gt;

&lt;p&gt;At first, I felt that familiar frustration. Another tool sunset. Another migration. But then I paused. Instead of scrambling for a 1:1 replacement, I asked myself: &lt;em&gt;what do I actually need from a terminal AI in 2026?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My answer wasn't "the same thing I had." It was more. I wanted something that understood my team's standards without me repeating them. Something that could pull real AWS data not just guess at it. Something I could shape into different personas for different tasks.&lt;/p&gt;

&lt;p&gt;That's when I discovered that Kiro which I'd only known as an IDE also ships a CLI. And it's not just a CLI. It's the tool I didn't know I was waiting for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Isn't Just Another Migration
&lt;/h2&gt;

&lt;p&gt;Let me be clear: I'm not switching because I have to. I'm switching because Kiro CLI solves problems Q CLI never addressed.&lt;/p&gt;

&lt;p&gt;Here's what changed my mind in the first 30 minutes:&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Custom Agents
&lt;/h3&gt;

&lt;p&gt;Define specialized personas with restricted tool access, scoped file permissions, and custom prompts. I now have a DevOps agent that can only touch infrastructure files, and a reviewer agent that's physically incapable of modifying anything. Different tools for different jobs from the same CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  📏 Steering Files
&lt;/h3&gt;

&lt;p&gt;Persistent rules that shape every response. My team's infrastructure standards Graviton instances, GP3 volumes, mandatory tags, Lambda best practices are baked into every interaction automatically. No more repeating "use ARM64" in every prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔌 MCP Server Ecosystem
&lt;/h3&gt;

&lt;p&gt;Connect to AWS Cost Explorer, Pricing API, CloudTrail, Well-Architected tools, and more. This isn't "AI guessing at your costs." This is real data flowing into real recommendations. Within my first week, it found a forgotten NAT Gateway costing $180/month.&lt;/p&gt;

&lt;h3&gt;
  
  
  📋 Planning Mode
&lt;/h3&gt;

&lt;p&gt;Press &lt;code&gt;Shift+Tab&lt;/code&gt; and Kiro switches from "do the thing" to "let's think about the thing first." Structured requirements gathering before implementation. Essential for anything touching multiple services.&lt;/p&gt;

&lt;h3&gt;
  
  
  📊 Real-Time Usage Tracking
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/usage&lt;/code&gt; shows exactly where you stand credits consumed, tier limits, what's left. No end-of-month surprises.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔐 Multiple Auth Options
&lt;/h3&gt;

&lt;p&gt;Builder ID, IAM Identity Center, GitHub, Google. Teams get centralized billing and access control. Solo developers get flexibility.&lt;/p&gt;

&lt;p&gt;Amazon Q CLI was a good conversational assistant. Kiro CLI is an extensible development platform that happens to live in your terminal.&lt;/p&gt;

&lt;p&gt;That distinction is why I'm writing this guide instead of a "how to cope with deprecation" post.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Quick Comparison
&lt;/h2&gt;

&lt;p&gt;For those coming from Q CLI, here's what's different at a glance:&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;Amazon Q CLI&lt;/th&gt;
&lt;th&gt;Kiro CLI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Auth options&lt;/td&gt;
&lt;td&gt;Builder ID, IAM Identity Center&lt;/td&gt;
&lt;td&gt;+ GitHub, Google&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entry point&lt;/td&gt;
&lt;td&gt;&lt;code&gt;q chat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kiro-cli chat&lt;/code&gt; (&lt;code&gt;q&lt;/code&gt; still works)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model selection&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Auto-selects per task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost visibility&lt;/td&gt;
&lt;td&gt;End of month surprise&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/usage&lt;/code&gt; command, real-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom agents&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Full tool/path scoping, hooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Steering/Rules&lt;/td&gt;
&lt;td&gt;Simple rules&lt;/td&gt;
&lt;td&gt;Rich markdown standards files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP ecosystem&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;20+ official AWS MCP servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Planning mode&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ Structured requirements first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;AWS IP License&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The license change matters if your org has strict open-source requirements. For most of us, it doesn't affect day-to-day use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Infrastructure Requirements
&lt;/h2&gt;

&lt;p&gt;Before installing, make sure your machine meets these specs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Minimum&lt;/th&gt;
&lt;th&gt;Recommended&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;x86_64 or ARM64&lt;/td&gt;
&lt;td&gt;Either works fine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Ubuntu, Fedora, or Amazon Linux 2023&lt;/td&gt;
&lt;td&gt;Ubuntu 22.04 LTS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;glibc&lt;/td&gt;
&lt;td&gt;2.34+&lt;/td&gt;
&lt;td&gt;2.35+ (ships with Ubuntu 22.04)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;2 GB&lt;/td&gt;
&lt;td&gt;4 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;2 vCPUs&lt;/td&gt;
&lt;td&gt;2 vCPUs is fine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk&lt;/td&gt;
&lt;td&gt;5 GB free&lt;/td&gt;
&lt;td&gt;10 GB+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;Internet access&lt;/td&gt;
&lt;td&gt;Stable connection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your glibc is older than 2.34 (check with &lt;code&gt;ldd --version&lt;/code&gt;), grab the musl version instead it's the one with &lt;code&gt;-musl.zip&lt;/code&gt; in the filename.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Setup
&lt;/h2&gt;

&lt;p&gt;I'm running this on an EC2 instance:&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;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instance Type&lt;/td&gt;
&lt;td&gt;t3.medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Ubuntu 22.04.5 LTS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;x86_64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vCPUs&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;4 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;50 GB GP3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Region&lt;/td&gt;
&lt;td&gt;us-east-1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I went with t3.medium (4 GB RAM) to keep things comfortable. AI model interactions benefit from headroom, and the cost difference is negligible for a development tool you use daily.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation (Under 5 Minutes)
&lt;/h2&gt;

&lt;p&gt;The whole process is straightforward. Nine steps, no surprises.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Download Kiro CLI
&lt;/h3&gt;

&lt;p&gt;SSH into your instance and pull the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-sSf&lt;/span&gt; &lt;span class="s1"&gt;'https://desktop-release.q.us-east-1.amazonaws.com/latest/kirocli-x86_64-linux.zip'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'kirocli.zip'&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%2Fcpern8zxbbaizbq48hm8.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%2Fcpern8zxbbaizbq48hm8.png" alt=" " width="800" height="72"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Takes about 10 seconds on a decent connection. The file is around 350 MB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Extract It
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unzip kirocli.zip
&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%2Fwax0rekz55p2srq39x22.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%2Fwax0rekz55p2srq39x22.png" alt=" " width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Run the Installer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./kirocli/install.sh
&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%2F1x1l3ll6tpigkzc5axlw.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%2F1x1l3ll6tpigkzc5axlw.png" alt=" " width="799" height="70"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Let It Configure Your Shell
&lt;/h3&gt;

&lt;p&gt;It'll ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do you want kiro-cli to modify your shell config (you will have to manually do this otherwise)?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt;. This adds the necessary PATH entries so you can run &lt;code&gt;kiro-cli&lt;/code&gt; from anywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Authenticate
&lt;/h3&gt;

&lt;p&gt;Now it needs to know who you are. I'm using &lt;strong&gt;Builder ID&lt;/strong&gt; it's free, quick to set up, and works well for individual use. You can also use IAM Identity Center for teams, or GitHub/Google if you prefer.&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%2Fbyp431nvftipim3w6bpt.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%2Fbyp431nvftipim3w6bpt.png" alt=" " width="798" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Open the Auth URL
&lt;/h3&gt;

&lt;p&gt;Kiro generates a URL for you. Copy it and open it in your browser.&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%2Fya5e23t7fa7ctbbzkhb1.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%2Fya5e23t7fa7ctbbzkhb1.png" alt=" " width="800" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Log in with your email and password:&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%2Fjdvuonqa3sfrme6j9dpb.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%2Fjdvuonqa3sfrme6j9dpb.png" alt=" " width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Confirm Access
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;Confirm and Continue&lt;/strong&gt;:&lt;/p&gt;

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

&lt;p&gt;Then click &lt;strong&gt;Allow&lt;/strong&gt; when it asks for data access:&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%2Fu3ciszj24xjmz3jawsrt.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%2Fu3ciszj24xjmz3jawsrt.png" alt=" " width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The browser tab closes automatically. You're done with the web part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8: Back to the Terminal
&lt;/h3&gt;

&lt;p&gt;You should see a success message:&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%2Fb8mdgmg8hqqsyrcu2u1t.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%2Fb8mdgmg8hqqsyrcu2u1t.png" alt=" " width="727" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9: Verify It Works
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kiro-cli
&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%2Fcqe1sqq8i6k7uqrbpz7j.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%2Fcqe1sqq8i6k7uqrbpz7j.png" alt=" " width="799" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you see the Kiro interface, you're in:&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%2Fbs2yjw5yts60tbnbogtd.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%2Fbs2yjw5yts60tbnbogtd.png" alt=" " width="799" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. Under five minutes from download to working CLI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automatic Migration from Q CLI
&lt;/h2&gt;

&lt;p&gt;This is the part that made me smile. When Kiro CLI installed, it automatically detected and migrated my existing Q CLI setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Agents&lt;/strong&gt; from &lt;code&gt;~/.aws/amazonq&lt;/code&gt; → &lt;code&gt;~/.kiro/agents/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP config&lt;/strong&gt; from &lt;code&gt;~/.aws/amazonq/mcp.json&lt;/code&gt; → &lt;code&gt;~/.kiro/settings/mcp.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules&lt;/strong&gt; from &lt;code&gt;~/.aws/amazonq/rules&lt;/code&gt; → &lt;code&gt;~/.kiro/steering/&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two years of custom configurations carried over in seconds. The only thing I had to fix was one MCP server with a hardcoded path to &lt;code&gt;~/.aws/amazonq/&lt;/code&gt; in its environment variables. Everything else just worked.&lt;/p&gt;

&lt;p&gt;No starting from scratch. No rebuilding your setup. That's how migrations should feel.&lt;/p&gt;




&lt;h2&gt;
  
  
  One More Thing: Classic Mode
&lt;/h2&gt;

&lt;p&gt;By default, Kiro opens in the new Terminal UI. It looks polished, but on remote servers and SSH sessions, I prefer classic mode faster rendering, no UI lag, works perfectly in tmux.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it 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;kiro-cli &lt;span class="nt"&gt;--classic&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%2F1hsjjiec5ochez1fwg92.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%2F1hsjjiec5ochez1fwg92.png" alt=" " width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make it permanent:&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;kiro-cli settings chat.ui &lt;span class="s2"&gt;"classic"&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%2Fk00xbsrzu060zwpedz8p.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%2Fk00xbsrzu060zwpedz8p.png" alt=" " width="797" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now every time you run &lt;code&gt;kiro-cli&lt;/code&gt;, it starts in classic mode:&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%2Fapk3n8pvp321mfjpw42w.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%2Fapk3n8pvp321mfjpw42w.png" alt=" " width="799" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I Prefer Classic Mode on Remote Servers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Faster rendering over SSH&lt;/li&gt;
&lt;li&gt;No UI lag&lt;/li&gt;
&lt;li&gt;Works perfectly in tmux&lt;/li&gt;
&lt;li&gt;More predictable behavior in long sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you ever want the full UI back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kiro-cli settings chat.ui &lt;span class="s2"&gt;"tui"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just run &lt;code&gt;kiro-cli --tui&lt;/code&gt; for a one-off session.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cleanup (Optional)
&lt;/h2&gt;

&lt;p&gt;That 350 MB zip file is still sitting there. Once you've verified everything works:&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;rm&lt;/span&gt; ~/kirocli.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  First Impressions After One Week
&lt;/h2&gt;

&lt;p&gt;After seven days of daily use, here's what stood out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;q&lt;/code&gt; command still works.&lt;/strong&gt; Muscle memory preserved. Kiro CLI responds to both &lt;code&gt;kiro-cli&lt;/code&gt; and &lt;code&gt;q&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model auto-selection is smart.&lt;/strong&gt; Simple questions get fast responses. Complex architecture questions get deeper reasoning. I don't have to think about which model to pick.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP servers change everything.&lt;/strong&gt; Going from "AI that guesses" to "AI that queries real data" is a paradigm shift. Cost optimization alone justified the switch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steering files are addictive.&lt;/strong&gt; Once you teach it your standards, you can't go back to repeating yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I went from "reluctant migrator" to "why didn't this exist sooner" in about three days.&lt;/p&gt;




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

&lt;p&gt;Now that it's installed, the real value starts. In the next article, I'll cover how I use Kiro CLI for actual work setting up MCP servers for cost optimization, creating custom agents that enforce team standards, writing steering files, and the daily workflows that made this tool indispensable.&lt;/p&gt;

&lt;p&gt;👉 Part 2: From Budget Alerts to AI-Powered Optimization The Complete Kiro CLI Guide&lt;/p&gt;




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

&lt;p&gt;Thank you for reading! I hope this article gave you practical insights and a clearer perspective on the topic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Was this helpful?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value&lt;/li&gt;
&lt;li&gt;🦄 Unicorn if you’re applying it today&lt;/li&gt;
&lt;li&gt;💾 Save for your next optimization session&lt;/li&gt;
&lt;li&gt;🔄 Share with your team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow me for more on:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS architecture patterns&lt;/li&gt;
&lt;li&gt;FinOps automation&lt;/li&gt;
&lt;li&gt;Multi-account strategies&lt;/li&gt;
&lt;li&gt;AI-driven DevOps&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 What’s Next
&lt;/h2&gt;

&lt;p&gt;More deep dives coming soon on cloud operations, GenAI, Agentic-AI, DevOps, and data workflows follow for weekly insights.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 Portfolio &amp;amp; Work
&lt;/h2&gt;

&lt;p&gt;You can explore my full body of work, certifications, architecture projects, and technical articles here:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit My Website&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Services I Offer
&lt;/h2&gt;

&lt;p&gt;If you're looking for hands-on guidance or collaboration, I provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Architecture Consulting (AWS / Azure)&lt;/li&gt;
&lt;li&gt;DevSecOps &amp;amp; Automation Design&lt;/li&gt;
&lt;li&gt;FinOps Optimization Reviews&lt;/li&gt;
&lt;li&gt;Technical Writing (Cloud, DevOps, GenAI)&lt;/li&gt;
&lt;li&gt;Product &amp;amp; Architecture Reviews&lt;/li&gt;
&lt;li&gt;Mentorship &amp;amp; 1:1 Technical Guidance&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🤝 Let’s Connect
&lt;/h2&gt;

&lt;p&gt;I’d love to hear your thoughts drop a comment or connect with me on &lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For collaborations, consulting, or technical discussions, feel free to reach out directly at &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Learning&lt;/strong&gt; 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Deploy Web Servers with Terraform: EC2 + Load Balancer Tutorial</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Mon, 27 Apr 2026 09:40:05 +0000</pubDate>
      <link>https://dev.to/aws-builders/deploy-web-servers-with-terraform-ec2-load-balancer-tutorial-304k</link>
      <guid>https://dev.to/aws-builders/deploy-web-servers-with-terraform-ec2-load-balancer-tutorial-304k</guid>
      <description>&lt;p&gt;👋 Hey there, tech enthusiasts! &lt;/p&gt;

&lt;p&gt;I'm Sarvar, a Cloud Architect with a passion for transforming complex technological challenges into elegant solutions. With extensive experience spanning Cloud Operations (AWS &amp;amp; Azure), Data Operations, Analytics, DevOps, and Generative AI, I've had the privilege of architecting solutions for global enterprises that drive real business impact. Through this article series, I'm excited to share practical insights, best practices, and hands-on experiences from my journey in the tech world. Whether you're a seasoned professional or just starting out, I aim to break down complex concepts into digestible pieces that you can apply in your projects.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"A single server is a single point of failure. A load balancer is your insurance policy."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🎯 Welcome Back!
&lt;/h2&gt;

&lt;p&gt;Remember in &lt;a href="https://dev.to/aws-builders/building-your-first-vpc-aws-networking-with-terraform-3h34"&gt;Article 6&lt;/a&gt; when you built your first VPC with public and private subnets? You created the network foundation, but it was empty—no servers, no applications, nothing running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the reality:&lt;/strong&gt; A VPC without compute resources is like building a highway with no cars. You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web servers to run your applications&lt;/li&gt;
&lt;li&gt;A way to handle traffic spikes&lt;/li&gt;
&lt;li&gt;Protection against server failures&lt;/li&gt;
&lt;li&gt;Zero-downtime deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;That's where EC2 instances and Load Balancers come in.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;By the end of this article, you'll:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Deploy EC2 instances with Terraform&lt;/li&gt;
&lt;li&gt;✅ Configure security groups for web servers&lt;/li&gt;
&lt;li&gt;✅ Use user data to automate server setup&lt;/li&gt;
&lt;li&gt;✅ Create an Application Load Balancer (ALB)&lt;/li&gt;
&lt;li&gt;✅ Implement health checks and target groups&lt;/li&gt;
&lt;li&gt;✅ Build high-availability web infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Time Required:&lt;/strong&gt; 45 minutes (20 min read + 25 min practice)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; ~$33/month (~$16 with free tier for ALB)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Difficulty:&lt;/strong&gt; Intermediate&lt;/p&gt;

&lt;p&gt;Let's deploy some real infrastructure! 🚀&lt;/p&gt;


&lt;h2&gt;
  
  
  💔 The Problem: Single Server Syndrome
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Nightmare Scenario
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;It's 2 AM. Your phone rings.&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;Monitoring Alert: Website Down
Status: 503 Service Unavailable
Cause: EC2 instance crashed
Impact: 100% of users affected
Revenue Loss: $500/minute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;You scramble to:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SSH into the server (if you can)&lt;/li&gt;
&lt;li&gt;Restart the application&lt;/li&gt;
&lt;li&gt;Hope it comes back up&lt;/li&gt;
&lt;li&gt;Watch users leave your site&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The root cause?&lt;/strong&gt; You had &lt;strong&gt;one server&lt;/strong&gt; running everything.&lt;/p&gt;
&lt;h3&gt;
  
  
  Common Single-Server Problems:
&lt;/h3&gt;

&lt;p&gt;❌ &lt;strong&gt;Traffic Spike:&lt;/strong&gt; Black Friday hits, server crashes from load&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Hardware Failure:&lt;/strong&gt; AWS instance dies, site goes down&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Deployment Risk:&lt;/strong&gt; Update breaks something, entire site offline&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;No Redundancy:&lt;/strong&gt; One point of failure = business risk&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Manual Recovery:&lt;/strong&gt; You're the human load balancer at 2 AM&lt;br&gt;&lt;br&gt;
❌ &lt;strong&gt;Poor User Experience:&lt;/strong&gt; Slow response times, timeouts, errors&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sound familiar?&lt;/strong&gt; Let's fix this with load balancing.&lt;/p&gt;


&lt;h2&gt;
  
  
  🌟 What is a Load Balancer? (Quick Theory)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Simple Definition
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Load Balancer&lt;/strong&gt; = Traffic cop for your servers. It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Distributes incoming requests across multiple servers&lt;/li&gt;
&lt;li&gt;Monitors server health automatically&lt;/li&gt;
&lt;li&gt;Routes traffic only to healthy servers&lt;/li&gt;
&lt;li&gt;Enables zero-downtime deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Think of it like this:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without Load Balancer:&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;All Users → Single Server → 💥 Overloaded/Crashed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;With Load Balancer:&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;Users → Load Balancer → Server 1 (healthy) ✅
                     → Server 2 (healthy) ✅
                     → Server 3 (unhealthy) ❌ (no traffic)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Why You Need This
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario 1: Traffic Spike&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Normal: 100 requests/sec → 2 servers handle it easily&lt;/li&gt;
&lt;li&gt;Black Friday: 10,000 requests/sec → Load balancer distributes across all servers&lt;/li&gt;
&lt;li&gt;Result: Site stays up, users happy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scenario 2: Server Failure&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server 1 crashes at 2 AM&lt;/li&gt;
&lt;li&gt;Load balancer detects failure in 30 seconds&lt;/li&gt;
&lt;li&gt;Automatically stops sending traffic to Server 1&lt;/li&gt;
&lt;li&gt;Server 2 handles all traffic&lt;/li&gt;
&lt;li&gt;Result: You sleep through the night&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scenario 3: Deployment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy new code to Server 1&lt;/li&gt;
&lt;li&gt;Load balancer keeps sending traffic to Server 2&lt;/li&gt;
&lt;li&gt;Test Server 1, then switch traffic&lt;/li&gt;
&lt;li&gt;Result: Zero downtime deployment&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  📋 Prerequisites
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;✅ Completed &lt;a href="https://dev.tolink-to-article-6"&gt;Article 6: Building Your First AWS VPC&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✅ Terraform installed (v1.0+)&lt;/li&gt;
&lt;li&gt;✅ AWS CLI configured&lt;/li&gt;
&lt;li&gt;✅ Basic understanding of VPC and networking&lt;/li&gt;
&lt;li&gt;✅ An SSH key pair in AWS (or we'll create one)&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🏗️ What We're Building
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet
    ↓
Application Load Balancer (ALB)
    ↓
    ├─→ EC2 Instance 1 (Apache)
    └─→ EC2 Instance 2 (Apache)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Architecture Components:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;VPC&lt;/strong&gt; - Our isolated network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public Subnet&lt;/strong&gt; - Where our resources live&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internet Gateway&lt;/strong&gt; - Internet access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Groups&lt;/strong&gt; - Firewall rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2 Instances&lt;/strong&gt; - Web servers running Apache&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Load Balancer&lt;/strong&gt; - Traffic distributor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target Group&lt;/strong&gt; - Manages server health&lt;/li&gt;
&lt;/ol&gt;


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

&lt;p&gt;Create this folder structure:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;07-ec2-load-balancer/
├── main.tf           # Main infrastructure code
├── variables.tf      # Input variables
├── outputs.tf        # Output values
└── terraform.tfvars  # Variable values
&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%2F2igrdc5u5wq4g90eiyhl.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%2F2igrdc5u5wq4g90eiyhl.png" alt=" " width="800" height="190"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;The complete source code and Terraform configuration used in this article can be found on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/simplynadaf" rel="noopener noreferrer"&gt;
        simplynadaf
      &lt;/a&gt; / &lt;a href="https://github.com/simplynadaf/terraform-by-sarvar" rel="noopener noreferrer"&gt;
        terraform-by-sarvar
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Terraform Series
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🚀 Terraform By Sarvar&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Complete Terraform tutorial series from basics to advanced concepts&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://www.terraform.io/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/446abdaa5e617237549619105d10cbd99f693a057dc7b366edd5256283015a75/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5465727261666f726d2d76312e31342b2d3632334345343f7374796c653d666f722d7468652d6261646765266c6f676f3d7465727261666f726d266c6f676f436f6c6f723d7768697465" alt="Terraform"&gt;&lt;/a&gt;
&lt;a href="https://aws.amazon.com/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0b14c31424193ad1651b7422c0c3fd0f5339f3f2632f5ecc3d5313c83e8aeba0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4157532d436c6f75642d4646393930303f7374796c653d666f722d7468652d6261646765266c6f676f3d616d617a6f6e2d617773266c6f676f436f6c6f723d7768697465" alt="AWS"&gt;&lt;/a&gt;
&lt;a href="https://github.com/simplynadaf/terraform-by-sarvar/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9218332452902d9e542a100d0af126fd3174a116456614d2cf093546a13783db/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e7376673f7374796c653d666f722d7468652d6261646765" alt="License"&gt;&lt;/a&gt;
&lt;a href="https://dev.to/sarvar_04/series/36958" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/ad1f81014cac91cbb305e20c6fb83301ac4992821ede633a6a83bda4379ed118/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6465762e746f2d5365726965732d3041304130413f7374796c653d666f722d7468652d6261646765266c6f676f3d6465762e746f266c6f676f436f6c6f723d7768697465" alt="dev.to"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dev.to/sarvar_04/series/36963" rel="nofollow"&gt;📖 Read the Series&lt;/a&gt; • &lt;a href="https://sarvarnadaf.com" rel="nofollow noopener noreferrer"&gt;🌐 Portfolio&lt;/a&gt; • &lt;a href="https://www.linkedin.com/in/sarvar04/" rel="nofollow noopener noreferrer"&gt;💼 LinkedIn&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📚 Series Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This repository contains &lt;strong&gt;ONLY Terraform code examples&lt;/strong&gt; for the &lt;strong&gt;Terraform By Sarvar&lt;/strong&gt; tutorial series.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ IMPORTANT:&lt;/strong&gt; This repo contains only &lt;code&gt;.tf&lt;/code&gt; files and infrastructure code. Articles are published on dev.to, not stored here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;📖 &lt;strong&gt;Read the series on dev.to:&lt;/strong&gt; &lt;a href="https://dev.to/sarvar_04/series/36963" rel="nofollow"&gt;https://dev.to/sarvar_04/series/36963&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;🎯 What You'll Learn&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;✅ Infrastructure as Code (IaC) fundamentals&lt;/li&gt;
&lt;li&gt;✅ Terraform basics to advanced concepts&lt;/li&gt;
&lt;li&gt;✅ AWS resource provisioning&lt;/li&gt;
&lt;li&gt;✅ Best practices and real-world patterns&lt;/li&gt;
&lt;li&gt;✅ Production-ready configurations&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;📊 Series Progress&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/e7a8f995fcf62075df5b38ff48a03f1ee698e1cacdeede4e004b6b2434c8e77e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41727469636c65732d3825324631302d626c75653f7374796c653d666c61742d737175617265"&gt;&lt;img src="https://camo.githubusercontent.com/e7a8f995fcf62075df5b38ff48a03f1ee698e1cacdeede4e004b6b2434c8e77e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41727469636c65732d3825324631302d626c75653f7374796c653d666c61742d737175617265" alt="Progress"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/ab5bccc65fd0dfc759cb04ffe3446775f8674a09e936f473d7a51abcd3e02713/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5374617475732d4163746976652d737563636573733f7374796c653d666c61742d737175617265"&gt;&lt;img src="https://camo.githubusercontent.com/ab5bccc65fd0dfc759cb04ffe3446775f8674a09e936f473d7a51abcd3e02713/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5374617475732d4163746976652d737563636573733f7374796c653d666c61742d737175617265" alt="Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📖 Article Series&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;📘 Foundation (Articles 1-5) ✅ Complete&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/why-every-developer-should-learn-terraform-in-2026-and-how-to-start--4fk0" rel="nofollow"&gt;Introduction to Terraform &amp;amp; IaC&lt;/a&gt; - ✅ Published&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/installing-terraform-and-setting-up-your-environment-1j9b" rel="nofollow"&gt;Installation &amp;amp; Setup&lt;/a&gt; - ✅ Published&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/your-first-infrastructure-as-code-in-four-commands-46ep" rel="nofollow"&gt;Your First AWS Resource&lt;/a&gt; - ✅ Published&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/terraform-state-the-one-file-you-cant-afford-to-lose-33l4" rel="nofollow"&gt;Understanding Terraform State&lt;/a&gt; - ✅ Published&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/terraform-variables-and-outputs-making-your-infrastructure-flexible-3ebc" rel="nofollow"&gt;Variables and Outputs&lt;/a&gt; - ✅ Published&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;📗 Real Infrastructure (Articles 6-10)&lt;/h3&gt;

&lt;/div&gt;
&lt;ol start="6"&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/building-your-first-vpc-aws-networking-with-terraform-3h34" rel="nofollow"&gt;Building a VPC from Scratch&lt;/a&gt;…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/simplynadaf/terraform-by-sarvar" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;Follow these steps to run the project on your local machine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/simplynadaf/terraform-by-sarvar.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Navigate to the project directory:
&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;cd &lt;/span&gt;terraform-by-sarvar/articles/08-lambda-serverless
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔧 Step 1: Define Variables
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;variables.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# AWS Region&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"aws_region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS region where resources will be created"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Project Name&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"project_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Project name for resource naming"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-web"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Environment&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Environment name"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# VPC CIDR&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"vpc_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CIDR block for VPC"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Public Subnet CIDR&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"public_subnet_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CIDR block for public subnet"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Instance Type&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"instance_type"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EC2 instance type"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Instance Count&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"instance_count"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Number of EC2 instances"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# SSH Key Name&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"key_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SSH key pair name"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-key"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# My IP for SSH Access&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"my_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Your IP address for SSH access (CIDR format)"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;  &lt;span class="c1"&gt;# Change this to your IP for security&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%2Fif5m2cl3bo96xf9a27tn.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%2Fif5m2cl3bo96xf9a27tn.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;instance_count = 2&lt;/code&gt; - We'll create 2 web servers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;instance_type = "t2.micro"&lt;/code&gt; - Free tier eligible&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;my_ip&lt;/code&gt; - Restrict SSH access (change from 0.0.0.0/0 in production!)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌐 Step 2: Create VPC and Networking
&lt;/h2&gt;

&lt;p&gt;Add to &lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;# Provider Configuration&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Data source: Get latest Amazon Linux 2023 AMI&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_ami"&lt;/span&gt; &lt;span class="s2"&gt;"amazon_linux"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;most_recent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;owners&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"amazon"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"al2023-ami-*-x86_64"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"virtualization-type"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hvm"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Data source: Get available availability zones&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_availability_zones"&lt;/span&gt; &lt;span class="s2"&gt;"available"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"available"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# VPC&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_hostnames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_support&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-vpc"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Internet Gateway&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_internet_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-igw"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Public Subnet&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"public"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_subnet_cidr&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;map_public_ip_on_launch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-public-subnet"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;Type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Public"&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Public Route Table&lt;/span&gt;

&lt;span class="c1"&gt;# Public Subnet 2 (Different AZ for ALB)&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"public_2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;names&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="nx"&gt;map_public_ip_on_launch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-public-subnet-2"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;Type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Public"&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"public"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
    &lt;span class="nx"&gt;gateway_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-public-rt"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;Type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Public"&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Route Table Association&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"public"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Route Table Association for Public Subnet 2&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"public_2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;New Concept: Data Sources&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_ami"&lt;/span&gt; &lt;span class="s2"&gt;"amazon_linux"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;most_recent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;owners&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"amazon"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="c1"&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%2F7lvfs3hi5vec2lll0jxq.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%2F7lvfs3hi5vec2lll0jxq.png" alt=" " width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data sources&lt;/strong&gt; query existing AWS resources. Here we're finding the latest Amazon Linux AMI automatically - no need to hardcode AMI IDs!&lt;/p&gt;




&lt;h2&gt;
  
  
  🔒 Step 3: Configure Security Groups
&lt;/h2&gt;

&lt;p&gt;Security groups are like firewalls. Add to &lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Security Group for ALB&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"alb"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-alb-sg"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Security group for Application Load Balancer"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP from anywhere"&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;egress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow all outbound traffic"&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-alb-sg"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Security Group for EC2 Instances&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"instance"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-instance-sg"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Security group for EC2 instances"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP from ALB"&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SSH from my IP"&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_ip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;egress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow all outbound traffic"&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-instance-sg"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;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%2Fmv8damni39oo8eticurk.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%2Fmv8damni39oo8eticurk.png" alt=" " width="796" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security Architecture:&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;Internet → ALB (Port 80) → EC2 Instances (Port 80)
                            EC2 Instances (Port 22 from your IP)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Security Practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ALB accepts HTTP (80) from anywhere&lt;/li&gt;
&lt;li&gt;EC2 instances only accept HTTP from ALB (not directly from internet!)&lt;/li&gt;
&lt;li&gt;SSH (22) only from your IP address&lt;/li&gt;
&lt;li&gt;This is called "defense in depth"&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🖥️ Step 4: Deploy EC2 Instances
&lt;/h2&gt;

&lt;p&gt;Now for the exciting part - creating web servers! Add to &lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# EC2 Instances&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&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;instance_count&lt;/span&gt;

  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amazon_linux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_type&lt;/span&gt;
  &lt;span class="nx"&gt;key_name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key_name&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;user_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
    #!/bin/bash
    dnf update -y
    dnf install -y httpd
    systemctl start httpd
    systemctl enable httpd
    echo "&amp;lt;h1&amp;gt;Terraform Web Server - Instance: $(ec2-metadata --instance-id | cut -d' ' -f2)&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;AZ: $(ec2-metadata --availability-zone | cut -d' ' -f2)&amp;lt;/p&amp;gt;" &amp;gt; /var/www/html/index.html
&lt;/span&gt;&lt;span class="no"&gt;    EOF
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-web-${count.index + 1}"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
    &lt;span class="nx"&gt;Server&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-${count.index + 1}"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Breaking Down the Code:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Count Meta-Argument:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creates multiple instances. With &lt;code&gt;instance_count = 2&lt;/code&gt;, we get 2 servers!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. User Data - The Magic:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;user_data&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
  #!/bin/bash
  yum update -y
  yum install -y httpd
  # ...
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;User data runs automatically when the instance starts. It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updates the system&lt;/li&gt;
&lt;li&gt;Installs Apache web server&lt;/li&gt;
&lt;li&gt;Starts Apache&lt;/li&gt;
&lt;li&gt;Creates a custom HTML page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Dynamic Naming:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-web-${count.index + 1}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;First instance: &lt;code&gt;terraform-web-web-1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Second instance: &lt;code&gt;terraform-web-web-2&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚖️ Step 5: Create Application Load Balancer
&lt;/h2&gt;

&lt;p&gt;The load balancer distributes traffic. Add to &lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Application Load Balancer&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-alb"&lt;/span&gt;
  &lt;span class="nx"&gt;internal&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;load_balancer_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application"&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;subnets&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;enable_deletion_protection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-alb"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Target Group&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-tg"&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;health_check&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;enabled&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;healthy_threshold&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="nx"&gt;unhealthy_threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="nx"&gt;timeout&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="nx"&gt;interval&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
    &lt;span class="nx"&gt;matcher&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-tg"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Target Group Attachment&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_count&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;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;target_id&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# ALB Listener&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_listener"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;load_balancer_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"80"&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;

  &lt;span class="nx"&gt;default_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;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-listener"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;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%2Ffccbk1qksgt48fu3fh3w.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%2Ffccbk1qksgt48fu3fh3w.png" alt=" " width="798" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understanding Load Balancer Components:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Application Load Balancer (ALB):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The main load balancer resource&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;internal = false&lt;/code&gt; - Internet-facing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;load_balancer_type = "application"&lt;/code&gt; - Layer 7 (HTTP/HTTPS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Target Group:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manages the backend servers&lt;/li&gt;
&lt;li&gt;Performs health checks every 30 seconds&lt;/li&gt;
&lt;li&gt;Marks servers healthy after 2 successful checks&lt;/li&gt;
&lt;li&gt;Marks servers unhealthy after 2 failed checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Target Group Attachment:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_count&lt;/span&gt;
&lt;span class="nx"&gt;target_id&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registers each EC2 instance with the target group.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Listener:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Listens on port 80&lt;/li&gt;
&lt;li&gt;Forwards traffic to the target group&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Health Check Flow:&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;ALB → Checks "/" every 30s → Expects HTTP 200 → 
  ✅ Healthy (2 successes) → Receives traffic
  ❌ Unhealthy (2 failures) → No traffic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📤 Step 6: Define Outputs
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;outputs.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# VPC Outputs&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ID of the VPC"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Subnet Output&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"public_subnet_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ID of public subnet"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# EC2 Instance Outputs&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"instance_ids"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"IDs of EC2 instances"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[*].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"instance_public_ips"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Public IPs of EC2 instances"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[*].&lt;/span&gt;&lt;span class="nx"&gt;public_ip&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Load Balancer Outputs&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"alb_dns_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DNS name of the Application Load Balancer"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dns_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"alb_url"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"URL to access the load balancer"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://${aws_lb.main.dns_name}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Security Group Outputs&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"alb_security_group_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ID of ALB security group"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"instance_security_group_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ID of instance security group"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;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%2Fqh3c9hhayj81ajiboh38.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%2Fqh3c9hhayj81ajiboh38.png" alt=" " width="799" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Splat Expression:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[*]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;[*]&lt;/code&gt; gets ALL instance IDs as a list. Super useful with &lt;code&gt;count&lt;/code&gt;!&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Step 7: Set Variable Values
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;terraform.tfvars&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="nx"&gt;project_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-web"&lt;/span&gt;
&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;

&lt;span class="c1"&gt;# Network Configuration&lt;/span&gt;
&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;
&lt;span class="nx"&gt;public_subnet_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;

&lt;span class="c1"&gt;# EC2 Configuration&lt;/span&gt;
&lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t2.micro"&lt;/span&gt;
&lt;span class="nx"&gt;instance_count&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nx"&gt;key_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-key"&lt;/span&gt;

&lt;span class="c1"&gt;# Security - Change this to your IP address&lt;/span&gt;
&lt;span class="nx"&gt;my_ip&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/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%2Faneab1a2uyh07iikqkvj.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%2Faneab1a2uyh07iikqkvj.png" alt=" " width="799" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Important:&lt;/strong&gt; Change &lt;code&gt;my_ip&lt;/code&gt; to your actual IP address for security!&lt;/p&gt;

&lt;p&gt;Find your 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;Then update:&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;my_ip&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"203.0.113.0/32"&lt;/span&gt;  &lt;span class="c1"&gt;# Your IP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🚀 Step 8: Deploy the Infrastructure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create SSH Key Pair (if you don't have one)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create key pair in AWS&lt;/span&gt;
aws ec2 create-key-pair &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key-name&lt;/span&gt; my-key &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'KeyMaterial'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; my-key.pem

&lt;span class="o"&gt;![&lt;/span&gt; &lt;span class="o"&gt;](&lt;/span&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ttadkcyqnuxbsnpwdqs7.png&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="c"&gt;# Set permissions&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;400 my-key.pem
&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%2F5w5onxtbivy5i7f6eju6.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%2F5w5onxtbivy5i7f6eju6.png" alt=" " width="799" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Initialize Terraform
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Initializing the backend...
Initializing provider plugins...
&lt;/span&gt;&lt;span class="gp"&gt;- Finding hashicorp/aws versions matching "~&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;5.0&lt;span class="s2"&gt;"...
&lt;/span&gt;&lt;span class="go"&gt;- Installing hashicorp/aws v5.100.0...

Terraform has been successfully initialized!
&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%2Fcugi4ubz4be7o44aclr5.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%2Fcugi4ubz4be7o44aclr5.png" alt=" " width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Review the Plan
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plan: 14 to add, 0 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;1 VPC&lt;/li&gt;
&lt;li&gt;1 Internet Gateway&lt;/li&gt;
&lt;li&gt;1 Subnet&lt;/li&gt;
&lt;li&gt;1 Route Table + Association&lt;/li&gt;
&lt;li&gt;2 Security Groups&lt;/li&gt;
&lt;li&gt;2 EC2 Instances&lt;/li&gt;
&lt;li&gt;1 Application Load Balancer&lt;/li&gt;
&lt;li&gt;1 Target Group&lt;/li&gt;
&lt;li&gt;2 Target Group Attachments&lt;/li&gt;
&lt;li&gt;1 ALB Listener&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  4. Apply the Configuration
&lt;/h3&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;Type &lt;code&gt;yes&lt;/code&gt; when prompted.&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%2F2ylek3n7tulgcoz3pwbg.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%2F2ylek3n7tulgcoz3pwbg.png" alt=" " width="778" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This will take 3-5 minutes&lt;/strong&gt; because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EC2 instances need to boot&lt;/li&gt;
&lt;li&gt;User data script runs&lt;/li&gt;
&lt;li&gt;ALB needs to provision&lt;/li&gt;
&lt;li&gt;Health checks need to pass&lt;/li&gt;
&lt;/ul&gt;

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




&lt;h2&gt;
  
  
  ✅ Step 9: Test Your Infrastructure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Get the Load Balancer URL
&lt;/h3&gt;



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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://terraform-web-alb-1740709428.us-east-1.elb.amazonaws.com
&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%2Fxmtznr2i4okd2xizw9cc.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%2Fxmtznr2i4okd2xizw9cc.png" alt=" " width="800" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Test in Browser
&lt;/h3&gt;

&lt;p&gt;Open the URL in your browser. You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🚀 Terraform Web Server
Deployed with Infrastructure as Code

Instance ID: i-09305fc7d4638360a
Availability Zone: us-east-1a
Server: 1
&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%2Fakpjfa84z1su2uavux4a.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%2Fakpjfa84z1su2uavux4a.png" alt=" " width="800" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Test Load Balancing
&lt;/h3&gt;

&lt;p&gt;Refresh the page multiple times. You'll see the &lt;strong&gt;Instance ID&lt;/strong&gt; and &lt;strong&gt;Server number&lt;/strong&gt; change - that's the load balancer distributing traffic!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First request  → Server: 1
Second request → Server: 2
Third request  → Server: 1
&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%2F8xsbgk7ketma91mhrisf.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%2F8xsbgk7ketma91mhrisf.png" alt=" " width="800" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Test Individual Instances
&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;# Get instance IPs&lt;/span&gt;
terraform output instance_public_ips

&lt;span class="o"&gt;![&lt;/span&gt; &lt;span class="o"&gt;](&lt;/span&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jbibepofm64vdjwyk9kj.png&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="c"&gt;# Test directly (should work)&lt;/span&gt;
curl http://&amp;lt;instance-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔍 Understanding What Happened
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Traffic Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Request
    ↓
ALB DNS (terraform-web-alb-xxx.elb.amazonaws.com)
    ↓
ALB Listener (Port 80)
    ↓
Target Group (Health Check: ✅)
    ↓
Round-Robin Distribution
    ├─→ EC2 Instance 1 (Apache) → Response
    └─→ EC2 Instance 2 (Apache) → Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Health Check Process
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every 30 seconds:
ALB → GET / → EC2 Instance
    ↓
HTTP 200 OK?
    ├─ Yes (2 times) → Mark Healthy → Send Traffic
    └─ No (2 times)  → Mark Unhealthy → Stop Traffic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧪 Advanced Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Test High Availability
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; What if one server fails?&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;# Get instance IDs&lt;/span&gt;
terraform output instance_ids

&lt;span class="c"&gt;# Stop one instance&lt;/span&gt;
aws ec2 stop-instances &lt;span class="nt"&gt;--instance-ids&lt;/span&gt; i-xxxxx

&lt;span class="c"&gt;# Wait 1 minute for health check to fail&lt;/span&gt;

&lt;span class="c"&gt;# Test the ALB URL - still works!&lt;/span&gt;
curl http://&amp;lt;alb-dns&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ALB automatically stops sending traffic to the unhealthy instance!&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitor Health Status
&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;# Check target health&lt;/span&gt;
aws elbv2 describe-target-health &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-group-arn&lt;/span&gt; &amp;lt;target-group-arn&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"TargetHealthDescriptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"i-xxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&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;"HealthCheckPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"80"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"TargetHealth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"State"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"healthy"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;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;
  
  
  🐛 Troubleshooting Common Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue 1: "InvalidKeyPair.NotFound"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error:&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;Error: creating EC2 Instance: InvalidKeyPair.NotFound
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&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;# List your key pairs&lt;/span&gt;
aws ec2 describe-key-pairs

&lt;span class="c"&gt;# Create if missing&lt;/span&gt;
aws ec2 create-key-pair &lt;span class="nt"&gt;--key-name&lt;/span&gt; my-key &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'KeyMaterial'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; my-key.pem
&lt;span class="nb"&gt;chmod &lt;/span&gt;400 my-key.pem

&lt;span class="c"&gt;# Update terraform.tfvars with correct key name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 2: ALB Shows "503 Service Unavailable"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Instances are unhealthy or still booting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&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;# Wait 2-3 minutes for:&lt;/span&gt;
&lt;span class="c"&gt;# 1. Instances to boot&lt;/span&gt;
&lt;span class="c"&gt;# 2. User data to complete&lt;/span&gt;
&lt;span class="c"&gt;# 3. Health checks to pass&lt;/span&gt;

&lt;span class="c"&gt;# Check target health&lt;/span&gt;
aws elbv2 describe-target-health &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-group-arn&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; target_group_arn&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 3: Can't SSH to Instances
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Security group blocks your IP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&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;# Get your current IP&lt;/span&gt;
curl ifconfig.me

&lt;span class="c"&gt;# Update terraform.tfvars&lt;/span&gt;
my_ip &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"YOUR_IP/32"&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 4: "Subnet must have at least 2 availability zones"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error with ALB:&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;Error: creating ELBv2 Load Balancer: ValidationError
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; ALBs require at least 2 subnets in different AZs. Update &lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add second subnet&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"public_2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;names&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="nx"&gt;map_public_ip_on_launch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-public-subnet-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;# Update ALB subnets&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="nx"&gt;subnets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💰 Cost Breakdown
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Monthly Costs (us-east-1):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Cost/Month&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;t2.micro EC2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;$8.50&lt;/td&gt;
&lt;td&gt;$17.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Load Balancer&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;$16.20&lt;/td&gt;
&lt;td&gt;$16.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Transfer (1GB)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;$0.09&lt;/td&gt;
&lt;td&gt;$0.09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$33.29&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Free Tier Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First 750 hours/month of t2.micro (covers both instances!)&lt;/li&gt;
&lt;li&gt;First 15 GB data transfer out&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actual cost with free tier: ~$16.20/month&lt;/strong&gt; (just the ALB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost Optimization Tips:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use t2.micro (free tier eligible)&lt;/li&gt;
&lt;li&gt;Delete resources when not in use&lt;/li&gt;
&lt;li&gt;Use Network Load Balancer ($10.95/month) if you don't need Layer 7 features&lt;/li&gt;
&lt;li&gt;Consider using Auto Scaling (covered in future articles)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🧹 Cleanup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Don't forget to destroy resources to avoid charges!&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;# Destroy everything&lt;/span&gt;
terraform destroy

&lt;span class="c"&gt;# Type 'yes' when prompted&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%2Fgwb740r1s3n1coq8826s.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%2Fgwb740r1s3n1coq8826s.png" alt=" " width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will delete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Load Balancer&lt;/li&gt;
&lt;li&gt;✅ Target Group&lt;/li&gt;
&lt;li&gt;✅ EC2 Instances&lt;/li&gt;
&lt;li&gt;✅ Security Groups&lt;/li&gt;
&lt;li&gt;✅ VPC and networking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verify deletion:&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;# Check EC2 instances&lt;/span&gt;
aws ec2 describe-instances &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="s2"&gt;"Name=tag:ManagedBy,Values=Terraform"&lt;/span&gt;

&lt;span class="c"&gt;# Check load balancers&lt;/span&gt;
aws elbv2 describe-load-balancers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📚 Key Concepts Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Count Meta-Argument&lt;/strong&gt;
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Creates multiple identical resources. Access with &lt;code&gt;[count.index]&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Data Sources&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_ami"&lt;/span&gt; &lt;span class="s2"&gt;"amazon_linux"&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;Query existing AWS resources instead of hardcoding values.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;User Data&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;user_data&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
  #!/bin/bash
  # Bootstrap script
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automate instance configuration at launch.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Security Group References&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reference one security group from another for layered security.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Splat Expressions&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[*]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get all values from resources created with &lt;code&gt;count&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. &lt;strong&gt;Health Checks&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;health_check&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;
  &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatic monitoring and traffic routing.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Real-World Applications
&lt;/h2&gt;

&lt;p&gt;This architecture is used for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Web Applications&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress sites&lt;/li&gt;
&lt;li&gt;E-commerce platforms&lt;/li&gt;
&lt;li&gt;SaaS applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. API Backends&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;REST APIs&lt;/li&gt;
&lt;li&gt;GraphQL servers&lt;/li&gt;
&lt;li&gt;Microservices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Content Delivery&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static websites&lt;/li&gt;
&lt;li&gt;Media streaming&lt;/li&gt;
&lt;li&gt;File downloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Development Environments&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Staging servers&lt;/li&gt;
&lt;li&gt;Testing environments&lt;/li&gt;
&lt;li&gt;Demo applications&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;In the next article, we'll add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RDS Database&lt;/strong&gt; - Persistent data storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Secrets Manager&lt;/strong&gt; - Secure credential management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private Subnets&lt;/strong&gt; - Enhanced security&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Connection&lt;/strong&gt; - Connect EC2 to RDS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Coming Up:&lt;/strong&gt; &lt;a href="https://dev.tolink"&gt;Article 8: Secure Database Deployment: RDS + Secrets Manager with Terraform&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📖 Additional Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Official Documentation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance" rel="noopener noreferrer"&gt;Terraform AWS Provider - EC2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb" rel="noopener noreferrer"&gt;Terraform AWS Provider - ALB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/" rel="noopener noreferrer"&gt;AWS Application Load Balancer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Related Articles:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.tolink"&gt;Article 6: Building Your First AWS VPC with Terraform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.tolink"&gt;Article 5: Variables and Outputs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/architecture/well-architected/" rel="noopener noreferrer"&gt;AWS Well-Architected Framework&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💬 Questions?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Common Questions:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can I use different instance types for each server?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: Yes! Use &lt;code&gt;for_each&lt;/code&gt; instead of &lt;code&gt;count&lt;/code&gt; for more flexibility (covered in Article 13).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How do I add HTTPS/SSL?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: You'll need an ACM certificate and update the listener to port 443. We'll cover this in a future article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can I deploy to multiple regions?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: Yes! Use Terraform workspaces or separate state files per region.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How do I add auto-scaling?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: Replace EC2 instances with an Auto Scaling Group. We'll cover this in Article 15.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Summary
&lt;/h2&gt;

&lt;p&gt;Today you learned how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Deploy multiple EC2 instances with Terraform&lt;/li&gt;
&lt;li&gt;✅ Use &lt;code&gt;count&lt;/code&gt; to create multiple resources&lt;/li&gt;
&lt;li&gt;✅ Configure security groups for layered security&lt;/li&gt;
&lt;li&gt;✅ Automate server setup with user data&lt;/li&gt;
&lt;li&gt;✅ Create an Application Load Balancer&lt;/li&gt;
&lt;li&gt;✅ Implement health checks and high availability&lt;/li&gt;
&lt;li&gt;✅ Test load balancing in action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You now have production-ready web infrastructure!&lt;/strong&gt; 🎉&lt;/p&gt;




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

&lt;p&gt;Thank you for reading. I hope this article provided practical insights and a clearer understanding of the topic.&lt;/p&gt;

&lt;p&gt;If you found this useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value
&lt;/li&gt;
&lt;li&gt;🦄 Unicorn if you’re applying it today
&lt;/li&gt;
&lt;li&gt;💾 Save it for your next optimization session
&lt;/li&gt;
&lt;li&gt;🔄 Share it with your team
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 What’s Next
&lt;/h2&gt;

&lt;p&gt;More deep dives are coming soon on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Operations&lt;/li&gt;
&lt;li&gt;GenAI &amp;amp; Agentic AI&lt;/li&gt;
&lt;li&gt;DevOps Automation&lt;/li&gt;
&lt;li&gt;Data &amp;amp; Platform Engineering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow along for weekly insights and hands-on guides.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 Portfolio &amp;amp; Work
&lt;/h2&gt;

&lt;p&gt;You can explore my full body of work, certifications, architecture projects, and technical articles here:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit My Website&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Services I Offer
&lt;/h2&gt;

&lt;p&gt;If you're looking for hands-on guidance or collaboration, I provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Architecture Consulting (AWS / Azure)&lt;/li&gt;
&lt;li&gt;DevSecOps &amp;amp; Automation Design&lt;/li&gt;
&lt;li&gt;FinOps Optimization Reviews&lt;/li&gt;
&lt;li&gt;Technical Writing (Cloud, DevOps, GenAI)&lt;/li&gt;
&lt;li&gt;Product &amp;amp; Architecture Reviews&lt;/li&gt;
&lt;li&gt;Mentorship &amp;amp; 1:1 Technical Guidance&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🤝 Let’s Connect
&lt;/h2&gt;

&lt;p&gt;I’d love to hear your thoughts. Feel free to drop a comment or connect with me on:&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For collaborations, consulting, or technical discussions, reach out at:&lt;/p&gt;

&lt;p&gt;📧 &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Found this helpful? Share it with your team.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;⭐ Star the repo • 📖 Follow the series • 💬 Ask questions  &lt;/p&gt;

&lt;p&gt;Made by &lt;strong&gt;Sarvar Nadaf&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
🌐 &lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;https://sarvarnadaf.com&lt;/a&gt;&lt;/p&gt;




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