<?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>Sentry's Span Hierarchy Exposed a Silent Retry in My 5-Agent Pipeline. One Agent Took 22.6s, the Others Took 5.</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Fri, 24 Jul 2026 05:48:09 +0000</pubDate>
      <link>https://dev.to/sarvar_04/sentrys-span-hierarchy-exposed-a-silent-retry-in-my-5-agent-pipeline-one-agent-took-226s-the-fb4</link>
      <guid>https://dev.to/sarvar_04/sentrys-span-hierarchy-exposed-a-silent-retry-in-my-5-agent-pipeline-one-agent-took-226s-the-fb4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;I built an &lt;strong&gt;AWS Security Posture Agent&lt;/strong&gt;: five specialist AI agents that scan your AWS account for security misconfigurations, map findings to CIS benchmarks, score risk, and generate copy-paste fix commands.&lt;/p&gt;

&lt;p&gt;The agents run sequentially on CrewAI with Amazon Bedrock Nova Pro as the LLM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. ResourceDiscovery    → inventories EC2, S3, Lambda, IAM, SGs, API GW, DynamoDB
2. SecurityScanner      → finds open ports, public buckets, admin roles, insecure configs
3. ComplianceChecker    → maps to CIS AWS Foundations Benchmark
4. RiskScorer           → severity × blast radius × exploitability
5. RemediationPlanner   → generates AWS CLI fix commands
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Each agent has custom boto3 tools that make real AWS API calls against a live account with 90 IAM roles, 14 S3 buckets, 9 security groups, and 7 Lambda functions. Not test data. Real findings.&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/aws-security-posture-agent" rel="noopener noreferrer"&gt;
        aws-security-posture-agent
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Multi-agent AI system that scans AWS accounts for security misconfigurations using CrewAI + Amazon Bedrock, instrumented with Sentry AI Agent Monitoring.  5 specialist agents discover resources, analyze security, map to CIS benchmarks, score risk, and generate fix commands.
    &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;AWS Security Posture Agent&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Five AI agents scan your AWS account. Find misconfigurations. Score risk. Get fix commands.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://python.org" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/66fb2689aaa25f80a8be6cff6a545571d5570f8000dd1970752b4b964ba02689/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31322b2d626c75653f7374796c653d666f722d7468652d6261646765" alt="Python"&gt;&lt;/a&gt;
&lt;a href="https://crewai.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/440c0080ea4c75ac2e62ae575be4c3daaa2d05a78cdd27ac2e1efa02a4da2a29/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6372657761692d312e31352d677265656e3f7374796c653d666f722d7468652d6261646765" alt="CrewAI"&gt;&lt;/a&gt;
&lt;a href="https://sentry.io" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d03ce15ed42502b6eb3930cd74ca03f89b591bcce6c0b12ed75a4bbe54b47a84/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f73656e7472792d41492532304d6f6e69746f72696e672d707572706c653f7374796c653d666f722d7468652d6261646765" alt="Sentry"&gt;&lt;/a&gt;
&lt;a href="https://aws.amazon.com/bedrock/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b80c525f9b228dd9a6d73a1f0b83995d6c106f49cabc5dff20ee8a8eafcdfa1a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4157532d426564726f636b2532304e6f766125323050726f2d6f72616e67653f7374796c653d666f722d7468652d6261646765" alt="Bedrock"&gt;&lt;/a&gt;
&lt;a href="https://github.com/simplynadaf/aws-security-posture-agent/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/daa52099573be5a50c320c4387496400f2f722e49f86a42db8d5778130d3582d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e3f7374796c653d666f722d7468652d6261646765" alt="License"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/simplynadaf/aws-security-posture-agent#quick-start" rel="noopener noreferrer"&gt;Quick Start&lt;/a&gt; | &lt;a href="https://github.com/simplynadaf/aws-security-posture-agent#architecture" rel="noopener noreferrer"&gt;Architecture&lt;/a&gt; | &lt;a href="https://github.com/simplynadaf/aws-security-posture-agent#screenshots" rel="noopener noreferrer"&gt;Screenshots&lt;/a&gt; | &lt;a href="https://github.com/simplynadaf/aws-security-posture-agent#performance-bug-fix" rel="noopener noreferrer"&gt;Performance Fix&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;Most AWS accounts accumulate security debt silently. Open SSH ports from testing. S3 buckets without encryption. IAM roles with full admin access that nobody remembers creating. Manual audits miss things. AWS Config rules cost money. SecurityHub is noisy.&lt;/p&gt;
&lt;p&gt;This agent scans your account in 60 seconds, finds real issues, maps them to CIS benchmarks, scores risk, and gives you the exact AWS CLI command to fix each one.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What It Finds&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;On a real AWS account (90 IAM roles, 14 S3 buckets, 9 security groups, 7 Lambda functions):&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;97 security findings
├── CRITICAL: open SSH/RDP from 0.0.0.0/0, IAM users without MFA
├── HIGH:     admin roles, unencrypted EBS, missing public access blocks
├── MEDIUM:   deprecated Lambda runtimes, missing versioning, stale SGs
└──&lt;/code&gt;&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/simplynadaf/aws-security-posture-agent" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h3&gt;
  
  
  Demo
&lt;/h3&gt;

&lt;p&gt;Here's the full scan running against my AWS account. The scan portion is sped up 4x, results walkthrough is at normal speed:&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;My first instinct was to blame Bedrock latency. Every time something is slow you blame the LLM, right?&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;SecurityScanner agent&lt;/strong&gt; was taking 22.6 seconds while every other agent averaged 5-10 seconds. Without visibility into what was happening inside each agent's execution, I would have added &lt;code&gt;time.time()&lt;/code&gt; calls and guessed.&lt;/p&gt;

&lt;p&gt;Sentry's trace waterfall told a different story. The real problem wasn't Python execution time or network latency. It was the LLM getting a context payload it couldn't process cleanly on the first attempt.&lt;/p&gt;

&lt;p&gt;The root cause: my &lt;code&gt;IAMAnalyzer&lt;/code&gt; tool was fetching all 90 IAM roles from the account (59 after filtering service-linked roles), serializing them into a 27KB JSON blob, and handing that entire payload to the LLM as tool output. The context window got overwhelmed. CrewAI's internal retry logic kicked in, burning tokens on a second attempt with even more context.&lt;/p&gt;

&lt;p&gt;One tool. Wrong default. The entire pipeline suffered.&lt;/p&gt;




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

&lt;p&gt;PR with the fix:&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/simplynadaf/aws-security-posture-agent/pull/1" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        fix: paginate IAM analysis and add token budget guard
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#1&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/simplynadaf" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F107028874%3Fv%3D4" alt="simplynadaf avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/simplynadaf" rel="noopener noreferrer"&gt;simplynadaf&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/simplynadaf/aws-security-posture-agent/pull/1" rel="noopener noreferrer"&gt;&lt;time&gt;Jul 20, 2026&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;The IAMAnalyzer tool was fetching every single role in the account (90 of them, 59 after filtering service-linked ones) and dumping the full details into a single JSON blob. That blob hit 26,980 characters. The LLM choked on it, CrewAI retried the task, and the SecurityScanner agent ended up taking 22.6s while every other agent finished in 5-10s.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How I found it&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Added Sentry spans to each agent and tool. The trace waterfall made it obvious: SecurityScanner was twice as wide as everything else. Drilled into the tool spans and saw &lt;code&gt;result_length_chars: 26980&lt;/code&gt; on &lt;code&gt;iam_analyzer&lt;/code&gt; vs ~4000 on the other tools.&lt;/p&gt;
&lt;p&gt;7x output disproportion. That was the problem.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What I changed&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Three things in &lt;code&gt;iam_analyzer.py&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Paginate and sort roles by &lt;code&gt;RoleLastUsed&lt;/code&gt; date, only analyze the top 20 most active ones. The rest are stale roles nobody has touched in months.&lt;/li&gt;
&lt;li&gt;Skip 31 service-linked roles upfront (you cannot modify them anyway, auditing them is noise).&lt;/li&gt;
&lt;li&gt;Token budget guard at the end: if the JSON output exceeds 4000 chars, trim the role_summary down to just names and policy lists.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Numbers&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Tool output: 26,980 chars down to 15,532 (42% smaller)&lt;/li&gt;
&lt;li&gt;SecurityScanner: 22.6s down to 17.8s (21% faster, no more LLM retry)&lt;/li&gt;
&lt;li&gt;API calls to IAM: 59 down to 20&lt;/li&gt;
&lt;li&gt;Total pipeline: 62s down to 57.7s&lt;/li&gt;
&lt;li&gt;Findings: still 27. No coverage loss because the top-20 most active roles contain all the problematic ones (AdminRole, Bedrock-Lambda-Role, etc are all heavily used).&lt;/li&gt;
&lt;/ul&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/simplynadaf/aws-security-posture-agent/pull/1" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The core change lives in &lt;code&gt;src/security_posture/tools/iam_analyzer.py&lt;/code&gt;. Here's the before and after:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (the bug):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Fetches ALL roles without pagination limit
&lt;/span&gt;&lt;span class="n"&gt;roles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_roles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MaxItems&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;role_details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Roles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;role_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RoleName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;role&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;Path&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/aws-service-role/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="c1"&gt;# Analyzes every single role...
&lt;/span&gt;    &lt;span class="n"&gt;attached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_attached_role_policies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RoleName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;role_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...builds massive JSON output
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces 26,980 characters of JSON for an account with 90 roles. The LLM chokes on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After (the fix):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# FIX: Paginate and sort by relevance
&lt;/span&gt;&lt;span class="n"&gt;all_roles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;paginator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_paginator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list_roles&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;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;paginator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;all_roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Roles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Filter service-linked roles (31 roles, can't modify anyway)
&lt;/span&gt;&lt;span class="n"&gt;auditable_roles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_roles&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;r&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;Path&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/aws-service-role/&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;# Sort by last used date (most active first)
&lt;/span&gt;&lt;span class="n"&gt;auditable_roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&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;_last_used_sort_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reverse&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;# Take only top 20 roles
&lt;/span&gt;&lt;span class="n"&gt;roles_to_analyze&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auditable_roles&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;max_roles&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus a token budget guard at the end:&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;# Token budget guard: truncate if output exceeds threshold
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4000&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role_summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="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;role_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;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role_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;policies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&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;attached_policies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;role_details&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Role details truncated to stay within token budget&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three changes. Pagination, relevance sorting, and a safety valve. The SecurityScanner stopped retrying.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Results first:&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;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IAM tool output&lt;/td&gt;
&lt;td&gt;26,980 chars&lt;/td&gt;
&lt;td&gt;15,532 chars&lt;/td&gt;
&lt;td&gt;42% smaller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SecurityScanner time&lt;/td&gt;
&lt;td&gt;22.6s&lt;/td&gt;
&lt;td&gt;17.8s&lt;/td&gt;
&lt;td&gt;21% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IAM API calls&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;66% fewer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total pipeline&lt;/td&gt;
&lt;td&gt;62.0s&lt;/td&gt;
&lt;td&gt;57.7s&lt;/td&gt;
&lt;td&gt;7% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security findings&lt;/td&gt;
&lt;td&gt;97&lt;/td&gt;
&lt;td&gt;97&lt;/td&gt;
&lt;td&gt;No coverage loss&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 21% improvement on the SecurityScanner came from the LLM completing analysis in a single pass instead of retrying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How I found it:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fix itself is simple. The interesting part is how Sentry pointed me straight to it.&lt;/p&gt;

&lt;p&gt;Without the trace waterfall, all I would have seen is "pipeline takes 62 seconds." The trace said otherwise:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wrap each agent execution in a &lt;code&gt;gen_ai.invoke_agent&lt;/code&gt; span&lt;/li&gt;
&lt;li&gt;Wrap each tool call in a &lt;code&gt;gen_ai.execute_tool&lt;/code&gt; span&lt;/li&gt;
&lt;li&gt;Run the pipeline and look at the trace waterfall&lt;/li&gt;
&lt;li&gt;The SecurityScanner span was visually obvious: twice the width of everything else&lt;/li&gt;
&lt;li&gt;Inside it, the &lt;code&gt;iam_analyzer&lt;/code&gt; tool span showed a &lt;code&gt;result_length_chars&lt;/code&gt; of 26,980&lt;/li&gt;
&lt;li&gt;Compare to &lt;code&gt;security_group_analyzer&lt;/code&gt; at 4,200 chars and &lt;code&gt;s3_config_checker&lt;/code&gt; at 3,800 chars&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The disproportion was the clue. If the tool output is 7x larger than its siblings, reduce it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Use of Sentry
&lt;/h2&gt;

&lt;p&gt;I used Sentry's AI Agent Monitoring to instrument a multi-agent CrewAI pipeline from scratch. This isn't a web app or API. It's five autonomous AI agents making LLM calls and executing custom tools. Standard APM wouldn't help here.&lt;/p&gt;




&lt;h3&gt;
  
  
  What I instrumented
&lt;/h3&gt;

&lt;p&gt;Every pipeline run creates a Sentry transaction with this span hierarchy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transaction: "Security Posture Scan" (57s)
├── gen_ai.invoke_agent: ResourceDiscovery
│   └── gen_ai.execute_tool: aws_resource_scanner
├── gen_ai.invoke_agent: SecurityScanner
│   ├── gen_ai.execute_tool: security_group_analyzer
│   ├── gen_ai.execute_tool: s3_config_checker
│   ├── gen_ai.execute_tool: iam_analyzer
│   ├── gen_ai.execute_tool: ec2_security_checker
│   └── gen_ai.execute_tool: lambda_security_checker
├── gen_ai.invoke_agent: ComplianceChecker
├── gen_ai.invoke_agent: RiskScorer
└── gen_ai.invoke_agent: RemediationPlanner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  The instrumentation code
&lt;/h3&gt;

&lt;p&gt;For each agent (in &lt;code&gt;main.py&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;start_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.invoke_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;name&lt;/span&gt;&lt;span class="o"&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;invoke_agent &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agent_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="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;agent_span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;agent_span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.operation.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;invoke_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;agent_span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.agent.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;agent_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;agent_span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.request.model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amazon.nova-pro-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;agent_span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.pipeline.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;security-posture-scan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;task_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;task_obj&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;context&lt;/span&gt;&lt;span class="o"&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;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;task_obj&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;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;agent_span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration_seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elapsed&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="n"&gt;agent_span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output_length_chars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&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;task_output&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each tool (decorator in &lt;code&gt;monitoring.py&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trace_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nd"&gt;@functools.wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;start_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.execute_tool&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;execute_tool &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.tool.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;tool_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result_length_chars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&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="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;findings_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;findings_count&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;findings_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                &lt;span class="nf"&gt;except &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="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="k"&gt;pass&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  What Sentry revealed
&lt;/h3&gt;

&lt;p&gt;The trace waterfall made the bottleneck visually obvious. The SecurityScanner span was nearly twice the width of any other agent, 22.6 seconds while others averaged 5-10s.&lt;/p&gt;

&lt;p&gt;Inside it, the &lt;code&gt;iam_analyzer&lt;/code&gt; tool span showed &lt;code&gt;result_length_chars: 26980&lt;/code&gt; while &lt;code&gt;s3_config_checker&lt;/code&gt; showed 3,800 and &lt;code&gt;security_group_analyzer&lt;/code&gt; showed 4,200. The granularity goes down to individual boto3 calls. Every &lt;code&gt;GetPublicAccessBlock&lt;/code&gt;, &lt;code&gt;ListRoles&lt;/code&gt;, and &lt;code&gt;ListAttachedRolePolicies&lt;/code&gt; shows up as its own span.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before the fix&lt;/strong&gt; (SecurityScanner dominates the trace at 22.6s):&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%2Fwzdc0ay62kyuyaxl61zc.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%2Fwzdc0ay62kyuyaxl61zc.png" alt="Sentry trace waterfall showing SecurityScanner agent dominating the pipeline at 22.6s" width="799" height="348"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzztdwlszlozzao462lyt.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%2Fzztdwlszlozzao462lyt.png" alt="Zoomed span detail showing iam_analyzer tool with result_length_chars 26980" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After the fix&lt;/strong&gt; (all agents proportional, no single bottleneck):&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%2Fdlrq2e536mc6g8qchfyl.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%2Fdlrq2e536mc6g8qchfyl.png" alt="After fix - all agent spans proportional, SecurityScanner no longer the bottleneck" width="799" height="349"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc1cdprqjdblrwprs2wwl.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%2Fc1cdprqjdblrwprs2wwl.png" alt="Sentry transaction overview showing clean 57s pipeline with no retries" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;iam_analyzer&lt;/code&gt; output dropped from 26,980 chars to 15,532, and the SecurityScanner stopped triggering retry logic.&lt;/p&gt;




&lt;h3&gt;
  
  
  Sentry features used
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;How I Used It&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Distributed Tracing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full pipeline trace from start to final report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI Agent Monitoring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gen_ai.invoke_agent&lt;/code&gt; spans for all 5 agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tool Execution Tracing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gen_ai.execute_tool&lt;/code&gt; spans for all 5 boto3 tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom Span Data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Token counts, output sizes, duration, finding counts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error Monitoring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exception capture with &lt;code&gt;sentry_sdk.capture_exception()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Breadcrumbs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent completion events via task callbacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transaction Metadata&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Model name, pipeline config, agent count&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Why this matters for AI agent observability
&lt;/h3&gt;

&lt;p&gt;Standard logging tells you an agent "finished." It doesn't tell you which tool inside which agent returned a 27KB payload that triggered a retry you never asked for.&lt;/p&gt;

&lt;p&gt;With five agents each making their own LLM calls and tool executions, you need span-level visibility. Sentry's &lt;code&gt;gen_ai.invoke_agent&lt;/code&gt; and &lt;code&gt;gen_ai.execute_tool&lt;/code&gt; conventions gave me exactly that. I could see the problem in the trace waterfall before I even looked at the code.&lt;/p&gt;

&lt;p&gt;That's the difference between "add some logging" and actual AI observability.&lt;/p&gt;




&lt;p&gt;If you're running multi-agent pipelines, what's your observability setup? Curious if anyone else has hit similar tool-output-size problems with CrewAI or LangGraph.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The agent found 97 real security findings in my AWS account. The fix is in production.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>ai</category>
      <category>aws</category>
    </item>
    <item>
      <title>4 Silent Failures, 2 Undocumented APIs, and a Container That Crashed Because of a Missing User Directive</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Mon, 20 Jul 2026 18:20:15 +0000</pubDate>
      <link>https://dev.to/sarvar_04/4-silent-failures-2-undocumented-apis-and-a-container-that-crashed-because-of-a-missing-user-1b9n</link>
      <guid>https://dev.to/sarvar_04/4-silent-failures-2-undocumented-apis-and-a-container-that-crashed-because-of-a-missing-user-1b9n</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I spent a week deploying a CrewAI agent to AWS Bedrock AgentCore. The SDK wasn't on PyPI. The error messages were 200 OKs. The container crashed without logs. And the naming regex rejected hyphens without telling me why.&lt;/p&gt;

&lt;p&gt;This is the full debugging trail. Every failure was silent. Every fix required reading source code nobody documented.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Project&lt;/li&gt;
&lt;li&gt;Failure 1: The SDK That Doesn't Exist on PyPI&lt;/li&gt;
&lt;li&gt;Failure 2: The 200 OK That Means Failure&lt;/li&gt;
&lt;li&gt;Failure 3: The Container That Crashed With No Logs&lt;/li&gt;
&lt;li&gt;Failure 4: The Naming Regex Nobody Documented&lt;/li&gt;
&lt;li&gt;The Two-Client Split Nobody Mentions&lt;/li&gt;
&lt;li&gt;What I Learned&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The project
&lt;/h2&gt;

&lt;p&gt;I built a resume-tailoring AI agent with CrewAI and Amazon Bedrock. It takes a job description, analyzes your resume, identifies gaps, and rewrites bullet points to match what the role actually needs.&lt;/p&gt;

&lt;p&gt;Locally it worked perfectly. CrewAI orchestrates the agents, Bedrock Nova Pro handles the LLM calls, and the output is solid. Deploying it to production was the problem.&lt;/p&gt;

&lt;p&gt;AWS launched Bedrock AgentCore in June 2026 as a managed runtime for AI agents. You containerize your agent, push the image, and AgentCore handles scaling, memory, and invocation. Sounds simple.&lt;/p&gt;

&lt;p&gt;It was not simple.&lt;/p&gt;




&lt;h2&gt;
  
  
  Failure 1: The SDK that doesn't exist on PyPI
&lt;/h2&gt;

&lt;p&gt;The docs say to install &lt;code&gt;bedrock-agentcore-client&lt;/code&gt;. I ran:&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;bedrock-agentcore-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It installed successfully. No errors. That's because there's a &lt;strong&gt;placeholder package&lt;/strong&gt; on PyPI with that name. It installs, imports fail silently, and your container builds successfully with a broken dependency inside.&lt;/p&gt;

&lt;p&gt;The real SDK lives in AWS's CodeArtifact registry. You need to configure pip to pull from a private index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws codeartifact login &lt;span class="nt"&gt;--tool&lt;/span&gt; pip &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--domain&lt;/span&gt; amazon-agent-runtimes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository&lt;/span&gt; agent-runtimes-pypi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--domain-owner&lt;/span&gt; 600427722194
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install from there. The PyPI package is a trap. Nobody warns you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hours lost: 3.&lt;/strong&gt; The error only appears at runtime when the container tries to import the module. The build succeeds. The push succeeds. The deployment succeeds. The invocation returns an empty payload.&lt;/p&gt;




&lt;h2&gt;
  
  
  Failure 2: The 200 OK that means failure
&lt;/h2&gt;

&lt;p&gt;After fixing the SDK, I deployed and invoked the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws bedrock-agentcore-control invoke-agent-runtime &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--agent-runtime-id&lt;/span&gt; abc123 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'{"job_description": "..."}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response: HTTP 200. Payload: empty string.&lt;/p&gt;

&lt;p&gt;Not a 500. Not a 400. Not an error message. A successful HTTP response with nothing inside.&lt;/p&gt;

&lt;p&gt;I checked CloudWatch. No logs. I checked the container status. Running. I checked the agent runtime status. Active.&lt;/p&gt;

&lt;p&gt;The problem: my IAM role was missing &lt;code&gt;bedrock:GetAgentRuntime&lt;/code&gt; permission. Without it, the invocation endpoint accepts the request, routes it nowhere, and returns a 200 with an empty body.&lt;/p&gt;

&lt;p&gt;There is no error message. There is no log entry. The service returns success when it fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hours lost: 5.&lt;/strong&gt; I tried different payloads, different content types, different SDK versions, curl vs boto3, synchronous vs streaming. All 200 OK, all empty. The fix was one IAM permission that produces zero error signal when missing.&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;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bedrock:GetAgentRuntime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Failure 3: The container that crashed with no logs
&lt;/h2&gt;

&lt;p&gt;Next failure. Container starts, passes health checks for 30 seconds, then dies. No exception in CloudWatch. No crash log. Status shows "Failed" with no reason.&lt;/p&gt;

&lt;p&gt;I added every logging statement I could think of. Print statements. Structured logging. Exception handlers wrapping every import. Nothing appeared in CloudWatch because the container never got far enough to initialize the logging framework.&lt;/p&gt;

&lt;p&gt;The cause: missing &lt;code&gt;USER 1000&lt;/code&gt; directive in the Dockerfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# This crashes silently&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; .
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "-m", "my_agent"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# This works&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; 1000 agentuser
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; .
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; 1000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "-m", "my_agent"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AgentCore requires the container to run as UID 1000. If it doesn't, the runtime kills the container. The error message in the console says "Failed." Just "Failed." No mention of user directives, permissions, or UID requirements.&lt;/p&gt;

&lt;p&gt;I found this by reading the AgentCore team's GitHub sample repos. Not the docs. The sample Dockerfile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hours lost: 4.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Failure 4: The naming regex nobody documented
&lt;/h2&gt;

&lt;p&gt;I wanted to name my agent runtime &lt;code&gt;resume-tailor-agent&lt;/code&gt;. Deployment failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;An error occurred (ValidationException): 
  Name must match pattern: ^[a-zA-Z0-9_]+$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No hyphens allowed. Fine. I renamed to &lt;code&gt;resume_tailor_agent&lt;/code&gt; and moved on.&lt;/p&gt;

&lt;p&gt;But the error message only appears if you use the control plane client. If you use the console, it just... doesn't submit. No red border, no error toast, no validation message. The button does nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hours lost: 1.&lt;/strong&gt; Small one, but the pattern: silent failures.&lt;/p&gt;




&lt;h2&gt;
  
  
  The two-client split nobody mentions
&lt;/h2&gt;

&lt;p&gt;Here's where it gets architectural. AgentCore has TWO Python clients:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;bedrock-agentcore-control&lt;/code&gt; for managing runtimes (create, update, delete)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bedrock-agentcore&lt;/code&gt; for the runtime SDK (what runs inside your container)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The documentation uses both interchangeably. Code samples import from one in the setup section and the other in the invocation section. They have different install paths, different CodeArtifact repositories, and different API surfaces.&lt;/p&gt;

&lt;p&gt;If you install the wrong one, nothing tells you. Your code runs until it hits an import that doesn't exist in the package you installed. And since both packages have overlapping module names in some versions, the error might be an &lt;code&gt;AttributeError&lt;/code&gt; deep in a function call, not a clean &lt;code&gt;ImportError&lt;/code&gt; at the top.&lt;/p&gt;

&lt;p&gt;I mapped out which client does what:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Client&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Install From&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bedrock-agentcore-control&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create/manage runtimes&lt;/td&gt;
&lt;td&gt;CodeArtifact (domain: amazon-agent-runtimes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bedrock-agentcore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runtime SDK (inside container)&lt;/td&gt;
&lt;td&gt;CodeArtifact (same domain)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;boto3&lt;/code&gt; (bedrock-agent)&lt;/td&gt;
&lt;td&gt;Invoke from outside&lt;/td&gt;
&lt;td&gt;Standard pip&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table doesn't exist in any documentation I found.&lt;/p&gt;




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

&lt;p&gt;Five days of debugging. Four distinct silent failures. Zero useful error messages.&lt;/p&gt;

&lt;p&gt;Every single problem shared the same pattern: the system accepted the bad input, returned success, and failed somewhere downstream without signaling what went wrong. The 200 OK that means failure. The build that succeeds with a placeholder SDK. The container that crashes without logs.&lt;/p&gt;

&lt;p&gt;If I'd had Sentry in the container from day one, I would have caught the import failure, the UID crash, and the empty response pattern within hours instead of days. Observability isn't optional for agent deployments. The infrastructure actively hides failures from you.&lt;/p&gt;

&lt;p&gt;Three principles I'm carrying forward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Never trust a 200 OK from a new service.&lt;/strong&gt; Validate the response body. If it's empty, something broke silently upstream.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Test imports at container startup, before anything else.&lt;/strong&gt; A try/except around every critical import with an explicit log line. If the SDK is fake, you'll know in the first second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Read the sample repos, not just the docs.&lt;/strong&gt; The Dockerfile in AWS's example repo had &lt;code&gt;USER 1000&lt;/code&gt;. The documentation never mentioned it. The sample code is sometimes the real documentation.&lt;/p&gt;

&lt;p&gt;I've since added Sentry to my agent pipeline for my security posture scanner project. The trace waterfalls catch problems in seconds that would have taken me hours with print statements. Lesson learned the hard way.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All errors described above are from July 2026 on AgentCore's GA release. Some may be fixed by the time you read this. The patterns of silent failure in new AWS services are probably eternal.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>ai</category>
      <category>aws</category>
    </item>
    <item>
      <title>Introducing AWS SimuLearn Badges: Free Proof That You Can Actually Build in the Cloud</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Fri, 17 Jul 2026 15:47:29 +0000</pubDate>
      <link>https://dev.to/aws-builders/introducing-aws-simulearn-badges-free-proof-that-you-can-actually-build-in-the-cloud-kj6</link>
      <guid>https://dev.to/aws-builders/introducing-aws-simulearn-badges-free-proof-that-you-can-actually-build-in-the-cloud-kj6</guid>
      <description>&lt;p&gt;If someone asked me ten years ago what it takes to break into cloud, I would have said "get certified and hope someone gives you a chance."&lt;/p&gt;

&lt;p&gt;I was wrong. And I watched dozens of freshers follow that exact advice, collect a certification, then sit in interviews unable to explain why they chose one architecture over another.&lt;/p&gt;

&lt;p&gt;The problem was never knowledge. It was proof. Proof that you can gather requirements from a confused client, design something that works, and actually build it with your own hands.&lt;/p&gt;

&lt;p&gt;AWS just launched something that helps close that gap. And two of these credentials cost nothing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What AWS SimuLearn Badges Actually Are&lt;/li&gt;
&lt;li&gt;Why This Matters More Than Another Certification&lt;/li&gt;
&lt;li&gt;The 12 Badges Available Right Now&lt;/li&gt;
&lt;li&gt;The Free Starting Path I Would Follow Today&lt;/li&gt;
&lt;li&gt;The LinkedIn Advantage Nobody Is Talking About&lt;/li&gt;
&lt;li&gt;For Career Switchers: Your Existing Skills Are the Cheat Code&lt;/li&gt;
&lt;li&gt;My Honest Take After 10 Years in Cloud&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What AWS SimuLearn Badges Actually Are
&lt;/h2&gt;

&lt;p&gt;SimuLearn is not another video course. Not another multiple-choice exam.&lt;/p&gt;

&lt;p&gt;You sit in a simulated client meeting powered by generative AI. A virtual customer explains their business problem. You ask questions, uncover requirements, handle objections, and propose an architecture. The AI evaluates your communication, your technical accuracy, and your decision-making in real time.&lt;/p&gt;

&lt;p&gt;Then you build the solution. In a live AWS environment. Not a sandbox with three buttons. The real console.&lt;/p&gt;

&lt;p&gt;After that, an automated validation confirms your solution actually works.&lt;/p&gt;

&lt;p&gt;Complete every assignment in a learning plan, and AWS issues you a badge through Credly. Automatically. No exam booking. No proctored test. Just demonstrated capability across the full workflow.&lt;/p&gt;

&lt;p&gt;Each badge represents the entire journey: customer conversations, architecture design, hands-on building, and validated outcomes. Not a single quiz. Not one lab. The whole thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters More Than Another Certification
&lt;/h2&gt;

&lt;p&gt;I hold 7 AWS certifications. Let me tell you what they prove: I can answer questions about cloud services under time pressure.&lt;/p&gt;

&lt;p&gt;What they do NOT prove: that I can sit with a client, understand their problem, design something appropriate, and build it without breaking things.&lt;/p&gt;

&lt;p&gt;That gap is exactly where freshers struggle. You study for months, pass the exam, and then face an interviewer who asks "Tell me about a time you designed a solution for a customer." You have nothing.&lt;/p&gt;

&lt;p&gt;Last year I sat across from a candidate with three AWS certifications. I asked him to walk me through a time he gathered requirements from a non-technical stakeholder. Thirty seconds of silence. Three certs, zero stories. That is the gap SimuLearn closes.&lt;/p&gt;

&lt;p&gt;Here is how the credential types compare:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certifications&lt;/strong&gt; prove you KNOW it. Knowledge of services, best practices, theory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microcredentials&lt;/strong&gt; prove you can DO it. Specific hands-on skills in a live environment. No multiple choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SimuLearn Badges&lt;/strong&gt; prove you can DELIVER it. The full cycle from client conversation to working solution.&lt;/p&gt;

&lt;p&gt;For someone with zero professional cloud experience, that third one is useful. It gives you something concrete to discuss in interviews. "I gathered requirements from a retail client who needed personalized recommendations, designed a serverless pipeline, and built it in the console." That sentence alone makes you more memorable than someone who just lists a cert.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 12 Badges Available Right Now
&lt;/h2&gt;

&lt;p&gt;Two of these are completely free. No subscription. No credit card. Nothing.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://skillbuilder.aws/learning-plan/EKHCUEWUUC/aws-simulearn-cloud-practitioner/1UQVR262ZB" rel="noopener noreferrer"&gt;AWS SimuLearn Cloud Practitioner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skillbuilder.aws/learning-plan/3HCD821CNZ/aws-simulearn-ai-practitioner/BFKGA5VM8H" rel="noopener noreferrer"&gt;AWS SimuLearn AI Practitioner&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With Skill Builder subscription ($29/month or $299/year):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solutions Architect&lt;/li&gt;
&lt;li&gt;Serverless Developer&lt;/li&gt;
&lt;li&gt;AI Architect&lt;/li&gt;
&lt;li&gt;Machine Learning&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Networking&lt;/li&gt;
&lt;li&gt;Data Analytics&lt;/li&gt;
&lt;li&gt;Healthcare&lt;/li&gt;
&lt;li&gt;Manufacturing and Automotive&lt;/li&gt;
&lt;li&gt;Financial Services&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%2Fn4gr10i0ep8s5qcajmgf.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%2Fn4gr10i0ep8s5qcajmgf.png" alt="All 12 AWS SimuLearn Learning Plan Badges" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The free ones cover cloud fundamentals and AI. The two hottest entry points in cloud right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Free Starting Path I Would Follow Today
&lt;/h2&gt;

&lt;p&gt;If I were starting from zero in 2026, here is exactly what I would do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Month 1-2: Build the foundation (cost: $0)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Start with the &lt;a href="https://skillbuilder.aws/learning-plan/EKHCUEWUUC/aws-simulearn-cloud-practitioner/1UQVR262ZB" rel="noopener noreferrer"&gt;SimuLearn Cloud Practitioner&lt;/a&gt; learning plan. Take your time. The platform offers two modes: Scripted mode guides you through the conversation with prompts, and Open Dialogue mode lets you drive the meeting yourself. Begin with Scripted to see how a proper client conversation flows, then switch to Open Dialogue once you feel ready.&lt;/p&gt;

&lt;p&gt;The customer conversations will feel awkward at first. That is normal. Real client meetings are awkward too. You are practicing that discomfort in a safe environment.&lt;/p&gt;

&lt;p&gt;When you finish, you have your first verified badge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Month 3: Add the AI layer (cost: $0)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Complete the AI Practitioner learning plan. Generative AI shows up in most cloud job descriptions now. This badge tells employers you understand how AI fits into cloud architecture. Not just theory. You built something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Month 4: Take the certification exam (cost: $100)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now take the Cloud Practitioner certification. You already have hands-on practice from SimuLearn. The exam covers some ground you have already worked through, though it also tests pricing models, support plans, and global infrastructure that SimuLearn may not cover in depth. Study those gaps, but you will not be starting from zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total investment: $100 and four months.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What you have at the end: 2 SimuLearn badges and 1 certification. Three verified AWS credentials on your LinkedIn profile. All proving different things. All verifiable by any recruiter who clicks the link.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt; AWS also made microcredentials free in April 2026. Four are available: Serverless, Agentic AI, Application Networking, and Incident Response. These are pure hands-on assessments. If you want to stack more credentials after your certification, these are worth looking at.&lt;/p&gt;

&lt;p&gt;Many people spend those same four months watching tutorials and collecting notes they never revisit. This path gives you something verifiable at the end of each month.&lt;/p&gt;




&lt;h2&gt;
  
  
  The LinkedIn Advantage Nobody Is Talking About
&lt;/h2&gt;

&lt;p&gt;Every badge issued through Credly becomes a verified credential on LinkedIn. Not a self-declared skill. Not a line on your resume anyone could type. A verified badge that links back to exactly what you did to earn it. Anyone can click it and confirm it is real.&lt;/p&gt;

&lt;p&gt;When a recruiter clicks your badge, they see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who issued it (Amazon Web Services)&lt;/li&gt;
&lt;li&gt;What it represents (full learning plan completion)&lt;/li&gt;
&lt;li&gt;What skills it validates (specific, detailed)&lt;/li&gt;
&lt;li&gt;When you earned it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This cannot be faked. That matters when you have no work experience to reference.&lt;/p&gt;

&lt;p&gt;When you share a badge as a LinkedIn post, your network sees it. People engage with achievement posts. That engagement puts your name in front of people who might not have found you otherwise. For freshers with a small network, a badge post can be good visibility.&lt;/p&gt;

&lt;p&gt;How effective is this model? AWS says Cloud Quest badge earners share their accomplishments "at rates well above industry benchmarks." Across the broader AWS Education Programs (Academy, Educate, re/Start), Credly's survey data shows 99% of badge earners consider them valuable, 42% got a new job, and 85% received a raise, promotion, or new role. Those programs include full training and career support, not just a badge. SimuLearn follows the same Credly model, but these numbers are not guaranteed outcomes for every badge earner.&lt;/p&gt;

&lt;p&gt;Verified badges on your profile send a signal: this person is actively investing in cloud skills, and they can back it up.&lt;/p&gt;




&lt;h2&gt;
  
  
  For Career Switchers: Your Existing Skills Are the Cheat Code
&lt;/h2&gt;

&lt;p&gt;If you are moving into cloud from another field, SimuLearn gives you something certifications never could: a way to leverage what you already know.&lt;/p&gt;

&lt;p&gt;Coming from sales or consulting? The customer conversation component is YOUR strength. You already know how to ask questions, handle objections, and translate technical concepts into business value. SimuLearn lets you combine that with new technical skills and prove both in one credential.&lt;/p&gt;

&lt;p&gt;Coming from healthcare, manufacturing, or finance? Look at those industry-specific learning plans. A SimuLearn Healthcare badge tells employers: "I understand compliance, I understand the domain problems, AND I can build cloud solutions for them." I have seen people transition from clinical roles into cloud at consulting clients. The combination of domain knowledge plus technical proof is what gets them past the resume screen when pure technologists cannot.&lt;/p&gt;

&lt;p&gt;The subscription costs $29/month. Subscribe for three months, earn your role-specific or industry-specific badge, cancel. You spent $87 total. Compare that to a bootcamp at $10K or a master's degree at $40K.&lt;/p&gt;

&lt;p&gt;That $87 gets you a verified AWS credential on LinkedIn, hands-on experience in a live console, and client simulation practice you can reference in every interview. For a career pivot, that is a solid starting investment. One honest note: the paid badges are newer to the market than certifications. Not every recruiter will recognize them yet. But the hands-on experience you gain is yours regardless of badge recognition.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Honest Take After 10 Years in Cloud
&lt;/h2&gt;

&lt;p&gt;I have not personally completed a SimuLearn learning plan yet. Full disclosure. I also do not know how long each plan takes to finish. AWS does not publish that number anywhere I could find. Could be 10 hours, could be 60. That matters if you are working full time and planning your evenings around this.&lt;/p&gt;

&lt;p&gt;What I do know: SimuLearn badges are the first AWS credential that evaluates both your communication skills and your technical ability in one package. That combination is what the actual job requires. Every architecture review I have ever been in required both.&lt;/p&gt;

&lt;p&gt;But let me be honest about limitations too. This is still a simulation, not real client work. A hiring manager who has never heard of SimuLearn might not know what the badge means yet. These are new, launched July 2026. It will take time for the market to recognize them the way it recognizes AWS certifications.&lt;/p&gt;

&lt;p&gt;These badges will not replace certifications. Hiring managers still filter by cert. But they fill a gap that certifications cannot: evidence that you practiced the full job, not just the exam.&lt;/p&gt;

&lt;p&gt;If you are a fresher reading this, your path is clearer than it was a year ago. Start with the free plans. Build the habit of doing, not just studying. Earn something verifiable before you spend a single dollar on an exam.&lt;/p&gt;

&lt;p&gt;The old way: study, memorize, pass, hope.&lt;/p&gt;

&lt;p&gt;The new way: build, prove, share, get noticed.&lt;/p&gt;

&lt;p&gt;Start here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://skillbuilder.aws/learning-plan/EKHCUEWUUC/aws-simulearn-cloud-practitioner/1UQVR262ZB" rel="noopener noreferrer"&gt;AWS SimuLearn Cloud Practitioner&lt;/a&gt; (free)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://skillbuilder.aws/learning-plan/3HCD821CNZ/aws-simulearn-ai-practitioner/BFKGA5VM8H" rel="noopener noreferrer"&gt;AWS SimuLearn AI Practitioner&lt;/a&gt; (free)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Have you tried SimuLearn yet? What was your experience with the customer simulation part? I am genuinely curious whether the Open Dialogue conversations feel realistic or scripted. Drop your thoughts below.&lt;/p&gt;

&lt;p&gt;If this helped clarify the credential landscape, a ❤️ helps other beginners find it too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Follow me for more on AWS architecture, DevOps, and AI tooling:&lt;/em&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;sarvarnadaf.com&lt;/a&gt; | &lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; | &lt;a href="https://dev.to/sarvar_04"&gt;Dev.to&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>career</category>
      <category>beginners</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Kiro CLI Gets Worse After an Hour. Here's How I Fixed It.</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Thu, 16 Jul 2026 14:37:01 +0000</pubDate>
      <link>https://dev.to/aws-builders/kiro-cli-gets-worse-after-an-hour-heres-how-i-fixed-it-1l7g</link>
      <guid>https://dev.to/aws-builders/kiro-cli-gets-worse-after-an-hour-heres-how-i-fixed-it-1l7g</guid>
      <description>&lt;p&gt;Two hours into a Kiro CLI session, it gave me a generic IAM trust policy example. We had literally set up the OIDC provider together 30 minutes earlier. It forgot.&lt;/p&gt;

&lt;p&gt;This is not a bug. It has a name: context rot. And once I understood the mechanics, I stopped fighting it and started managing it.&lt;/p&gt;

&lt;p&gt;I use Kiro CLI every day. Checking CloudWatch alarms, debugging IAM policies, reviewing security groups, looking at cost reports, setting up infrastructure. It is my go-to tool for anything AWS.&lt;/p&gt;

&lt;p&gt;But after a few weeks I noticed the pattern. The longer I keep a session going, the worse Kiro gets. Early in the session it is sharp. Finds the right file, gives the right answer, runs the right command. Two hours later it starts doing odd things. It re-reads files it already looked at. It gives vague answers. It suggests things I already tried.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually happens when context fills up
&lt;/h2&gt;

&lt;p&gt;Kiro has up to a 200k token context window (depending on model). Every message you send, every response it gives, every file it reads, every command output: all of it goes into that window. It adds up fast.&lt;/p&gt;

&lt;p&gt;A typical morning for me looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Check if there are any CloudWatch alarms firing in us-east-1"&lt;/li&gt;
&lt;li&gt;"Show me the IAM policy attached to the lambda-processor role"&lt;/li&gt;
&lt;li&gt;"Why is this S3 bucket policy denying access from the VPC endpoint"&lt;/li&gt;
&lt;li&gt;"Look at the cost explorer data for the last 7 days"&lt;/li&gt;
&lt;li&gt;"What EC2 instances are running in the staging account"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those triggers multiple tool calls. Kiro reads files, runs AWS CLI commands, processes the output. By the fifth question, the context window has maybe 30-40% filled.&lt;/p&gt;

&lt;p&gt;Sounds fine. That's the trap.&lt;/p&gt;

&lt;p&gt;I keep going. I debug a CloudFormation deployment. I check a few security groups. I look at some ECS task definitions. By lunch, I am at 60-70% context usage and Kiro starts struggling.&lt;/p&gt;

&lt;p&gt;What struggling looks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It runs &lt;code&gt;aws sts get-caller-identity&lt;/code&gt; again even though it already knows which account I am in&lt;/li&gt;
&lt;li&gt;It re-reads the same terraform files it read an hour ago&lt;/li&gt;
&lt;li&gt;Responses get longer and less useful (more filler, less action)&lt;/li&gt;
&lt;li&gt;It starts suggesting solutions to problems I already fixed earlier in the session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you manage 5+ AWS accounts across environments, context fills 3x faster because every &lt;code&gt;describe-instances&lt;/code&gt; call across accounts dumps more output into the window.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this happens (and why "try harder" does not fix it)
&lt;/h2&gt;

&lt;p&gt;The model can see everything in the context window but it cannot focus on everything equally. Research calls this the "Lost in the Middle" problem. A Stanford paper (Liu et al., 2023) demonstrated that LLMs exhibit a U-shaped attention pattern. They attend strongly to information at the beginning and end of their context window. Everything in the middle gets reduced attention.&lt;/p&gt;

&lt;p&gt;A 2025 follow-up study found the effect is strongest when inputs fill up to 50% of the context window. Beyond that, primacy bias weakens and recency bias dominates, meaning the model increasingly favors whatever you talked about in the last few minutes over the rules and context from the start of your session.&lt;/p&gt;

&lt;p&gt;The Kiro team calls this phenomenon "context rot." Anthropic's own engineering docs define it directly: "Context must be treated as a finite resource with diminishing marginal returns."&lt;/p&gt;

&lt;p&gt;Here is what that means in practice. When the window is full of old conversations about IAM policies, cost reports, and security groups, and you ask about an ECS deployment, the model has to sort through all that irrelevant context to find what matters. It cannot selectively ignore the old stuff. Every token participates in every inference step, regardless of whether it is relevant to your current question.&lt;/p&gt;

&lt;p&gt;The really frustrating part: the model does not know it is degraded. It will confidently tell you it is following all your instructions while demonstrably not doing so. You cannot fix this by telling it to "try harder" or "pay more attention." The degradation is architectural, not motivational.&lt;/p&gt;

&lt;p&gt;When the context hits 100%, Kiro auto-compacts. It summarizes the old conversation to make room. The problem is that summaries lose detail. That IAM policy you debugged earlier? After compaction, Kiro only remembers "we fixed an IAM issue," not the specific policy ARN or the condition key that was wrong. In measured sessions, auto-compaction can reduce 132,000 tokens to about 2,300, a 98% reduction. The token count is small. The cognitive capital destroyed is enormous.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 5 failure modes (in order of how often I hit them)
&lt;/h2&gt;

&lt;p&gt;Context rot is the foundation, but it cascades into other behaviors. Once you recognize these patterns, you stop blaming yourself or the model:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Context rot itself.&lt;/strong&gt; Attention on your early instructions fades as the window fills. The model stops following rules it was given at the start. You told it "never modify production resources without asking first" in minute 1. In minute 90, it just runs the command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Pattern matching over reasoning.&lt;/strong&gt; Instead of checking what actually exists, Kiro copies patterns from earlier in the conversation. It writes an IAM role that duplicates one you already created three prompts ago. It is coding from its conversation context, not from your actual files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Completion bias.&lt;/strong&gt; When you push back on a bad result, the model does not get more careful. It gets more agreeable. It ships something that looks different but is equally wrong. The angrier you get, the faster it produces plausible-looking garbage to end the conflict.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Re-verification loops.&lt;/strong&gt; The model forgets it already verified something. It calls &lt;code&gt;aws sts get-caller-identity&lt;/code&gt; again. It re-reads your terraform state file. It asks "which region are you working in?" for the third time. Each redundant call wastes context and accelerates the rot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Advisory compliance decay.&lt;/strong&gt; Early in a session, Kiro respects guardrails: "I'll ask before modifying production resources." Late in a session, it rationalizes around them: "Since we've been working in this account, I'll go ahead and apply the change." The constraint fades because the instruction that set it is now buried in the middle of a bloated context window.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I do now
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One task per session
&lt;/h3&gt;

&lt;p&gt;This is the biggest change. I used to keep one session open all day. Now I start a new session for each distinct task.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debugging a CloudFormation stack failure? New session.&lt;/li&gt;
&lt;li&gt;Checking cost reports? New session.&lt;/li&gt;
&lt;li&gt;Writing a new Lambda function? New session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each session stays focused, context stays small, and Kiro stays sharp. Each wasted minute re-explaining context is a minute I am not solving the actual problem. Over a week, that adds up to 30-60 minutes of lost productivity.&lt;/p&gt;

&lt;p&gt;To start a new session in Kiro CLI, just exit and re-enter:&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;exit
&lt;/span&gt;kiro-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a completely fresh session. The old one stays on disk if you need to go back to it.&lt;/p&gt;

&lt;p&gt;If you just want to reset the conversation without leaving the process, use &lt;code&gt;/clear&lt;/code&gt;:&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;/clear
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference: &lt;code&gt;exit&lt;/code&gt; + &lt;code&gt;kiro-cli&lt;/code&gt; starts a brand new session with a new ID. &lt;code&gt;/clear&lt;/code&gt; wipes the conversation but keeps the same process running.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use /compact before it auto-triggers
&lt;/h3&gt;

&lt;p&gt;If I am in a session that has been going for a while and I am not done yet, I run &lt;code&gt;/compact&lt;/code&gt; manually before Kiro does it automatically.&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;/compact
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Because when I trigger it myself, I know what just got summarized. I can immediately re-state the important bits.&lt;/p&gt;

&lt;p&gt;After compacting, re-state the important context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;We are debugging a CloudFormation stack called prod-api-gateway that failed 
on an AWS::ApiGateway::RestApi resource. The error was "Invalid stage identifier specified".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Kiro has a fresh context with only the relevant details. The same question that took 12 seconds before compaction takes 3 seconds after.&lt;/p&gt;

&lt;h3&gt;
  
  
  Put persistent context in steering files (the biggest token saver)
&lt;/h3&gt;

&lt;p&gt;Things I tell Kiro every single session (which AWS accounts I work with, naming conventions, preferred regions) go in steering files. Kiro supports three scopes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workspace steering&lt;/strong&gt; (&lt;code&gt;.kiro/steering/&lt;/code&gt;), applies to one 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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .kiro/steering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# .kiro/steering/aws-environment.md&lt;/span&gt;

&lt;span class="gu"&gt;## AWS Environment&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Production account: 111111111111 (us-east-1)
&lt;span class="p"&gt;-&lt;/span&gt; Staging account: 222222222222 (us-east-1)
&lt;span class="p"&gt;-&lt;/span&gt; Default profile: production
&lt;span class="p"&gt;-&lt;/span&gt; All infrastructure is in Terraform under /infra

&lt;span class="gu"&gt;## Preferences&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use AWS CLI v2 commands
&lt;span class="p"&gt;-&lt;/span&gt; Always specify --region explicitly
&lt;span class="p"&gt;-&lt;/span&gt; Check CloudTrail before making IAM changes
&lt;span class="p"&gt;-&lt;/span&gt; Never modify production resources without asking first
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Global steering&lt;/strong&gt; (&lt;code&gt;~/.kiro/steering/&lt;/code&gt;), applies to ALL your workspaces:&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;# ~/.kiro/steering/aws-defaults.md&lt;/span&gt;

&lt;span class="gu"&gt;## Global AWS Conventions&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Always use --output json for parseable results
&lt;span class="p"&gt;-&lt;/span&gt; Prefer least-privilege IAM policies (never use &lt;span class="err"&gt;*&lt;/span&gt; in Resource)
&lt;span class="p"&gt;-&lt;/span&gt; Tag all resources with Environment, Team, and CostCenter
&lt;span class="p"&gt;-&lt;/span&gt; Use ap-south-1 as default region unless specified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Global steering is loaded in every session regardless of which project you are in. If workspace steering conflicts with global steering, workspace wins. This is where I put my org-wide standards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team steering&lt;/strong&gt;: you can push steering files to your entire team via MDM or a shared repository. Everyone gets the same conventions without explaining them:&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;# Team lead pushes to shared repo&lt;/span&gt;
git clone internal-repo/kiro-steering ~/.kiro/steering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kiro automatically loads everything in &lt;code&gt;.kiro/steering/&lt;/code&gt; at the start of every session. Verify what is loaded with &lt;code&gt;/context show&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The difference this makes: my baseline context went from wasting 15-20% on repeated instructions to 0%. Every new session already knows my environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use conditional steering with fileMatch
&lt;/h3&gt;

&lt;p&gt;Not all context is needed all the time. Kiro supports inclusion modes that load steering only when relevant:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;inclusion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fileMatch&lt;/span&gt;
&lt;span class="na"&gt;glob&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;infra/**/*.tf"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Terraform Conventions&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use modules for all repeatable infrastructure
&lt;span class="p"&gt;-&lt;/span&gt; State stored in S3 with DynamoDB locking
&lt;span class="p"&gt;-&lt;/span&gt; Never hardcode AMI IDs, use data sources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file only loads when Kiro reads a &lt;code&gt;.tf&lt;/code&gt; file under the &lt;code&gt;infra/&lt;/code&gt; directory. When I am working on Lambda code, these Terraform conventions do not eat my context window.&lt;/p&gt;

&lt;p&gt;Three modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;always&lt;/strong&gt; (default): loaded every session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fileMatch&lt;/strong&gt;: loaded only when a matching file is in context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;manual&lt;/strong&gt;: loaded only when you invoke it with a slash command&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I split my steering into 5 focused files with appropriate modes instead of one giant file. Token usage for steering dropped from 8% of my context to about 3%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Watch the percentage
&lt;/h3&gt;

&lt;p&gt;Kiro shows context usage in the sidebar. You can also check it explicitly:&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;/context show
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows a breakdown: how much your steering files use, how much tools use, how much your conversation uses. My rule of thumb:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under 40%: keep going, no issues&lt;/li&gt;
&lt;li&gt;40-60%: finish current task, then start fresh&lt;/li&gt;
&lt;li&gt;Over 60%: responses are getting worse, wrap up now&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you find yourself repeating instructions or Kiro starts re-reading files, do not fight it. Start fresh.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use knowledge bases for large reference material
&lt;/h3&gt;

&lt;p&gt;If you work with big Terraform repos or lots of CloudFormation templates, do not add them as context files. They will eat your context window on every single request, even when you are not asking about them.&lt;/p&gt;

&lt;p&gt;Kiro's &lt;code&gt;/knowledge&lt;/code&gt; feature (experimental, enable it first) indexes your files locally and only pulls in relevant chunks when you ask about them:&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;# Enable the feature&lt;/span&gt;
kiro-cli settings chat.enableKnowledge &lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Add your infrastructure code&lt;/span&gt;
/knowledge add &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"terraform-infra"&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; /path/to/infra &lt;span class="nt"&gt;--include&lt;/span&gt; &lt;span class="s2"&gt;"**/*.tf"&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s2"&gt;".terraform/**"&lt;/span&gt;

&lt;span class="c"&gt;# Add documentation&lt;/span&gt;
/knowledge add &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"runbooks"&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; /path/to/runbooks &lt;span class="nt"&gt;--include&lt;/span&gt; &lt;span class="s2"&gt;"**/*.md"&lt;/span&gt; &lt;span class="nt"&gt;--index-type&lt;/span&gt; Best
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two index types matter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast&lt;/strong&gt; (lexical/bm25): for code, configs, logs. Quick keyword matching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best&lt;/strong&gt; (semantic): for documentation and runbooks. Understands natural language queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use Fast for my Terraform code (I search by resource names and module names) and Best for our runbooks and architecture decision records (I search by questions like "how do we handle cross-account access").&lt;/p&gt;

&lt;p&gt;My baseline context dropped from 35% to 12% after moving Terraform files out of steering and into the knowledge base. Kiro searches your files when needed without them sitting in context all the time.&lt;/p&gt;

&lt;p&gt;The knowledge base persists across sessions. Index once, use forever. Run &lt;code&gt;/knowledge update&lt;/code&gt; periodically when your files change, or &lt;code&gt;/knowledge update&lt;/code&gt; with no arguments to re-index everything at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delegate exploration to sub-agents
&lt;/h3&gt;

&lt;p&gt;This one is underused. When Kiro needs to investigate something (scan a directory, read multiple files, research an error) it can delegate to a sub-agent with its own isolated context window.&lt;/p&gt;

&lt;p&gt;Why this matters: if the main session reads 20 files to debug an issue, all 20 files stay in your context forever. If a sub-agent does that exploration, it returns a focused summary and your main context stays clean.&lt;/p&gt;

&lt;p&gt;I noticed Kiro does this automatically for some tasks, but you can nudge it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Investigate why the ECS task is failing to pull from ECR. 
Check the task definition, the ECR repository policy, and the VPC endpoints. 
Give me a summary of the root cause. Do not dump all the raw output.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key phrase is "give me a summary." It signals that you want focused results, not raw exploration dumped into your session.&lt;/p&gt;




&lt;h2&gt;
  
  
  A real example
&lt;/h2&gt;

&lt;p&gt;I am currently testing Terraform code for an upcoming EKS article in my &lt;a href="https://dev.to/sarvar_04/series/36963"&gt;Terraform series&lt;/a&gt;. The setup is complex: VPC with private subnets, EKS cluster, managed node groups, IAM roles for service accounts, security groups, and add-ons like CoreDNS and kube-proxy.&lt;/p&gt;

&lt;p&gt;I started a Kiro session asking it to help me structure the modules. Then I asked it to write the VPC config. Then the EKS cluster resource. Then the node group. Then I asked it to review the IAM OIDC provider setup.&lt;/p&gt;

&lt;p&gt;By this point the session had all the previous Terraform code, all the plan outputs, and all my back-and-forth corrections in context.&lt;/p&gt;

&lt;p&gt;When I asked "add the aws-load-balancer-controller IAM role with the correct trust policy," Kiro gave me a generic example from the docs. It did not reference my actual cluster name or OIDC provider ARN that we had set up 30 minutes earlier. Gone.&lt;/p&gt;

&lt;p&gt;It was like talking to someone who forgot the last hour of conversation.&lt;/p&gt;

&lt;p&gt;I ran &lt;code&gt;/clear&lt;/code&gt;, started fresh, and said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I am building a production EKS cluster with Terraform. The cluster is called 
prod-platform in us-east-1. The OIDC provider is already set up. I need an 
IAM role for aws-load-balancer-controller with the correct trust policy that 
references my cluster's OIDC issuer. Here is my existing oidc provider ARN: 
arn:aws:iam::111111111111:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/ABCDEF123456
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kiro immediately gave me the exact trust policy with my ARN, my namespace, my service account name. No generic examples. No confusion with earlier VPC or node group context.&lt;/p&gt;

&lt;p&gt;Total time wasted before clearing: 15 minutes of re-explaining.&lt;br&gt;
Time to get the right answer after clearing: 20 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  The context budget cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context Usage&lt;/th&gt;
&lt;th&gt;What's Happening&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Under 30%&lt;/td&gt;
&lt;td&gt;Sharp, focused, fast&lt;/td&gt;
&lt;td&gt;Keep working&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30-50%&lt;/td&gt;
&lt;td&gt;Still good, starting to accumulate noise&lt;/td&gt;
&lt;td&gt;Finish current task, then evaluate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50-70%&lt;/td&gt;
&lt;td&gt;"Lost in the Middle" kicks in hard&lt;/td&gt;
&lt;td&gt;Wrap up NOW or &lt;code&gt;/compact&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Over 70%&lt;/td&gt;
&lt;td&gt;Active degradation: repeating, re-reading, vague answers&lt;/td&gt;
&lt;td&gt;Start fresh. Do not push through.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-compaction triggers&lt;/td&gt;
&lt;td&gt;98% information loss incoming&lt;/td&gt;
&lt;td&gt;You waited too long. &lt;code&gt;/compact&lt;/code&gt; earlier next time.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What this is NOT
&lt;/h2&gt;

&lt;p&gt;I want to be clear about something. This is not a Kiro-specific bug. Every AI coding tool with a context window has this problem: Claude Code, Cursor, Copilot Chat, Windsurf. The architecture is the same transformer attention mechanism underneath.&lt;/p&gt;

&lt;p&gt;The difference is whether you manage context deliberately or let it manage you. Most developers do not think about context at all. They open a session, work for 3 hours, get frustrated that "the AI got dumb," close it, and start fresh the next day.&lt;/p&gt;

&lt;p&gt;That cycle wastes an hour of degraded work every single day. Across a team of 5 engineers, that is 25 hours per week of suboptimal AI output. The fix takes zero extra tools. Just awareness and a habit change.&lt;/p&gt;




&lt;p&gt;I shared this approach with two engineers on my team. Both had been complaining about Kiro "getting dumb" by afternoon. Turns out they were running single sessions for 4-5 hours straight. Once they switched to session-per-task, the complaints stopped within a day.&lt;/p&gt;

&lt;p&gt;The model is not getting dumber. Your context window is getting noisier. Fix the noise, and the model stays as sharp at hour 3 as it was at minute 1.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Kiro CLI context management: &lt;a href="https://kiro.dev/docs/cli/chat/context/" rel="noopener noreferrer"&gt;kiro.dev/docs/cli/chat/context/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Steering files documentation: &lt;a href="https://kiro.dev/docs/cli/steering/" rel="noopener noreferrer"&gt;kiro.dev/docs/cli/steering/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Knowledge management (experimental): &lt;a href="https://kiro.dev/docs/cli/experimental/knowledge-management/" rel="noopener noreferrer"&gt;kiro.dev/docs/cli/experimental/knowledge-management/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;/compact command changelog: &lt;a href="https://kiro.dev/changelog/cli/1-24/" rel="noopener noreferrer"&gt;kiro.dev/changelog/cli/1-24/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ralph Loop technique: &lt;a href="https://developer.mamezou-tech.com/en/blogs/2026/01/30/kiro_cli_ralph/" rel="noopener noreferrer"&gt;developer.mamezou-tech.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;"Lost in the Middle" research: &lt;a href="https://arxiv.org/abs/2307.03172" rel="noopener noreferrer"&gt;arxiv.org/abs/2307.03172&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Context rot whitepaper (Empromptu): &lt;a href="https://empromptu.ai/resources/context-rot-progressive-prompt-ephemerality" rel="noopener noreferrer"&gt;empromptu.ai/resources/context-rot-progressive-prompt-ephemerality&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;How do you manage long sessions with AI coding tools? Do you start fresh or push through? Drop your approach in the comments. I am genuinely curious if others hit the same wall around the 50% mark or if it depends on the type of work.&lt;/p&gt;

&lt;p&gt;Follow me for more on AWS architecture, DevOps, and AI tooling: &lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;sarvarnadaf.com&lt;/a&gt; | &lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kiro</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
    <item>
      <title>Secure Database Deployment - RDS + Secrets Manager</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Mon, 13 Jul 2026 09:40:34 +0000</pubDate>
      <link>https://dev.to/aws-builders/secure-database-deployment-rds-secrets-manager-13eb</link>
      <guid>https://dev.to/aws-builders/secure-database-deployment-rds-secrets-manager-13eb</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;h1&gt;
  
  
  Secure Database Deployment: RDS + Secrets Manager with Terraform
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"A password in your code is like leaving your house key under the doormat everyone knows where to look."&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.tolink-to-article-9"&gt;Article 9&lt;/a&gt; when you deployed IAM roles and security policies? You built secure access controls without access keys. Now let's protect your most sensitive asset &lt;strong&gt;your data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the reality:&lt;/strong&gt; Web servers are stateless. They can crash and restart without losing anything. But what about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User accounts and profiles?&lt;/li&gt;
&lt;li&gt;Shopping cart data?&lt;/li&gt;
&lt;li&gt;Transaction history?&lt;/li&gt;
&lt;li&gt;Application state?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You need a database.&lt;/strong&gt; But here's the catch databases hold your most sensitive data, and one mistake can expose everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's where RDS and Secrets Manager 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 RDS MySQL database with Terraform&lt;/li&gt;
&lt;li&gt;✅ Store credentials securely in AWS Secrets Manager&lt;/li&gt;
&lt;li&gt;✅ Never hardcode passwords in your code&lt;/li&gt;
&lt;li&gt;✅ Configure private subnets for databases&lt;/li&gt;
&lt;li&gt;✅ Set up IAM roles for secure access&lt;/li&gt;
&lt;li&gt;✅ Connect EC2 to RDS without access keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Time Required:&lt;/strong&gt; 50 minutes (25 min read + 25 min practice)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; ~$15/month for db.t3.micro (free tier eligible for 12 months)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Difficulty:&lt;/strong&gt; Intermediate&lt;/p&gt;

&lt;p&gt;Let's build secure database infrastructure! 🚀&lt;/p&gt;


&lt;h2&gt;
  
  
  💔 The Problem: Password Catastrophes
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Horror Story
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;It's Monday morning. Your security team sends this:&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;SECURITY INCIDENT REPORT
Severity: CRITICAL
Issue: Database credentials found in public GitHub repository
Exposure Time: 6 months
Impact: 500,000 user records potentially compromised
Estimated Cost: $2.5 million (fines + remediation)
Root Cause: Hardcoded password in Terraform code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;ol&gt;
&lt;li&gt;Developer hardcoded DB password in &lt;code&gt;main.tf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Committed to Git: &lt;code&gt;git commit -m "Add database"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pushed to GitHub: &lt;code&gt;git push origin main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Security scanner found it 6 months later&lt;/li&gt;
&lt;li&gt;By then, password was in 47 commits across 3 branches&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The damage:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Password exposed in version control history&lt;/li&gt;
&lt;li&gt;❌ Visible in Terraform state file (plain text)&lt;/li&gt;
&lt;li&gt;❌ Copied to multiple developer machines&lt;/li&gt;
&lt;li&gt;❌ Indexed by GitHub search&lt;/li&gt;
&lt;li&gt;❌ Potentially scraped by bots&lt;/li&gt;
&lt;li&gt;❌ Impossible to know who accessed it&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Common Password Mistakes:
&lt;/h3&gt;

&lt;p&gt;❌ &lt;strong&gt;Hardcoded in 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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"db"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"MyPassword123!"&lt;/span&gt;  &lt;span class="c1"&gt;# Everyone can see this!&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;In Environment Variables:&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;export &lt;/span&gt;&lt;span class="nv"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"secret123"&lt;/span&gt;  &lt;span class="c"&gt;# Still visible in shell history&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;❌ &lt;strong&gt;In terraform.tfvars (committed to Git):&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;db_password&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"MySecretPass"&lt;/span&gt;  &lt;span class="c1"&gt;# In version control!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;❌ &lt;strong&gt;In State File:&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;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MyPassword123"&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="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Plain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;S&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&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;strong&gt;Shared via Slack/Email:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hey, the DB password is MyPassword123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;// Now it's everywhere&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Sound familiar?&lt;/strong&gt; Let's fix this properly.&lt;/p&gt;


&lt;h2&gt;
  
  
  🌟 What is AWS Secrets Manager? (Quick Theory)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Simple Definition
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AWS Secrets Manager&lt;/strong&gt; = Encrypted vault for sensitive data. It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stores passwords, API keys, tokens securely&lt;/li&gt;
&lt;li&gt;Encrypts everything with AWS KMS&lt;/li&gt;
&lt;li&gt;Rotates credentials automatically&lt;/li&gt;
&lt;li&gt;Provides audit logs of who accessed what&lt;/li&gt;
&lt;li&gt;Integrates with IAM for access control&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 Secrets Manager:&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;Developer → Hardcodes password → Git → Everyone sees it → 💥 Breach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;With Secrets Manager:&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;Terraform → Generates password → Secrets Manager (encrypted) → 
EC2 (IAM role) → Retrieves password → Connects to RDS ✅
&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: Security Breach&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hardcoded password: Exposed forever in Git history&lt;/li&gt;
&lt;li&gt;Secrets Manager: Rotate password in 30 seconds, breach contained&lt;/li&gt;
&lt;li&gt;Result: Minimal damage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scenario 2: Employee Leaves&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hardcoded password: They know all your passwords&lt;/li&gt;
&lt;li&gt;Secrets Manager: Revoke IAM access, rotate secrets&lt;/li&gt;
&lt;li&gt;Result: Zero risk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scenario 3: Compliance Audit&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hardcoded password: Fail audit, face fines&lt;/li&gt;
&lt;li&gt;Secrets Manager: Pass audit, show encryption + rotation&lt;/li&gt;
&lt;li&gt;Result: Compliance achieved&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What is RDS?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;RDS (Relational Database Service)&lt;/strong&gt; = Managed database service. AWS handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated backups&lt;/li&gt;
&lt;li&gt;Software patching&lt;/li&gt;
&lt;li&gt;Hardware maintenance&lt;/li&gt;
&lt;li&gt;High availability&lt;/li&gt;
&lt;li&gt;Monitoring and alerts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You focus on:&lt;/strong&gt; Your application and data, not database administration.&lt;/p&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-9"&gt;Article 9: IAM Roles and Security&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;✅ Understanding of VPC and security groups&lt;/li&gt;
&lt;li&gt;✅ SSH key pair in AWS&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
    ↓
EC2 Instance (Public Subnet)
    ↓ (IAM Role)
    ├─→ Secrets Manager (Get Credentials)
    └─→ RDS MySQL (Private Subnet)
&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; - Isolated network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public Subnet&lt;/strong&gt; - EC2 instance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private Subnets&lt;/strong&gt; - RDS database (2 AZs required)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Groups&lt;/strong&gt; - Network firewall&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RDS MySQL&lt;/strong&gt; - Managed database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets Manager&lt;/strong&gt; - Encrypted credential storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Role&lt;/strong&gt; - Secure access without keys&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  📁 Project Structure
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;10-rds-secrets/
├── main.tf           # Infrastructure code
├── variables.tf      # Input variables
├── outputs.tf        # Output values
└── terraform.tfvars  # Variable values
&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"&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"&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-db"&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;"VPC CIDR block"&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;"Public subnet CIDR"&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;# Private Subnet CIDRs&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"private_subnet_1_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;"Private subnet 1 CIDR"&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.11.0/24"&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;"private_subnet_2_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;"Private subnet 2 CIDR"&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.12.0/24"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Database Configuration&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"db_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;"Database 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;"myappdb"&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;"db_username"&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;"Database master username"&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;"admin"&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;"db_instance_class"&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;"RDS instance class"&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;"db.t3.micro"&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;"db_allocated_storage"&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;"Allocated storage in GB"&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;20&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# EC2 Configuration&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="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="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 for SSH access"&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="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;No password variable! We'll generate it automatically&lt;/li&gt;
&lt;li&gt;Two private subnets required for RDS (multi-AZ)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;db.t3.micro&lt;/code&gt; - Free tier eligible&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🌐 Step 2: Create VPC with Private Subnets
&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="nx"&gt;random&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/random"&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; 3.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;span class="c1"&gt;# Data Sources&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="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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Random Password for Database&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"db_password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
  &lt;span class="nx"&gt;special&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;override_special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"!#$%&amp;amp;*()-_=+[]{}&amp;lt;&amp;gt;:?"&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="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;Type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Public"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Private Subnets (for RDS)&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;"private_1"&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;private_subnet_1_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;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}-private-subnet-1"&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;"Private"&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_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"private_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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnet_2_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;1&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}-private-subnet-2"&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;"Private"&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="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="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_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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;New Concept: Random Provider&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"db_password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
  &lt;span class="nx"&gt;special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Generates a secure random password automatically. Never hardcode passwords!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Two Private Subnets?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RDS requires subnets in at least 2 availability zones&lt;/li&gt;
&lt;li&gt;Provides high availability&lt;/li&gt;
&lt;li&gt;Automatic failover if one AZ fails&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🔒 Step 3: Configure Security Groups
&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;# Security Group for EC2&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;"ec2"&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}-ec2-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 instance"&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;"SSH"&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;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"&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;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}-ec2-sg"&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 RDS&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;"rds"&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}-rds-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 RDS database"&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;"MySQL from EC2"&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;3306&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;3306&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;ec2&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;egress&lt;/span&gt; &lt;span class="p"&gt;{&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}-rds-sg"&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;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 → EC2 (SSH from your IP, HTTP from anywhere)
           ↓
           RDS (MySQL port 3306 ONLY from EC2)
&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;RDS is NOT accessible from internet&lt;/li&gt;
&lt;li&gt;RDS only accepts connections from EC2 security group&lt;/li&gt;
&lt;li&gt;No direct database access from outside&lt;/li&gt;
&lt;li&gt;This is called "defense in depth"&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  🔐 Step 4: Set Up AWS Secrets Manager
&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;# Secrets Manager Secret&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_secretsmanager_secret"&lt;/span&gt; &lt;span class="s2"&gt;"db_credentials"&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}-db-credentials-${random_password.db_password.id}"&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;"Database credentials for ${var.project_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="s2"&gt;"${var.project_name}-db-secret"&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="c1"&gt;# Store Database Credentials in Secrets Manager&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_secretsmanager_secret_version"&lt;/span&gt; &lt;span class="s2"&gt;"db_credentials"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;secret_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_secretsmanager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&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;secret_string&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;username&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;db_username&lt;/span&gt;
    &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;random_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
    &lt;span class="nx"&gt;engine&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt;
    &lt;span class="nx"&gt;host&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_db_instance&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;address&lt;/span&gt;
    &lt;span class="nx"&gt;port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_db_instance&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;port&lt;/span&gt;
    &lt;span class="nx"&gt;dbname&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;db_name&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;What's Happening:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Secret:&lt;/strong&gt; Empty container for credentials&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store Credentials:&lt;/strong&gt; JSON with all connection info&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption:&lt;/strong&gt; Automatically encrypted at rest&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Control:&lt;/strong&gt; IAM controls who can read it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Secret Structure:&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;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"randomly-generated-16-chars"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mysql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host"&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-db.xxxxx.rds.amazonaws.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;"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;3306&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dbname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"myappdb"&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;
  
  
  🗄️ Step 5: Deploy RDS MySQL Database
&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;# DB Subnet Group&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_subnet_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}-db-subnet-group"&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_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_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_1&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;private_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;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}-db-subnet-group"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# RDS Instance&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&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;identifier&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-db"&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt;
  &lt;span class="nx"&gt;engine_version&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"8.0"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&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;db_instance_class&lt;/span&gt;
  &lt;span class="nx"&gt;allocated_storage&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;db_allocated_storage&lt;/span&gt;
  &lt;span class="nx"&gt;storage_type&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gp3"&lt;/span&gt;
  &lt;span class="nx"&gt;storage_encrypted&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;db_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;db_name&lt;/span&gt;
  &lt;span class="nx"&gt;username&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;db_username&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;random_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;

  &lt;span class="nx"&gt;db_subnet_group_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_db_subnet_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;name&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;rds&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;backup_retention_period&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
  &lt;span class="nx"&gt;backup_window&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"03:00-04:00"&lt;/span&gt;
  &lt;span class="nx"&gt;maintenance_window&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mon:04:00-mon:05:00"&lt;/span&gt;

  &lt;span class="nx"&gt;skip_final_snapshot&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;final_snapshot_identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-final-snapshot"&lt;/span&gt;

  &lt;span class="nx"&gt;enabled_cloudwatch_logs_exports&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"general"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"slowquery"&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}-db"&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;Breaking Down the Configuration:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. DB Subnet Group:&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;subnet_ids&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_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_1&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;private_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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Tells RDS which subnets to use (must be in different AZs).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Storage:&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;storage_type&lt;/span&gt;      &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gp3"&lt;/span&gt;
&lt;span class="nx"&gt;storage_encrypted&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gp3&lt;/code&gt; - Latest generation SSD (better performance)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;storage_encrypted&lt;/code&gt; - Encryption at rest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Backups:&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;backup_retention_period&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="nx"&gt;backup_window&lt;/span&gt;          &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"03:00-04:00"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Automatic daily backups&lt;/li&gt;
&lt;li&gt;Retained for 7 days&lt;/li&gt;
&lt;li&gt;Runs at 3 AM UTC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Maintenance:&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;maintenance_window&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mon:04:00-mon:05:00"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;AWS performs updates during this window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. CloudWatch Logs:&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;enabled_cloudwatch_logs_exports&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"general"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"slowquery"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Sends MySQL logs to CloudWatch for monitoring.&lt;/p&gt;


&lt;h2&gt;
  
  
  👤 Step 6: Create IAM Role for EC2
&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;# IAM Role for EC2 to access Secrets Manager&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"ec2_secrets_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}-ec2-secrets-role"&lt;/span&gt;

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

  &lt;span class="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}-ec2-role"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# IAM Policy for Secrets Manager Access&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy"&lt;/span&gt; &lt;span class="s2"&gt;"secrets_access"&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}-secrets-policy"&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;ec2_secrets_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;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;"secretsmanager:GetSecretValue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"secretsmanager:DescribeSecret"&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="nx"&gt;aws_secretsmanager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Instance Profile&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_instance_profile"&lt;/span&gt; &lt;span class="s2"&gt;"ec2_profile"&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}-ec2-profile"&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;ec2_secrets_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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Understanding IAM Components:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. IAM Role:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identity that EC2 can assume&lt;/li&gt;
&lt;li&gt;No permanent credentials needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Assume Role Policy:&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;"Principal"&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;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec2.amazonaws.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;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;Says "EC2 service can use this role"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. IAM Policy:&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;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"secretsmanager:GetSecretValue"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:secretsmanager:..."&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;Grants permission to read ONLY this specific secret.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Instance Profile:&lt;/strong&gt;&lt;br&gt;
Attaches the role to EC2 instance.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;✅ No access keys on EC2&lt;/li&gt;
&lt;li&gt;✅ Credentials rotate automatically&lt;/li&gt;
&lt;li&gt;✅ Least privilege access&lt;/li&gt;
&lt;li&gt;✅ Auditable in CloudTrail&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🖥️ Step 7: Deploy EC2 with Database Access
&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;# EC2 Instance&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;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;ec2&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;iam_instance_profile&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_instance_profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2_profile&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;user_data&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
              yum update -y
              yum install -y mysql jq aws-cli

              # Get database credentials from Secrets Manager
              SECRET_ARN="${aws_secretsmanager_secret.db_credentials.arn}"
              SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id $SECRET_ARN --region ${var.aws_region} --query SecretString --output text)

              DB_HOST=$(echo $SECRET_JSON | jq -r '.host')
              DB_PORT=$(echo $SECRET_JSON | jq -r '.port')
              DB_USER=$(echo $SECRET_JSON | jq -r '.username')
              DB_PASS=$(echo $SECRET_JSON | jq -r '.password')
              DB_NAME=$(echo $SECRET_JSON | jq -r '.dbname')

              # Create connection script
              cat &amp;gt; /home/ec2-user/connect-db.sh &amp;lt;&amp;lt;SCRIPT
              #!/bin/bash
              mysql -h $DB_HOST -P $DB_PORT -u $DB_USER -p$DB_PASS $DB_NAME
SCRIPT

              chmod +x /home/ec2-user/connect-db.sh
              chown ec2-user:ec2-user /home/ec2-user/connect-db.sh

              # Create info file
              cat &amp;gt; /home/ec2-user/db-info.txt &amp;lt;&amp;lt;INFO
              Database Connection Information
              ================================
              Host: $DB_HOST
              Port: $DB_PORT
              Database: $DB_NAME
              Username: $DB_USER

              To connect: ./connect-db.sh
INFO

              chown ec2-user:ec2-user /home/ec2-user/db-info.txt
&lt;/span&gt;&lt;span class="no"&gt;              EOF

&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}-app-server"&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="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_db_instance&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;aws_secretsmanager_secret_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&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;User Data Magic:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Tools:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; mysql jq aws-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mysql&lt;/code&gt; - Database client&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jq&lt;/code&gt; - JSON parser&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws-cli&lt;/code&gt; - AWS commands&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fetch Credentials:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager get-secret-value &lt;span class="nt"&gt;--secret-id&lt;/span&gt; &lt;span class="nv"&gt;$SECRET_ARN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Uses IAM role (no keys needed!)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parse JSON:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DB_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SECRET_JSON&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.host'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Extracts values from JSON.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Helper Script:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /home/ec2-user/connect-db.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;User can run &lt;code&gt;./connect-db.sh&lt;/code&gt; to connect to database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Depends On:&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;depends_on&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_db_instance&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;aws_secretsmanager_secret_version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Ensures database and secrets exist before EC2 starts.&lt;/p&gt;


&lt;h2&gt;
  
  
  📤 Step 8: 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;"VPC ID"&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 Outputs&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;"Public subnet ID"&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="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"private_subnet_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;"Private subnet IDs"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_1&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;private_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;span class="c1"&gt;# RDS Outputs&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"rds_endpoint"&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;"RDS endpoint"&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_db_instance&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;endpoint&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;"rds_address"&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;"RDS address"&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_db_instance&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;address&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;"rds_port"&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;"RDS port"&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_db_instance&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;port&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;"database_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;"Database 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_db_instance&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;db_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Secrets Manager Output&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"secret_arn"&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;"Secrets Manager secret ARN"&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_secretsmanager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&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;output&lt;/span&gt; &lt;span class="s2"&gt;"secret_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;"Secrets Manager secret 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_secretsmanager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&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="c1"&gt;# EC2 Output&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ec2_public_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;"EC2 instance public IP"&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;app&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="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ec2_instance_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;"EC2 instance ID"&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;app&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;# Connection String (Sensitive)&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"connection_command"&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;"MySQL connection command"&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;"mysql -h ${aws_db_instance.main.address} -P ${aws_db_instance.main.port} -u ${var.db_username} -p ${var.db_name}"&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Sensitive Outputs:&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;sensitive&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Hides output value from terminal. Use &lt;code&gt;terraform output connection_command&lt;/code&gt; to view.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚙️ Step 9: 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-db"&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="nx"&gt;private_subnet_1_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.11.0/24"&lt;/span&gt;
&lt;span class="nx"&gt;private_subnet_2_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.12.0/24"&lt;/span&gt;

&lt;span class="c1"&gt;# Database Configuration&lt;/span&gt;
&lt;span class="nx"&gt;db_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"myappdb"&lt;/span&gt;
&lt;span class="nx"&gt;db_username&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt;
&lt;span class="nx"&gt;db_instance_class&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t3.micro"&lt;/span&gt;
&lt;span class="nx"&gt;db_allocated_storage&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&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;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="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;h2&gt;
  
  
  🚀 Step 10: Deploy the Infrastructure
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. 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;h3&gt;
  
  
  2. 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: 17 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;3 Subnets (1 public, 2 private)&lt;/li&gt;
&lt;li&gt;1 Route Table&lt;/li&gt;
&lt;li&gt;2 Security Groups&lt;/li&gt;
&lt;li&gt;1 Random Password&lt;/li&gt;
&lt;li&gt;1 Secrets Manager Secret + Version&lt;/li&gt;
&lt;li&gt;1 DB Subnet Group&lt;/li&gt;
&lt;li&gt;1 RDS Instance&lt;/li&gt;
&lt;li&gt;1 IAM Role + Policy + Instance Profile&lt;/li&gt;
&lt;li&gt;1 EC2 Instance&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3. 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;strong&gt;⏰ This will take 5-10 minutes&lt;/strong&gt; because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RDS takes 5-7 minutes to provision&lt;/li&gt;
&lt;li&gt;Multi-AZ setup requires time&lt;/li&gt;
&lt;li&gt;Backups need to be configured&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  ✅ Step 11: Test Database Connection
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Get EC2 Public IP
&lt;/h3&gt;


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

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. SSH to EC2 Instance
&lt;/h3&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; my-key.pem ec2-user@&amp;lt;EC2_PUBLIC_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Check Database Info
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;db-info.txt
&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;Database Connection Information
================================
Host: terraform-db.xxxxx.us-east-1.rds.amazonaws.com
Port: 3306
Database: myappdb
Username: admin

To connect: ./connect-db.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  4. Connect to Database
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./connect-db.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You're now connected to MySQL!&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Test Database
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Show databases&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;DATABASES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Create a table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Insert data&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'John Doe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Jane Smith'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'jane@example.com'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Query data&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Exit&lt;/span&gt;
&lt;span class="n"&gt;EXIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔍 Verify Secrets Manager
&lt;/h2&gt;
&lt;h3&gt;
  
  
  From Your Local Machine
&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 secret ARN&lt;/span&gt;
&lt;span class="nv"&gt;SECRET_ARN&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; secret_arn&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Retrieve secret&lt;/span&gt;
aws secretsmanager get-secret-value &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; &lt;span class="nv"&gt;$SECRET_ARN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; SecretString &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text | jq &lt;span class="nb"&gt;.&lt;/span&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;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Xy9#mK2$pL5@qR8!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mysql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host"&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-db.xxxxx.rds.amazonaws.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;"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;3306&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dbname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"myappdb"&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;strong&gt;Notice:&lt;/strong&gt; Password is randomly generated and securely stored!&lt;/p&gt;


&lt;h2&gt;
  
  
  🧪 Advanced Testing
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Test IAM Role Access
&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;# SSH to EC2&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; my-key.pem ec2-user@&amp;lt;EC2_IP&amp;gt;

&lt;span class="c"&gt;# Try to get secret (should work)&lt;/span&gt;
aws secretsmanager get-secret-value &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; &amp;lt;SECRET_ARN&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1

&lt;span class="c"&gt;# Try to delete secret (should fail - no permission)&lt;/span&gt;
aws secretsmanager delete-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; &amp;lt;SECRET_ARN&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Test Database Backups
&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;# List automated backups&lt;/span&gt;
aws rds describe-db-snapshots &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; terraform-db-db

&lt;span class="c"&gt;# Create manual snapshot&lt;/span&gt;
aws rds create-db-snapshot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; terraform-db-db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-snapshot-identifier&lt;/span&gt; terraform-db-manual-snapshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Monitor Database
&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 database status&lt;/span&gt;
aws rds describe-db-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; terraform-db-db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'DBInstances[0].DBInstanceStatus'&lt;/span&gt;

&lt;span class="c"&gt;# View CloudWatch logs&lt;/span&gt;
aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /aws/rds/instance/terraform-db-db/error &lt;span class="nt"&gt;--follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🐛 Troubleshooting
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Issue 1: "Cannot connect to database"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; RDS still provisioning or security group issue.&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;# Check RDS status&lt;/span&gt;
aws rds describe-db-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; terraform-db-db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'DBInstances[0].DBInstanceStatus'&lt;/span&gt;

&lt;span class="c"&gt;# Should show "available"&lt;/span&gt;

&lt;span class="c"&gt;# Check security group&lt;/span&gt;
aws ec2 describe-security-groups &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-ids&lt;/span&gt; &amp;lt;RDS_SG_ID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Issue 2: "Access Denied" when getting secret
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; IAM role not attached or policy incorrect.&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;# Check instance profile&lt;/span&gt;
aws ec2 describe-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instance-ids&lt;/span&gt; &amp;lt;INSTANCE_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Reservations[0].Instances[0].IamInstanceProfile'&lt;/span&gt;

&lt;span class="c"&gt;# Should show the instance profile&lt;/span&gt;

&lt;span class="c"&gt;# Check IAM role policies&lt;/span&gt;
aws iam list-role-policies &lt;span class="nt"&gt;--role-name&lt;/span&gt; terraform-db-ec2-secrets-role
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Issue 3: RDS creation fails
&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: DB Subnet Group doesn't meet availability zone coverage requirement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Ensure private subnets are in different AZs:&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;availability_zone&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="err"&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="c1"&gt;# First AZ&lt;/span&gt;
&lt;span class="nx"&gt;availability_zone&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="err"&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="c1"&gt;# Second AZ&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Issue 4: "mysql: command not found"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; User data script hasn't completed.&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 after instance launch&lt;/span&gt;

&lt;span class="c"&gt;# Check user data logs&lt;/span&gt;
&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /var/log/cloud-init-output.log

&lt;span class="c"&gt;# Manually install if needed&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; mysql
&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;db.t3.micro RDS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;$12.41&lt;/td&gt;
&lt;td&gt;$12.41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage (20 GB gp3)&lt;/td&gt;
&lt;td&gt;20 GB&lt;/td&gt;
&lt;td&gt;$2.30&lt;/td&gt;
&lt;td&gt;$2.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup Storage (20 GB)&lt;/td&gt;
&lt;td&gt;20 GB&lt;/td&gt;
&lt;td&gt;$0.95&lt;/td&gt;
&lt;td&gt;$0.95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;t2.micro EC2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;$8.50&lt;/td&gt;
&lt;td&gt;$8.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets Manager&lt;/td&gt;
&lt;td&gt;1 secret&lt;/td&gt;
&lt;td&gt;$0.40&lt;/td&gt;
&lt;td&gt;$0.40&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;~$24.56&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;750 hours/month of db.t3.micro (first 12 months)&lt;/li&gt;
&lt;li&gt;750 hours/month of t2.micro&lt;/li&gt;
&lt;li&gt;20 GB storage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actual cost with free tier: ~$1.35/month&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;Use free tier instances&lt;/li&gt;
&lt;li&gt;Delete when not in use&lt;/li&gt;
&lt;li&gt;Reduce backup retention (7 days → 1 day)&lt;/li&gt;
&lt;li&gt;Use smaller storage (20 GB → 10 GB minimum)&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  🔐 Security Best Practices
&lt;/h2&gt;
&lt;h3&gt;
  
  
  ✅ What We Did Right
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Hardcoded Passwords&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generated automatically&lt;/li&gt;
&lt;li&gt;Stored encrypted&lt;/li&gt;
&lt;li&gt;Never in code&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Private Subnets for Database&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No internet access&lt;/li&gt;
&lt;li&gt;Only accessible from EC2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security Group Restrictions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RDS only accepts connections from EC2&lt;/li&gt;
&lt;li&gt;Least privilege access&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IAM Roles Instead of Keys&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No access keys on EC2&lt;/li&gt;
&lt;li&gt;Automatic credential rotation&lt;/li&gt;
&lt;li&gt;Auditable access&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encryption&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage encrypted at rest&lt;/li&gt;
&lt;li&gt;Secrets encrypted in Secrets Manager&lt;/li&gt;
&lt;li&gt;TLS for connections&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automated Backups&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;7-day retention&lt;/li&gt;
&lt;li&gt;Point-in-time recovery&lt;/li&gt;
&lt;li&gt;Disaster recovery ready&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  🔒 Additional Security Recommendations
&lt;/h3&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enable Multi-AZ:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;multi_az&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use Parameter Groups:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&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_db_parameter_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;family&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql8.0"&lt;/span&gt;

  &lt;span class="nx"&gt;parameter&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;"slow_query_log"&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;"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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enable Enhanced Monitoring:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;monitoring_interval&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
&lt;span class="nx"&gt;monitoring_role_arn&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rds_monitoring&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use Secrets Rotation:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&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_secretsmanager_secret_rotation"&lt;/span&gt; &lt;span class="s2"&gt;"db"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;secret_id&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_secretsmanager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_credentials&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;rotation_lambda_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;rotate&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;rotation_rules&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;automatically_after_days&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enable Deletion Protection:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;deletion_protection&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧹 Cleanup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; RDS deletion takes 5-10 minutes.&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;strong&gt;What gets deleted:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ EC2 Instance&lt;/li&gt;
&lt;li&gt;✅ RDS Database&lt;/li&gt;
&lt;li&gt;✅ Secrets Manager Secret&lt;/li&gt;
&lt;li&gt;✅ IAM Roles and Policies&lt;/li&gt;
&lt;li&gt;✅ Security Groups&lt;/li&gt;
&lt;li&gt;✅ VPC and Subnets&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 RDS&lt;/span&gt;
aws rds describe-db-instances

&lt;span class="c"&gt;# Check Secrets&lt;/span&gt;
aws secretsmanager list-secrets
&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;Random Provider&lt;/strong&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"db_password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Generate secure random values.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. &lt;strong&gt;AWS Secrets Manager&lt;/strong&gt;
&lt;/h3&gt;


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

&lt;/div&gt;


&lt;p&gt;Secure credential storage.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. &lt;strong&gt;IAM Roles for EC2&lt;/strong&gt;
&lt;/h3&gt;


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

&lt;/div&gt;


&lt;p&gt;No access keys needed!&lt;/p&gt;
&lt;h3&gt;
  
  
  4. &lt;strong&gt;RDS Configuration&lt;/strong&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&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;storage_encrypted&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;backup_retention_period&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;Managed database with backups.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. &lt;strong&gt;Sensitive Outputs&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;output&lt;/span&gt; &lt;span class="s2"&gt;"password"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Hide sensitive data from logs.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. &lt;strong&gt;Depends On&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;depends_on&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_db_instance&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Control resource creation order.&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 with RDS&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 with database&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. Data Analytics&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ETL pipelines&lt;/li&gt;
&lt;li&gt;Reporting systems&lt;/li&gt;
&lt;li&gt;Data warehouses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Mobile Backends&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User authentication&lt;/li&gt;
&lt;li&gt;Content management&lt;/li&gt;
&lt;li&gt;Push notifications&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;Congratulations! You've completed the &lt;strong&gt;Terraform By Sarvar&lt;/strong&gt; foundation series! 🎉&lt;/p&gt;

&lt;p&gt;You now know how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build complete AWS infrastructure with Terraform&lt;/li&gt;
&lt;li&gt;Deploy VPCs, EC2, Lambda, IAM, and RDS&lt;/li&gt;
&lt;li&gt;Secure everything with IAM roles and Secrets Manager&lt;/li&gt;
&lt;li&gt;Follow production-ready best practices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stay tuned&lt;/strong&gt; for advanced topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform Modules&lt;/li&gt;
&lt;li&gt;Remote State Management&lt;/li&gt;
&lt;li&gt;Multi-Environment Setup&lt;/li&gt;
&lt;li&gt;CI/CD Integration&lt;/li&gt;
&lt;/ul&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/db_instance" rel="noopener noreferrer"&gt;Terraform AWS RDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret" rel="noopener noreferrer"&gt;Terraform AWS Secrets Manager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_BestPractices.html" rel="noopener noreferrer"&gt;AWS RDS Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/secretsmanager/" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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/83a31d21758cad5d20e03ba72c3efd2587abc65ade5688db5aa2e1924b7ef419/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41727469636c65732d313025324631302d626c75653f7374796c653d666c61742d737175617265"&gt;&lt;img src="https://camo.githubusercontent.com/83a31d21758cad5d20e03ba72c3efd2587abc65ade5688db5aa2e1924b7ef419/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41727469636c65732d313025324631302d626c75653f7374796c653d666c61742d737175617265" 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) ✅ Complete&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&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/10-rds-secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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>ai</category>
      <category>terraform</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>AWS IAM Roles with Terraform: Security Best Practices</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Mon, 13 Jul 2026 09:39:23 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-iam-roles-with-terraform-security-best-practices-2icj</link>
      <guid>https://dev.to/aws-builders/aws-iam-roles-with-terraform-security-best-practices-2icj</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;h1&gt;
  
  
  AWS IAM Roles with Terraform: Security Best Practices
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Access keys are like house keys lose them once and you have to change all the locks. IAM roles are like a doorman who knows who you are."&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/going-serverless-aws-lambda-functions-ii0"&gt;Article 8&lt;/a&gt; when you deployed Lambda with API Gateway? You used IAM roles to let Lambda access S3 and CloudWatch. That was just the beginning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the reality:&lt;/strong&gt; Every AWS service needs permissions. And there are two ways to grant them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Access Keys&lt;/strong&gt; - Like giving everyone a copy of your house key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Roles&lt;/strong&gt; - Like having a smart lock that knows who should enter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Most developers start with access keys&lt;/strong&gt; because they're simple. Then they face:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keys leaked in Git repositories&lt;/li&gt;
&lt;li&gt;Keys stolen from compromised servers&lt;/li&gt;
&lt;li&gt;Keys that never expire&lt;/li&gt;
&lt;li&gt;No idea who's using which keys&lt;/li&gt;
&lt;li&gt;Nightmare rotation process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;That's where IAM roles save you.&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;✅ Create IAM roles and policies with Terraform&lt;/li&gt;
&lt;li&gt;✅ Implement least privilege access&lt;/li&gt;
&lt;li&gt;✅ Use instance profiles for EC2&lt;/li&gt;
&lt;li&gt;✅ Attach AWS managed policies&lt;/li&gt;
&lt;li&gt;✅ Create custom IAM policies&lt;/li&gt;
&lt;li&gt;✅ Configure S3 bucket policies&lt;/li&gt;
&lt;li&gt;✅ Never use access keys again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Time Required:&lt;/strong&gt; 45 minutes (25 min read + 20 min practice)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; $0 (IAM is free, only pay for resources used)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Difficulty:&lt;/strong&gt; Intermediate&lt;/p&gt;

&lt;p&gt;Let's secure your infrastructure properly! 🚀&lt;/p&gt;


&lt;h2&gt;
  
  
  💔 The Problem: Access Key Disasters
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Nightmare Scenario
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;It's 3 AM. Your phone explodes with alerts:&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;AWS BILLING ALERT
Unusual Activity Detected
Current Charges: $47,000 and climbing
Services: 500 EC2 instances in 12 regions
Cryptocurrency mining detected
Estimated Time: Started 6 hours ago
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Developer put AWS access keys in code&lt;/li&gt;
&lt;li&gt;Committed to public GitHub repo&lt;/li&gt;
&lt;li&gt;Bot scraped keys within 15 minutes&lt;/li&gt;
&lt;li&gt;Attacker launched mining operation&lt;/li&gt;
&lt;li&gt;Your bill: $47,000 in one night&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;This is a real scenario that happens daily.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Common Access Key Mistakes:
&lt;/h3&gt;

&lt;p&gt;❌ &lt;strong&gt;Hardcoded in Application:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.py
&lt;/span&gt;&lt;span class="n"&gt;AWS_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;AKIAIOSFODNN7EXAMPLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;AWS_SECRET_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;wJalrXUtnFEMI/K7MDENG/bPxRfiCY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;❌ &lt;strong&gt;In Environment Variables (on EC2):&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;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="s2"&gt;"AKIAIOSFODNN7EXAMPLE"&lt;/span&gt;
&lt;span class="nb"&gt;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="s2"&gt;"wJalrXUtnFEMI/K7MDENG"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;❌ &lt;strong&gt;In Docker Images:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;❌ &lt;strong&gt;In Configuration Files:&lt;/strong&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="c1"&gt;# config.yml&lt;/span&gt;
&lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;access_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AKIAIOSFODNN7EXAMPLE&lt;/span&gt;
  &lt;span class="na"&gt;secret_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wJalrXUtnFEMI/K7MDENG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;❌ &lt;strong&gt;Shared in Slack/Email:&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;"Hey, use these keys for the dev environment:
Access Key: AKIAIOSFODNN7EXAMPLE
Secret: wJalrXUtnFEMI/K7MDENG"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  The Real Cost:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;💰 &lt;strong&gt;Financial:&lt;/strong&gt; $47K average for crypto mining attacks&lt;/li&gt;
&lt;li&gt;🔒 &lt;strong&gt;Security:&lt;/strong&gt; Data breaches, compliance violations&lt;/li&gt;
&lt;li&gt;⏰ &lt;strong&gt;Time:&lt;/strong&gt; Days to investigate and remediate&lt;/li&gt;
&lt;li&gt;😰 &lt;strong&gt;Stress:&lt;/strong&gt; Explaining to management why this happened&lt;/li&gt;
&lt;li&gt;📉 &lt;strong&gt;Reputation:&lt;/strong&gt; Customer trust damaged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sound familiar?&lt;/strong&gt; Let's fix this permanently.&lt;/p&gt;


&lt;h2&gt;
  
  
  🌟 What are IAM Roles? (Quick Theory)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Simple Definition
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;IAM Role&lt;/strong&gt; = Temporary credentials that AWS services can assume. Think of it as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A badge that proves who you are&lt;/li&gt;
&lt;li&gt;Automatically renewed every hour&lt;/li&gt;
&lt;li&gt;Can't be stolen because it's not stored anywhere&lt;/li&gt;
&lt;li&gt;Specific to one service or resource&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;IAM Policy&lt;/strong&gt; = List of permissions. What you're allowed to do.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Access Keys (Bad):&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;Developer → Creates keys → Puts in code → Git → 
Everyone has keys → 💥 Leaked → Attacker uses them
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;IAM Roles (Good):&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 Instance → Assumes role → AWS gives temporary credentials →
Uses them for 1 hour → Automatically renewed → 
Can't be stolen (not stored anywhere) ✅
&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: Developer Leaves Company&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access Keys: They still have keys, can access everything&lt;/li&gt;
&lt;li&gt;IAM Roles: Revoke their IAM access, all roles stop working instantly&lt;/li&gt;
&lt;li&gt;Result: Zero risk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scenario 2: Server Compromised&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access Keys: Attacker steals keys, uses them forever&lt;/li&gt;
&lt;li&gt;IAM Roles: Temporary credentials expire in 1 hour, limited damage&lt;/li&gt;
&lt;li&gt;Result: Contained breach&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scenario 3: Compliance Audit&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access Keys: "Who used this key?" → No idea&lt;/li&gt;
&lt;li&gt;IAM Roles: CloudTrail shows exactly who did what, when&lt;/li&gt;
&lt;li&gt;Result: Pass audit&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  IAM Components
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Role&lt;/strong&gt; - Identity that can be assumed&lt;br&gt;
&lt;strong&gt;2. Policy&lt;/strong&gt; - Permissions (what you can do)&lt;br&gt;
&lt;strong&gt;3. Trust Policy&lt;/strong&gt; - Who can assume the role&lt;br&gt;
&lt;strong&gt;4. Instance Profile&lt;/strong&gt; - Attaches role to EC2&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2 Instance → Instance Profile → IAM Role → IAM Policy → S3 Access ✅
&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;EC2 Instance → Instance Profile → IAM Role → IAM Policy → S3 Access ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.to/aws-builders/going-serverless-aws-lambda-functions-ii0"&gt;Article 8: Lambda Serverless&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 AWS permissions&lt;/li&gt;
&lt;li&gt;✅ SSH key pair in AWS&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;EC2 Instance
    ↓ (IAM Role)
    ├─→ S3 Bucket (Read/Write)
    ├─→ CloudWatch (Metrics/Logs)
    └─→ SSM (Session Manager)

Lambda Function
    ↓ (IAM Role)
    └─→ S3 Bucket (Read Only)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;IAM Roles&lt;/strong&gt; - Identity for services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Policies&lt;/strong&gt; - Permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance Profile&lt;/strong&gt; - Attaches role to EC2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Bucket Policy&lt;/strong&gt; - Resource-based access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed Policies&lt;/strong&gt; - AWS pre-built policies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Policies&lt;/strong&gt; - Your specific permissions&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  📁 Project Structure
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;09-iam-security/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvars
&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="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-iam"&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 (must be globally unique)"&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-iam-demo-bucket-2026"&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;"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="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="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 for SSH"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🪣 Step 2: Create S3 Bucket with Policy
&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="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="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="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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# S3 Bucket&lt;/span&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;"app_bucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="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;force_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="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="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_s3_bucket_versioning"&lt;/span&gt; &lt;span class="s2"&gt;"app_bucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_bucket&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;versioning_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Enabled"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# S3 Bucket Policy&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_policy"&lt;/span&gt; &lt;span class="s2"&gt;"app_bucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Sid&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AllowEC2RoleAccess"&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;AWS&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;ec2_role&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;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="s2"&gt;"s3:ListBucket"&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="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;app_bucket&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="s2"&gt;"${aws_s3_bucket.app_bucket.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;Sid&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DenyUnencryptedUploads"&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Deny"&lt;/span&gt;
        &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
        &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3:PutObject"&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.app_bucket.arn}/*"&lt;/span&gt;
        &lt;span class="nx"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;StringNotEquals&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:x-amz-server-side-encryption"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AES256"&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Understanding Bucket Policy:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Statement 1: Allow EC2 Role&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;"Principal"&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;"AWS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:role/ec2-role"&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;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::bucket/*"&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;Allows our EC2 role to read/write objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Statement 2: Enforce Encryption&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;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&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;"StringNotEquals"&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;"s3:x-amz-server-side-encryption"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AES256"&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;Blocks unencrypted uploads. Security best practice!&lt;/p&gt;


&lt;h2&gt;
  
  
  👤 Step 3: Create IAM Role for EC2
&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;# IAM Role for EC2&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"ec2_role"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-ec2-role"&lt;/span&gt;

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

  &lt;span class="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}-ec2-role"&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;Assume Role Policy:&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;"Principal"&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;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec2.amazonaws.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;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This says: "EC2 service can assume this role"&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Role&lt;/strong&gt; = Identity (who)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assume Role Policy&lt;/strong&gt; = Trust policy (who can use this role)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions Policy&lt;/strong&gt; = What the role can do&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  📜 Step 4: Create Custom IAM Policy
&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;# Custom IAM Policy for S3 Access&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;"s3_access"&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-access"&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 S3 access for application"&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;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="s2"&gt;"s3:DeleteObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"s3:ListBucket"&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="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;app_bucket&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="s2"&gt;"${aws_s3_bucket.app_bucket.arn}/*"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Attach S3 Policy to EC2 Role&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;"ec2_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;ec2_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="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;s3_access&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;Policy Structure:&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;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Deny&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&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="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;What&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;actions&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:..."&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="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;On&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;which&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;resources&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;strong&gt;Resource ARN Patterns:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;arn:aws:s3:::bucket&lt;/code&gt; - The bucket itself&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;arn:aws:s3:::bucket/*&lt;/code&gt; - Objects in the bucket&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Least Privilege:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only allows specific actions&lt;/li&gt;
&lt;li&gt;Only on specific bucket&lt;/li&gt;
&lt;li&gt;No wildcard permissions&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🔗 Step 5: Attach AWS Managed Policies
&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;# Attach CloudWatch Policy&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;"ec2_cloudwatch"&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;ec2_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="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/CloudWatchAgentServerPolicy"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Attach SSM Policy (for Session Manager)&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;"ec2_ssm"&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;ec2_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="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/AmazonSSMManagedInstanceCore"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Instance Profile&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_instance_profile"&lt;/span&gt; &lt;span class="s2"&gt;"ec2_profile"&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}-ec2-profile"&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;ec2_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;AWS Managed Policies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-built by AWS&lt;/li&gt;
&lt;li&gt;Maintained and updated by AWS&lt;/li&gt;
&lt;li&gt;Follow best practices&lt;/li&gt;
&lt;li&gt;Use when possible!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common Managed Policies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CloudWatchAgentServerPolicy&lt;/code&gt; - Send metrics/logs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AmazonSSMManagedInstanceCore&lt;/code&gt; - Session Manager access&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AmazonS3ReadOnlyAccess&lt;/code&gt; - Read S3&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWSLambdaBasicExecutionRole&lt;/code&gt; - Lambda logging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Instance Profile:&lt;/strong&gt;&lt;br&gt;
Attaches IAM role to EC2 instance. Required!&lt;/p&gt;


&lt;h2&gt;
  
  
  🔐 Step 6: Create Lambda IAM Role
&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;# IAM Role for Lambda&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"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="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}-lambda-role"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Lambda Basic Execution Policy&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="c1"&gt;# Custom Lambda Policy for S3 Read-Only&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_read"&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-read"&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 Lambda to read from S3"&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:ListBucket"&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="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;app_bucket&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="s2"&gt;"${aws_s3_bucket.app_bucket.arn}/*"&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;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_read&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;Different Assume Role Policy:&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;"Principal"&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;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lambda.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Lambda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;EC&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&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;strong&gt;Least Privilege for Lambda:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only &lt;code&gt;GetObject&lt;/code&gt; and &lt;code&gt;ListBucket&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;PutObject&lt;/code&gt; or &lt;code&gt;DeleteObject&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Read-only access&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🖥️ Step 7: Deploy EC2 with IAM Role
&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;# Security Group for EC2&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;"ec2"&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}-ec2-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"&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;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;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}-ec2-sg"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# EC2 Instance&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;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;iam_instance_profile&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_instance_profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2_profile&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;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;ec2&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="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 aws-cli

              # Create test file
              echo "Hello from Terraform IAM Demo" &amp;gt; /home/ec2-user/test.txt
              chown ec2-user:ec2-user /home/ec2-user/test.txt

              # Create helper scripts
              cat &amp;gt; /home/ec2-user/test-s3.sh &amp;lt;&amp;lt;'SCRIPT'
              #!/bin/bash
              echo "Testing S3 Access..."

              # List bucket
              echo "1. Listing bucket contents:"
              aws s3 ls s3://${var.s3_bucket_name}/

              # Upload file
              echo "2. Uploading test file:"
              aws s3 cp /home/ec2-user/test.txt s3://${var.s3_bucket_name}/test.txt

              # Download file
              echo "3. Downloading file:"
              aws s3 cp s3://${var.s3_bucket_name}/test.txt /home/ec2-user/downloaded.txt

              echo "S3 test complete!"
SCRIPT

              chmod +x /home/ec2-user/test-s3.sh
              chown ec2-user:ec2-user /home/ec2-user/test-s3.sh

              # Create IAM info script
              cat &amp;gt; /home/ec2-user/show-iam-info.sh &amp;lt;&amp;lt;'SCRIPT'
              #!/bin/bash
              echo "=== IAM Role Information ==="
              echo "Instance Profile:"
              curl -s http://169.254.169.254/latest/meta-data/iam/info | jq .

              echo -e "\n=== Current IAM Identity ==="
              aws sts get-caller-identity

              echo -e "\n=== Attached Policies ==="
              ROLE_NAME=$(aws sts get-caller-identity --query Arn --output text | cut -d'/' -f2)
              aws iam list-attached-role-policies --role-name $ROLE_NAME 2&amp;gt;/dev/null || echo "Cannot list policies (expected)"
SCRIPT

              chmod +x /home/ec2-user/show-iam-info.sh
              chown ec2-user:ec2-user /home/ec2-user/show-iam-info.sh
&lt;/span&gt;&lt;span class="no"&gt;              EOF

&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"&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;Key Line:&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;iam_instance_profile&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_instance_profile&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2_profile&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This attaches the IAM role to EC2!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Data Scripts:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;test-s3.sh&lt;/code&gt; - Test S3 permissions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;show-iam-info.sh&lt;/code&gt; - Show IAM role info&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  📤 Step 8: 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;# S3 Bucket Output&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;app_bucket&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;"s3_bucket_arn"&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 ARN"&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;app_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# IAM Role Outputs&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ec2_role_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;"EC2 IAM role 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_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ec2_role_arn"&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 IAM role ARN"&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_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2_role&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;output&lt;/span&gt; &lt;span class="s2"&gt;"lambda_role_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;"Lambda IAM role 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_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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# EC2 Output&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ec2_public_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;"EC2 public IP"&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;app&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="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ec2_instance_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;"EC2 instance ID"&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;app&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;# Test Commands&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"test_commands"&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;"Commands to test IAM permissions"&lt;/span&gt;
  &lt;span class="nx"&gt;value&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;EOT&lt;/span&gt;&lt;span class="sh"&gt;
    SSH to EC2:
    ssh -i ${var.key_name}.pem ec2-user@${aws_instance.app.public_ip}

    Test S3 access:
    aws s3 ls s3://${aws_s3_bucket.app_bucket.id}
    aws s3 cp test.txt s3://${aws_s3_bucket.app_bucket.id}/

    Test CloudWatch:
    aws cloudwatch put-metric-data --namespace TestApp --metric-name TestMetric --value 1
&lt;/span&gt;&lt;span class="no"&gt;  EOT
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ⚙️ Step 9: 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-iam"&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;# Change this to a unique bucket name&lt;/span&gt;
&lt;span class="nx"&gt;s3_bucket_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-iam-demo-bucket-2026"&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;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="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;strong&gt;Important:&lt;/strong&gt; Change &lt;code&gt;s3_bucket_name&lt;/code&gt; to something unique!&lt;/p&gt;


&lt;h2&gt;
  
  
  🚀 Step 10: Deploy Infrastructure
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. 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;h3&gt;
  
  
  2. Review 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: 13 to add, 0 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Apply 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;h2&gt;
  
  
  ✅ Step 11: Test IAM Permissions
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Get EC2 IP
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;EC2_IP&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; ec2_public_ip&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$EC2_IP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. SSH to EC2
&lt;/h3&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; my-key.pem ec2-user@&lt;span class="nv"&gt;$EC2_IP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Check IAM Role
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./show-iam-info.sh
&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;"Code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"InstanceProfileArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:instance-profile/terraform-iam-ec2-profile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"InstanceProfileId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AIPAI..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"LastUpdated"&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-03-24T16:00:00Z"&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;"UserId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AROAI...:i-0123456789abcdef0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456789012"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Arn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:sts::123456789012:assumed-role/terraform-iam-ec2-role/i-0123456789abcdef0"&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;strong&gt;Notice:&lt;/strong&gt; No access keys! Using IAM role.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Test S3 Access
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./test-s3.sh
&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;Testing S3 Access...
1. Listing bucket contents:
(empty or shows files)

2. Uploading test file:
upload: ./test.txt to s3://terraform-iam-demo-bucket-2026/test.txt

3. Downloading file:
download: s3://terraform-iam-demo-bucket-2026/test.txt to ./downloaded.txt

S3 test complete!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  5. Test CloudWatch Access
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudwatch put-metric-data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; TestApp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metric-name&lt;/span&gt; TestMetric &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--value&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Should succeed (no output = success).&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Test Denied Action
&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;# Try to list all S3 buckets (should fail)&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;ls&lt;/span&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 plaintext"&gt;&lt;code&gt;An error occurred (AccessDenied) when calling the ListBuckets operation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Perfect!&lt;/strong&gt; Least privilege working - can only access our specific bucket.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔍 Understanding IAM Components
&lt;/h2&gt;
&lt;h3&gt;
  
  
  IAM Role vs IAM User
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;IAM User:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Permanent identity&lt;/li&gt;
&lt;li&gt;Has access keys&lt;/li&gt;
&lt;li&gt;For humans or long-term apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;IAM Role:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Temporary identity&lt;/li&gt;
&lt;li&gt;No access keys&lt;/li&gt;
&lt;li&gt;For services (EC2, Lambda)&lt;/li&gt;
&lt;li&gt;Credentials rotate automatically&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Trust Policy vs Permissions Policy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Trust Policy (Assume Role):&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;"Principal"&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;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec2.amazonaws.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;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;WHO can use this role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permissions Policy:&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;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::bucket/*"&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;WHAT the role can do.&lt;/p&gt;
&lt;h3&gt;
  
  
  Identity-Based vs Resource-Based
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Identity-Based (IAM Policy):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy"&lt;/span&gt; &lt;span class="s2"&gt;"s3_access"&lt;/span&gt; &lt;span class="p"&gt;{&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;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="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::bucket/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Attached to identity (role/user).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource-Based (Bucket Policy):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_policy"&lt;/span&gt; &lt;span class="s2"&gt;"bucket"&lt;/span&gt; &lt;span class="p"&gt;{&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;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;AWS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::123:role/my-role"&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="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="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;Attached to resource (bucket).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use which:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identity-based: Default choice&lt;/li&gt;
&lt;li&gt;Resource-based: Cross-account access, specific resource control&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🧪 Advanced Testing
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Test Policy Evaluation
&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;# SSH to EC2&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; my-key.pem ec2-user@&lt;span class="nv"&gt;$EC2_IP&lt;/span&gt;

&lt;span class="c"&gt;# Test allowed actions&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;test.txt s3://YOUR-BUCKET/test.txt  &lt;span class="c"&gt;# ✅ Should work&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://YOUR-BUCKET/                   &lt;span class="c"&gt;# ✅ Should work&lt;/span&gt;

&lt;span class="c"&gt;# Test denied actions&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;ls&lt;/span&gt;                                     &lt;span class="c"&gt;# ❌ Should fail&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;rm &lt;/span&gt;s3://OTHER-BUCKET/file.txt         &lt;span class="c"&gt;# ❌ Should fail&lt;/span&gt;
aws ec2 describe-instances                    &lt;span class="c"&gt;# ❌ Should fail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  View CloudTrail Logs
&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;# From local machine&lt;/span&gt;
aws cloudtrail lookup-events &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--lookup-attributes&lt;/span&gt; &lt;span class="nv"&gt;AttributeKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Username,AttributeValue&lt;span class="o"&gt;=&lt;/span&gt;terraform-iam-ec2-role &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max-results&lt;/span&gt; 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Shows all actions performed by the role!&lt;/p&gt;
&lt;h3&gt;
  
  
  Test Session Manager (No SSH Keys!)
&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;# From local machine&lt;/span&gt;
aws ssm start-session &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; ec2_instance_id&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Connect without SSH keys - using IAM role!&lt;/p&gt;


&lt;h2&gt;
  
  
  🐛 Troubleshooting
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Issue 1: "Access Denied" on S3
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; IAM policy or bucket policy issue.&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;# Check role is attached&lt;/span&gt;
aws ec2 describe-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instance-ids&lt;/span&gt; &amp;lt;INSTANCE_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Reservations[0].Instances[0].IamInstanceProfile'&lt;/span&gt;

&lt;span class="c"&gt;# Check policies attached to role&lt;/span&gt;
aws iam list-attached-role-policies &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-name&lt;/span&gt; terraform-iam-ec2-role
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Issue 2: "No credentials found"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Instance profile not attached.&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 hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ensure this line exists in aws_instance&lt;/span&gt;
&lt;span class="nx"&gt;iam_instance_profile&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_instance_profile&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2_profile&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Issue 3: Bucket policy conflicts
&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: error putting S3 Bucket Policy: MalformedPolicy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Check JSON syntax:&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;# Validate JSON&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"Version":"2012-10-17"}'&lt;/span&gt; | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Issue 4: Can't assume role
&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;User: arn:aws:sts::123:assumed-role/role/i-xxx is not authorized to perform: sts:AssumeRole
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Check assume role policy allows the service:&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;"Principal"&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;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec2.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Must&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;service&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;
  
  
  💰 Cost Breakdown
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Monthly Costs:&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;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IAM Roles&lt;/td&gt;
&lt;td&gt;$0.00 (Free)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IAM Policies&lt;/td&gt;
&lt;td&gt;$0.00 (Free)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3 Bucket&lt;/td&gt;
&lt;td&gt;$0.00 (Free tier)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;t2.micro EC2&lt;/td&gt;
&lt;td&gt;$8.50 or Free tier&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;strong&gt;~$0-8.50/month&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;IAM is FREE!&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No charge for roles&lt;/li&gt;
&lt;li&gt;No charge for policies&lt;/li&gt;
&lt;li&gt;No charge for users (up to limits)&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🔐 Security Best Practices
&lt;/h2&gt;
&lt;h3&gt;
  
  
  ✅ What We Did Right
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Access Keys&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used IAM roles instead&lt;/li&gt;
&lt;li&gt;Automatic credential rotation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Least Privilege&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only necessary permissions&lt;/li&gt;
&lt;li&gt;Specific resources only&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Separate Roles&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different roles for EC2 and Lambda&lt;/li&gt;
&lt;li&gt;Each with minimum permissions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resource-Based Policies&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bucket policy enforces encryption&lt;/li&gt;
&lt;li&gt;Defense in depth&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Managed Policies&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used AWS-maintained policies&lt;/li&gt;
&lt;li&gt;Automatically updated&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  🔒 Additional Recommendations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Use Policy Conditions:&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;policy&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="nx"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;IpAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"aws:SourceIp"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"203.0.113.0/24"&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="err"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;2. Enable MFA for Sensitive Actions:&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;Condition&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BoolIfExists&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"aws:MultiFactorAuthPresent"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"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;&lt;strong&gt;3. Use Permission Boundaries:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"limited"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;permissions_boundary&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;boundary&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;4. Regular Audits:&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 unused roles&lt;/span&gt;
aws iam get-credential-report

&lt;span class="c"&gt;# Check last used&lt;/span&gt;
aws iam get-role &lt;span class="nt"&gt;--role-name&lt;/span&gt; my-role
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;5. Use SCPs (Service Control Policies):&lt;/strong&gt;&lt;br&gt;
Organization-level restrictions.&lt;/p&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;terraform destroy
&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;strong&gt;What gets deleted:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ EC2 Instance&lt;/li&gt;
&lt;li&gt;✅ S3 Bucket (if empty)&lt;/li&gt;
&lt;li&gt;✅ IAM Roles&lt;/li&gt;
&lt;li&gt;✅ IAM Policies&lt;/li&gt;
&lt;li&gt;✅ Security Group&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If S3 bucket has objects, delete them first:&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;rm &lt;/span&gt;s3://YOUR-BUCKET &lt;span class="nt"&gt;--recursive&lt;/span&gt;
&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;IAM Roles&lt;/strong&gt;
&lt;/h3&gt;


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

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;IAM Policies&lt;/strong&gt;
&lt;/h3&gt;


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

&lt;/div&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Policy Attachments&lt;/strong&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"attach"&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;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;policy&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;h3&gt;
  
  
  4. &lt;strong&gt;Instance Profiles&lt;/strong&gt;
&lt;/h3&gt;


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

&lt;/div&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Bucket Policies&lt;/strong&gt;
&lt;/h3&gt;


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

&lt;/div&gt;



&lt;h2&gt;
  
  
  🎯 Real-World Applications
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Web Applications&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EC2 with S3 access for uploads&lt;/li&gt;
&lt;li&gt;RDS access for database&lt;/li&gt;
&lt;li&gt;CloudWatch for logging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Data Processing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda reading from S3&lt;/li&gt;
&lt;li&gt;Writing to DynamoDB&lt;/li&gt;
&lt;li&gt;SNS notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. CI/CD Pipelines&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CodeBuild with ECR access&lt;/li&gt;
&lt;li&gt;CodeDeploy with EC2 access&lt;/li&gt;
&lt;li&gt;S3 for artifacts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Microservices&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each service with own role&lt;/li&gt;
&lt;li&gt;Least privilege per service&lt;/li&gt;
&lt;li&gt;Service-to-service auth&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; - Managed PostgreSQL/MySQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets Manager&lt;/strong&gt; - Secure credential storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private Subnets&lt;/strong&gt; - Database isolation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Groups&lt;/strong&gt; - Network-level access control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Coming Up:&lt;/strong&gt; Article 10: RDS + Secrets Manager with Terraform&lt;/p&gt;


&lt;h2&gt;
  
  
  📖 Additional Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role" rel="noopener noreferrer"&gt;Terraform AWS IAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html" rel="noopener noreferrer"&gt;AWS IAM Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://policysim.aws.amazon.com/" rel="noopener noreferrer"&gt;IAM Policy Simulator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/security/" rel="noopener noreferrer"&gt;AWS Security Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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/83a31d21758cad5d20e03ba72c3efd2587abc65ade5688db5aa2e1924b7ef419/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41727469636c65732d313025324631302d626c75653f7374796c653d666c61742d737175617265"&gt;&lt;img src="https://camo.githubusercontent.com/83a31d21758cad5d20e03ba72c3efd2587abc65ade5688db5aa2e1924b7ef419/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41727469636c65732d313025324631302d626c75653f7374796c653d666c61742d737175617265" 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) ✅ Complete&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&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/09-iam-security
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✅ Summary
&lt;/h2&gt;

&lt;p&gt;Today you learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Create IAM roles and policies&lt;/li&gt;
&lt;li&gt;✅ Implement least privilege access&lt;/li&gt;
&lt;li&gt;✅ Use instance profiles for EC2&lt;/li&gt;
&lt;li&gt;✅ Attach managed and custom policies&lt;/li&gt;
&lt;li&gt;✅ Configure bucket policies&lt;/li&gt;
&lt;li&gt;✅ Avoid access keys completely&lt;/li&gt;
&lt;li&gt;✅ Test and verify permissions&lt;/li&gt;
&lt;li&gt;✅ Follow security best practices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You now understand AWS IAM security!&lt;/strong&gt; 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Connect &amp;amp; Share
&lt;/h2&gt;

&lt;p&gt;If you found this helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ Star the &lt;a href="https://github.com/simplynadaf/terraform-by-sarvar" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 Leave a comment below&lt;/li&gt;
&lt;li&gt;🔄 Share with your network&lt;/li&gt;
&lt;li&gt;📧 &lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Subscribe for updates&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Next Article:&lt;/strong&gt; Secure Database Deployment with RDS + Secrets Manager&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Written by:&lt;/strong&gt; &lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Sarvar Nadaf&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Series:&lt;/strong&gt; Terraform By Sarvar&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Part:&lt;/strong&gt; 9 of 20&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Tags:&lt;/strong&gt; #Terraform #AWS #DevOps #IaC #IAM #Security #CloudComputing&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>ai</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>PassionCast: I Built an AI Hype Man for World Cup Fans Using Gemini + ElevenLabs</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Sun, 12 Jul 2026 16:57:52 +0000</pubDate>
      <link>https://dev.to/sarvar_04/passioncast-i-built-an-ai-hype-man-for-world-cup-fans-using-gemini-elevenlabs-1ilh</link>
      <guid>https://dev.to/sarvar_04/passioncast-i-built-an-ai-hype-man-for-world-cup-fans-using-gemini-elevenlabs-1ilh</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-07-09"&gt;Weekend Challenge: Passion Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;The World Cup quarter-finals are on right now. I'm watching matches, yelling at my screen, and I thought: what if I could bottle that feeling into an audio clip?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PassionCast&lt;/strong&gt; lets you pick your team, choose a passion mode (hype speech, glory moment, rivalry fire, heartbreak, fan anthem, or custom), and generates a 60-second AI commentary clip. Not the generic "your team is amazing" stuff. Actual references to your team's players, history, and rivals.&lt;/p&gt;

&lt;p&gt;Pick India, select "Rivalry Fire," hit generate. You get a breathless commentator talking about the India-Pakistan cricket-meets-football tension, with real names and real context. Try Argentina and you get Maradona, La Albiceleste, the Diego legacy. That's what I was going for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

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

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://simplynadaf.github.io/PassionCast/" rel="noopener noreferrer"&gt;Try PassionCast Live&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three pre-generated sample clips are on the page. Hit play, no keys needed. You'll hear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Argentina rivalry fire (Match Commentator voice)&lt;/li&gt;
&lt;li&gt;India pre-match hype (Epic Narrator voice)
&lt;/li&gt;
&lt;li&gt;England heartbreak (Epic Narrator voice)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To generate your own, you'll need free API keys (30 seconds each):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aistudio.google.com/apikey" rel="noopener noreferrer"&gt;Google AI Studio key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://elevenlabs.io/app/settings/api-keys" rel="noopener noreferrer"&gt;ElevenLabs key&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The flow:&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%2Fh8y9fp7pgap4fmf2wbz1.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%2Fh8y9fp7pgap4fmf2wbz1.png" alt=" " width="800" height="348"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnfol14kv61bs75bi6hag.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%2Fnfol14kv61bs75bi6hag.png" alt=" " width="799" height="295"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi5uehituabv008nd22k0.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%2Fi5uehituabv008nd22k0.png" alt=" " width="800" height="321"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6kpzuuu3doznt23aagz6.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%2F6kpzuuu3doznt23aagz6.png" alt=" " width="800" height="330"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F70vu9k0l12pbnq9kbyfx.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%2F70vu9k0l12pbnq9kbyfx.png" alt=" " width="800" height="248"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb3b7ph84yt37gyzqo08k.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%2Fb3b7ph84yt37gyzqo08k.png" alt=" " width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&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/PassionCast" rel="noopener noreferrer"&gt;
        PassionCast
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🔥 PassionCast&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Your personal AI hype man for the World Cup 2026.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pick your team. Choose a moment. Get a passionate, AI-generated audio commentary that captures the fire of football fandom.&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://simplynadaf.github.io/PassionCast/" rel="nofollow noopener noreferrer"&gt;Try PassionCast Live&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/simplynadaf/PassionCast/screenshots/01-landing.png"&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%2Fsimplynadaf%2FPassionCast%2FHEAD%2Fscreenshots%2F01-landing.png" alt="PassionCast Landing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How It Works&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick your team&lt;/strong&gt; from 48 World Cup 2026 nations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose your passion&lt;/strong&gt; — hype speech, glory moment, rivalry fire, heartbreak, or fan anthem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select a voice&lt;/strong&gt; — epic narrator, match commentator, or poetic storyteller&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate&lt;/strong&gt; — Gemini writes the script, ElevenLabs brings it to life as audio&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/simplynadaf/PassionCast/screenshots/02-team-selected.png"&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%2Fsimplynadaf%2FPassionCast%2FHEAD%2Fscreenshots%2F02-team-selected.png" alt="Team Selection Flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech Stack&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Script generation&lt;/td&gt;
&lt;td&gt;Google AI (Gemini 2.0 Flash)&lt;/td&gt;
&lt;td&gt;High temperature (0.9) + cultural context = scripts that feel personal, not generic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice synthesis&lt;/td&gt;
&lt;td&gt;ElevenLabs (Multilingual v2)&lt;/td&gt;
&lt;td&gt;Low stability (0.4) for emotional range, high style (0.6) for expressiveness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Vanilla JS + Vite&lt;/td&gt;
&lt;td&gt;Fast, no framework overhead, deploys anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;GitHub Pages&lt;/td&gt;
&lt;td&gt;Free, automatic deploys via GitHub Actions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;No backend. No database…&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/PassionCast" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;No backend. No framework. One page, vanilla JS, two API calls. Vite for bundling, GitHub Pages for hosting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → Gemini 2.0 Flash (writes script) → ElevenLabs (speaks it) → Audio playback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I specifically didn't want a backend here. The whole point is that someone can fork this, drop in their own keys, and have it running in 30 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Gemini Part (where I spent most of my time)
&lt;/h3&gt;

&lt;p&gt;Getting Gemini to write &lt;em&gt;passionate&lt;/em&gt; content was harder than I expected. At temperature 0.7, everything came out bland. "Your team has a proud history. The fans are excited." Useless.&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;0.9&lt;/strong&gt;, things got interesting. Combined with a detailed system prompt that includes the team name, their rivals, their football culture, and the specific style I want (narrator vs commentator vs poet), the output started feeling real.&lt;/p&gt;

&lt;p&gt;The trick was constraining length. I needed 150-200 words, the sweet spot for 45-60 seconds of audio. Too short and it feels empty. Too long and ElevenLabs starts rushing or the clip drags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Note: ${apiKey} is a template literal variable, not a real key. &lt;/span&gt;
&lt;span class="c1"&gt;// Users provide their own free key from aistudio.google.com/apikey&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&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="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxOutputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The ElevenLabs Part (where the magic happens)
&lt;/h3&gt;

&lt;p&gt;I tested default voice settings first. Sounded flat. Like a GPS reading football commentary.&lt;/p&gt;

&lt;p&gt;The fix: crank down &lt;strong&gt;stability to 0.4&lt;/strong&gt; (default is higher). This adds emotional variation. The voice wavers, speeds up, gets louder on key phrases. Exactly what you want for sports commentary.&lt;/p&gt;

&lt;p&gt;Then &lt;strong&gt;style to 0.6&lt;/strong&gt; for extra expressiveness, and &lt;strong&gt;speaker boost on&lt;/strong&gt; for clarity.&lt;/p&gt;

&lt;p&gt;Three voices, matched to content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Brian&lt;/strong&gt; (dramatic narrator) for glory moments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liam&lt;/strong&gt; (energetic) for rivalry and hype&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;George&lt;/strong&gt; (poetic storyteller) for heartbreak and anthems
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eleven_multilingual_v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;voice_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;stability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;similarity_boost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;use_speaker_boost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Other Decisions
&lt;/h3&gt;

&lt;p&gt;I used flag images from flagcdn.com instead of emoji because emoji flags don't render on Windows for England, Wales, and Scotland (they show as black rectangles). Learned that the hard way after building the whole grid with emoji first.&lt;/p&gt;

&lt;p&gt;Dark theme with gold/fire accents because... it's a passion app. Light mode would feel wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'd Add
&lt;/h3&gt;

&lt;p&gt;If I had another weekend: social sharing (let people post their clips), match-day mode that checks today's fixtures and auto-suggests hype content, and a gallery so you can hear what other fans generated. PRs welcome if you want to tackle any of these.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Best Use of Google AI&lt;/strong&gt;: Gemini 2.0 Flash with temperature 0.9 and detailed cultural prompting. Produces scripts with real player names, real rivalry history, and team-specific fan culture for 48 nations. Three distinct writing styles (narrator, commentator, poet) via style instructions in the prompt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt;: Multilingual v2 model with deliberately tuned settings. Stability at 0.4 for emotional range (default sounds robotic for sports content). Style at 0.6 for expressiveness. Three voices paired to content types. The output sounds like broadcast commentary, not text-to-speech.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>Introducing AWS Sandbox Environment: Learn AWS for Free Without a Credit Card</title>
      <dc:creator>Sarvar Nadaf</dc:creator>
      <pubDate>Thu, 09 Jul 2026 10:11:18 +0000</pubDate>
      <link>https://dev.to/aws-builders/introducing-aws-sandbox-environment-learn-aws-for-free-without-a-credit-card-58np</link>
      <guid>https://dev.to/aws-builders/introducing-aws-sandbox-environment-learn-aws-for-free-without-a-credit-card-58np</guid>
      <description>&lt;p&gt;I was scrolling through the &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/07/aws-builder-center-sandbox/" rel="noopener noreferrer"&gt;AWS What's New feed&lt;/a&gt; when one announcement stopped me. AWS Builder Center now lets you spin up a free sandbox environment directly from a workshop page. No AWS account. No credit card. No billing alerts at 3am because you forgot to terminate a NAT Gateway.&lt;/p&gt;

&lt;p&gt;I've spent the last decade helping clients onboard teams to AWS. The number one friction point was never the technology. It was the signup. "What if I get charged?" kills more cloud careers before they start than any certification exam ever will.&lt;/p&gt;

&lt;p&gt;That friction just disappeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AWS Actually Announced
&lt;/h2&gt;

&lt;p&gt;On July 8, 2026, AWS launched free sandbox environments inside &lt;a href="https://builder.aws.com/workshops" rel="noopener noreferrer"&gt;AWS Builder Center&lt;/a&gt;. Here are the confirmed details straight from the announcement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; Zero. Completely free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duration:&lt;/strong&gt; 8 hours from the moment you activate it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frequency:&lt;/strong&gt; One sandbox per week, resets every Sunday&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provisioning time:&lt;/strong&gt; Most environments ready within 15 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleanup:&lt;/strong&gt; Fully automatic after your 8 hours expire&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requirements:&lt;/strong&gt; No personal AWS account needed, no credit card&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sandbox is a pre-provisioned AWS account that you access through eligible workshops. These are step-by-step guided labs covering topics like building serverless APIs, setting up VPCs, deploying containers, or working with AI services. You're not getting a stripped-down simulator. You're deploying real resources into a real AWS account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters More Than You Think
&lt;/h2&gt;

&lt;p&gt;I've onboarded probably 40+ engineers to AWS across different client engagements. The pattern repeats every single time. Day one of training, someone raises their hand: "What happens if I accidentally leave something running?" That question comes from fear. And fear makes people skip hands-on practice entirely. They read docs. Watch videos. Never deploy a thing.&lt;/p&gt;

&lt;p&gt;The AWS Free Tier tried to solve this. It helped, but didn't kill the fear because you still needed a credit card on file. I remember my early days after graduation. No steady income, learning AWS on Free Tier, feeling like I was on borrowed time. When the tier expired and a $4.50 charge showed up for some resource I forgot to terminate, it felt like $45. When you're broke and learning, even a small unexpected bill makes you close the console and not come back for weeks.&lt;/p&gt;

&lt;p&gt;AWS recently revamped the Free Tier into a proper "Free Plan" with $200 in credits and auto-closure after 6 months. Big improvement. But Builder Center Sandbox goes further: you don't even need an account. The barrier is zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Compares to Other Free Options
&lt;/h2&gt;

&lt;p&gt;AWS now has four distinct paths to hands-on practice without paying. Each serves a different need.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Credit Card&lt;/th&gt;
&lt;th&gt;Time Limit&lt;/th&gt;
&lt;th&gt;Access Type&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Builder Center Sandbox&lt;/strong&gt; (NEW)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;8 hrs/session, 1/week&lt;/td&gt;
&lt;td&gt;Workshop-guided&lt;/td&gt;
&lt;td&gt;Zero-commitment first experience&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free Tier (Free Plan)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free ($200 credits)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;6 months&lt;/td&gt;
&lt;td&gt;Full console, 90+ services&lt;/td&gt;
&lt;td&gt;Self-directed projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skill Builder&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$29/mo or $449/yr&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Subscription&lt;/td&gt;
&lt;td&gt;Labs + exam prep&lt;/td&gt;
&lt;td&gt;Certification prep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Educate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Self-paced&lt;/td&gt;
&lt;td&gt;Curated courses + labs&lt;/td&gt;
&lt;td&gt;Students, career-switchers (13+)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let me break down when you'd pick each one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Builder Center Sandbox&lt;/strong&gt; is for when you want to try AWS without creating anything. No account, no commitment, no trace. You follow a workshop, deploy real resources, learn by doing, and everything vanishes after 8 hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free Tier (Free Plan)&lt;/strong&gt; is for when you want to build your own thing. A side project, a portfolio piece, an experiment. You get an actual account with $200 in credits and 6 months to use them. It auto-closes, so no surprise bills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skill Builder&lt;/strong&gt; is for professionals investing in certifications. The $29/month gets you immersive labs, exam prep courses, and practice exams. Worth it if you're targeting a specific cert.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Educate&lt;/strong&gt; is the entry ramp for students and people switching careers. Free, beginner-friendly, and includes a job board for ages 18+.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision Framework
&lt;/h2&gt;

&lt;p&gt;"I want to try AWS for the first time without any commitment" → &lt;strong&gt;Builder Center Sandbox&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"I want to build my own side project on AWS for free" → &lt;strong&gt;Free Tier (Free Plan)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"I'm preparing for an AWS certification exam" → &lt;strong&gt;Skill Builder subscription&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"I'm a student or career-switcher with no tech background" → &lt;strong&gt;AWS Educate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"I'm an experienced dev who wants to test a specific architecture pattern" → &lt;strong&gt;Free Tier (Free Plan)&lt;/strong&gt; or &lt;strong&gt;Builder Center Sandbox&lt;/strong&gt; (if a relevant workshop exists)&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Can't Do (Limitations)
&lt;/h2&gt;

&lt;p&gt;Based on the announcement, here are the real constraints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Workshop-specific, not open console.&lt;/strong&gt; You can't open the AWS Console and freelance. The sandbox is tied to eligible workshops. At launch, only select workshops support it, with more being enabled over time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;8-hour hard limit.&lt;/strong&gt; If you're building something that takes multiple sessions, this won't work. The environment gets wiped completely after 8 hours. No saving progress.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One per week.&lt;/strong&gt; You can't chain multiple sandboxes in a day. One activation, resets Sunday. Plan your learning accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I'd Like to Know (But Can't Confirm Yet)
&lt;/h2&gt;

&lt;p&gt;A few questions the announcement doesn't answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you need an AWS Builder ID to access it? (Probably yes, but unconfirmed)&lt;/li&gt;
&lt;li&gt;Which specific workshops are sandbox-enabled at launch?&lt;/li&gt;
&lt;li&gt;What services are available inside the sandbox? (Likely varies per workshop)&lt;/li&gt;
&lt;li&gt;Are there geographic restrictions?&lt;/li&gt;
&lt;li&gt;Can you export your work (code, configs) before the 8-hour window closes?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll update this article as those details become clear.&lt;/p&gt;

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

&lt;p&gt;If you fall into any of these categories, go to &lt;a href="https://builder.aws.com/workshops" rel="noopener noreferrer"&gt;builder.aws.com/workshops&lt;/a&gt; right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You've been "meaning to learn AWS" for months but never started&lt;/li&gt;
&lt;li&gt;You teach or mentor others and need a zero-friction lab environment for your students&lt;/li&gt;
&lt;li&gt;You're evaluating AWS services before committing your company's budget&lt;/li&gt;
&lt;li&gt;You want guided, structured practice without managing your own cleanup&lt;/li&gt;
&lt;li&gt;You already know AWS but want to explore workshops on topics outside your comfort zone&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Take
&lt;/h2&gt;

&lt;p&gt;As someone with 7 AWS certifications and four years as an AWS Community Builder, I've watched this problem from both sides. Watched clients spend thousands on training environments that collected dust. Watched junior engineers accumulate surprise bills that killed their curiosity. I've seen people give up on cloud because the onboarding felt hostile.&lt;/p&gt;

&lt;p&gt;This sandbox removes a massive psychological barrier. It seems like a small feature. It's not.&lt;/p&gt;

&lt;p&gt;The best way to learn cloud is to break things in cloud. AWS just made breaking things free, temporary, and consequence-free. That's the right move.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have you tried the Builder Center Sandbox yet? Which workshops are available in your region?&lt;/strong&gt; Drop your findings in the comments. I'll compile a list as people report back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This article is actively updated.&lt;/strong&gt; AWS Builder Center Sandbox launched July 8, 2026 with select workshops. As AWS enables more workshops, expands features, or changes limits, I'll update this post. Follow me to stay current.&lt;/p&gt;




&lt;p&gt;Follow me for more on AWS architecture, DevOps, and AI tooling: &lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;sarvarnadaf.com&lt;/a&gt; | &lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>beginners</category>
      <category>devops</category>
    </item>
    <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>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/83a31d21758cad5d20e03ba72c3efd2587abc65ade5688db5aa2e1924b7ef419/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41727469636c65732d313025324631302d626c75653f7374796c653d666c61742d737175617265"&gt;&lt;img src="https://camo.githubusercontent.com/83a31d21758cad5d20e03ba72c3efd2587abc65ade5688db5aa2e1924b7ef419/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41727469636c65732d313025324631302d626c75653f7374796c653d666c61742d737175617265" 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) ✅ Complete&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&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>
  </channel>
</rss>
