<?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: Tejas Shinkar</title>
    <description>The latest articles on DEV Community by Tejas Shinkar (@tejas_shinkar).</description>
    <link>https://dev.to/tejas_shinkar</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%2F3938675%2F6d029035-4f11-4558-aa7f-0b63467d1540.jpg</url>
      <title>DEV Community: Tejas Shinkar</title>
      <link>https://dev.to/tejas_shinkar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tejas_shinkar"/>
    <language>en</language>
    <item>
      <title>AWS CloudFormation — Infrastructure as Code, Templates, Stacks &amp; Change Sets</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Mon, 10 Aug 2026 05:46:29 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-cloudformation-infrastructure-as-code-templates-stacks-change-sets-50k0</link>
      <guid>https://dev.to/tejas_shinkar/aws-cloudformation-infrastructure-as-code-templates-stacks-change-sets-50k0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. CloudFormation is the foundation of infrastructure automation on AWS — the point where "clicking in the console" becomes "code that can be reviewed, versioned, and reproduced."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;What is IaC and Why It Exists&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;IaC Benefits&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;What is CloudFormation&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;How CloudFormation Works in the Backend&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;CloudFormation Template Structure — All Sections with Limits&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Template Section Plain-English Explanations&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Template vs Stack — The Distinction&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;CloudFormation Building Blocks&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;YAML Basics for CF Templates&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;CF Parameters&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;CF Rules&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;CF Mappings&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;CF Outputs&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;CF Conditions&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;CF Intrinsic Functions&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;CF Service Roles&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;CF Stack Policies and Deletion Policies&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;CF Change Sets&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;CF Stack Sets&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Lab — Full Build-Up with Real Failures&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;Interview Questions&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;Assignment&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What is IaC and Why It Exists
&lt;/h2&gt;

&lt;p&gt;Before IaC, infrastructure was managed manually — someone SSH'd into servers, clicked through console UIs, and hoped they remembered what they changed. When that person left, the knowledge went with them. When you needed three identical environments, you clicked through the console three times and hoped for no mistakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IaC (Infrastructure as Code)&lt;/strong&gt; is the practice of defining and managing infrastructure through code or configuration files — so infrastructure can be created, changed, versioned, and reproduced automatically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Instead of clicking "create EC2 instance" in the console, you write a file that says "I want an EC2 instance with these specs." You give that file to a tool. The tool creates the instance. The file goes into Git. Next time you need the same instance, you run the same file. Exact same infrastructure, zero manual steps.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The same principles that apply to application code now apply to infrastructure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version control — track every change with a commit message&lt;/li&gt;
&lt;li&gt;Code review — a colleague reviews infrastructure changes before they're applied&lt;/li&gt;
&lt;li&gt;CI/CD pipelines — infrastructure changes tested and deployed automatically&lt;/li&gt;
&lt;li&gt;Reproducibility — the same template produces identical infrastructure every time&lt;/li&gt;
&lt;li&gt;Documentation — the template &lt;em&gt;is&lt;/em&gt; the documentation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  IaC Benefits
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;th&gt;What it means in practice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reproducibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same template in any region or account → identical infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Version control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every change tracked in Git — who changed what, when, and why&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auditability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full history of infrastructure changes — essential for compliance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Automation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CI/CD pipelines deploy infrastructure changes without human intervention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Drift detection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compare actual infrastructure against the template — catch unauthorized manual changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Disaster recovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Recreate entire infrastructure in a new region in minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consistency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dev, staging, prod built from the same template — no more "works on staging, broken in prod"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What is CloudFormation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AWS CloudFormation&lt;/strong&gt; is AWS's native IaC service. You write a template (YAML or JSON), upload it, and CloudFormation creates, updates, and manages all resources on your behalf.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Think of CloudFormation as a construction manager working from a blueprint. Your YAML/JSON template is the blueprint — it describes exactly how your AWS infrastructure should look. CloudFormation reads the blueprint, builds the infrastructure, and remembers what it built. When you change the blueprint, the construction manager doesn't demolish the entire building — they identify what changed and modify only the affected parts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why CloudFormation over clicking in the console:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Console clicks are not reproducible — the exact sequence is never recorded&lt;/li&gt;
&lt;li&gt;Templates can be reviewed, approved, and audited&lt;/li&gt;
&lt;li&gt;Same template creates identical infrastructure across dev/staging/prod&lt;/li&gt;
&lt;li&gt;Changes can be previewed before applying via Change Sets&lt;/li&gt;
&lt;li&gt;Automatic rollback if something goes wrong during deployment&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How CloudFormation Works in the Backend
&lt;/h2&gt;

&lt;p&gt;Understanding this makes troubleshooting CloudFormation failures significantly easier. This is the exact flow from class — represented as a numbered sequence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The entire backend flow:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;[1] Parse + Validate:&lt;/strong&gt; Reads your YAML/JSON, checks for syntax errors, validates that resource types and properties are correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[2] Transform Expansion:&lt;/strong&gt; Expands any transforms — SAM shorthand, Includes, Language Extensions, Modules — into full CloudFormation resource definitions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[3] Pre-deployment Validation:&lt;/strong&gt; Checks whether the requested resources and configuration make sense before deployment begins. Validates IAM permissions and quota limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[4] Build Dependency Graph (DAG):&lt;/strong&gt; Determines resource dependencies — what depends on what and what needs to happen first. Independent resources are flagged for parallel execution. Dependent resources are ordered correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[5] Compute Change Set:&lt;/strong&gt; Compares the current AWS state (stored in CloudFormation's transactional state store) with the new template to determine exactly what needs to change — Add, Modify, Remove, Replace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[6] Evaluate Hooks / Guardrails:&lt;/strong&gt; Checks predefined organizational/security rules before making changes. Example: verifying every S3 bucket has encryption enabled before creation proceeds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[7] Parallel Execution (by DAG topological order):&lt;/strong&gt; Independent resources are created/updated simultaneously. Dependent resources follow the required order. For each resource: Resource Provider handles the actual Create/Update/Delete API call. Then → &lt;strong&gt;Stabilization Loop&lt;/strong&gt; — CloudFormation polls until the resource reaches its required ready state. A successful API call doesn't mean the resource is ready. Stabilization can take 20–90 minutes for some resources (RDS, ElasticSearch, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[8] Success or Failure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Success → State Committed:&lt;/strong&gt; Everything reaches the desired state → stack becomes &lt;code&gt;CREATE_COMPLETE&lt;/code&gt; or &lt;code&gt;UPDATE_COMPLETE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure → Rollback:&lt;/strong&gt; CloudFormation attempts to undo the changes in reverse DAG order → returns the stack toward its previous stable state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎯 &lt;strong&gt;Why this matters for troubleshooting:&lt;/strong&gt; When a stack gets stuck at &lt;code&gt;CREATE_IN_PROGRESS&lt;/code&gt;, it's in the Stabilization Loop — waiting for a resource to become available. Check the Events tab — it shows which resource is waiting and why. When it fails, the error comes from the underlying AWS API call in the Resource Provider step.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  CloudFormation Template Structure — All Sections with Limits
&lt;/h2&gt;

&lt;p&gt;A CloudFormation template is a YAML or JSON file with the following structure. Every section has limits you should know for production and for the SAA-C03 exam.&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;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2010-09-09"&lt;/span&gt;   &lt;span class="c1"&gt;# optional, but always write it&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;..."&lt;/span&gt;                        &lt;span class="c1"&gt;# max 1024 bytes&lt;/span&gt;
&lt;span class="na"&gt;Metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;                              &lt;span class="c1"&gt;# console UI hints, cfn-init config&lt;/span&gt;
&lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;                            &lt;span class="c1"&gt;# max 200&lt;/span&gt;
&lt;span class="na"&gt;Rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;                                 &lt;span class="c1"&gt;# UNDER-USED — param validation logic&lt;/span&gt;
&lt;span class="na"&gt;Mappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;                              &lt;span class="c1"&gt;# max 200 mappings, 200 attrs each&lt;/span&gt;
&lt;span class="na"&gt;Conditions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;                            &lt;span class="c1"&gt;# max 512&lt;/span&gt;
&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;                             &lt;span class="c1"&gt;# macro/SAM expansion&lt;/span&gt;
&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;                             &lt;span class="c1"&gt;# REQUIRED — max 500&lt;/span&gt;
&lt;span class="na"&gt;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;                               &lt;span class="c1"&gt;# max 200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Only &lt;code&gt;Resources&lt;/code&gt; is required.&lt;/strong&gt; Every other section is optional.&lt;/p&gt;




&lt;h2&gt;
  
  
  Template Section Plain-English Explanations
&lt;/h2&gt;

&lt;p&gt;From class — the clearest way to remember what each section does:&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;Plain-English Question It Answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AWSTemplateFormatVersion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What template format?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What is this blueprint for?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Metadata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extra blueprint information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Parameters&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What choices should the person deploying provide?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Rules&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Are those choices valid?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Mappings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lookup table for predefined values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Conditions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Should this part be built or not?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Transform&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expand/process the blueprint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Resources&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;⭐ What actually needs to be built?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Outputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What useful information should I give back after construction?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Template vs Stack — The Distinction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Template:&lt;/strong&gt; A YAML or JSON file — the blueprint. A text file. By itself, it creates nothing. Lives in Git, S3, or locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; What CloudFormation creates when it executes a template. A group of AWS resources managed together as one unit. The stack tracks every resource it created, their state, and the template that generated them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The same template creates multiple stacks:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;webapp.yaml&lt;/code&gt; (one file in Git)&lt;br&gt;
→ Stack: &lt;code&gt;webapp-dev&lt;/code&gt; → dev resources in dev account&lt;br&gt;
→ Stack: &lt;code&gt;webapp-staging&lt;/code&gt; → staging resources in staging account&lt;br&gt;
→ Stack: &lt;code&gt;webapp-prod&lt;/code&gt; → prod resources in prod account&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Three environments, identical configuration, zero manual effort.&lt;/p&gt;




&lt;h2&gt;
  
  
  CloudFormation Building Blocks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SAM (Serverless Application Model)&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Shorthand syntax for serverless — Lambda, API Gateway, DynamoDB — written in a simplified form. The &lt;code&gt;Transform: AWS::Serverless-2016-10-31&lt;/code&gt; macro expands SAM into full CloudFormation at deploy time. Instead of 50 lines to define a Lambda with its role and log group, SAM lets you write 10.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Include&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pulls content from another template file into the current one — splits large templates into focused, maintainable files.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Language Extensions&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Adds extra syntax features — native string operations, length functions, &lt;code&gt;ToJsonString&lt;/code&gt; — reducing repetitive code and making templates more flexible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Modules&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reusable infrastructure components used across multiple templates. Define a "standard VPC setup" once as a module — reference it in every team's template without repeating 200 lines.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  YAML Basics for CF Templates
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Comment in YAML&lt;/span&gt;

&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;value&lt;/span&gt;                  &lt;span class="c1"&gt;# String&lt;/span&gt;
&lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;                  &lt;span class="c1"&gt;# Integer&lt;/span&gt;
&lt;span class="na"&gt;boolean&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;               &lt;span class="c1"&gt;# Boolean&lt;/span&gt;
&lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                       &lt;span class="c1"&gt;# List/Array&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;item1&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;item2&lt;/span&gt;
&lt;span class="na"&gt;nested&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                     &lt;span class="c1"&gt;# Nested object/map&lt;/span&gt;
  &lt;span class="na"&gt;key1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;value1&lt;/span&gt;
  &lt;span class="na"&gt;key2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;value2&lt;/span&gt;
&lt;span class="na"&gt;multiline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;                &lt;span class="c1"&gt;# Multi-line string (preserves newlines)&lt;/span&gt;
  &lt;span class="s"&gt;line one&lt;/span&gt;
  &lt;span class="s"&gt;line two&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Indentation is everything in YAML.&lt;/strong&gt; Unlike JSON (curly braces), YAML uses 2-space indentation to show nesting. One wrong indent fails the entire template.&lt;/p&gt;




&lt;h2&gt;
  
  
  CF Parameters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Parameters&lt;/strong&gt; let you avoid hardcoding values — instead the person deploying provides them at stack creation or update time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The same template creates a &lt;code&gt;t3.micro&lt;/code&gt; in dev and a &lt;code&gt;c5.2xlarge&lt;/code&gt; in prod — just by passing different values. No template changes needed.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;InstanceType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;t3.micro&lt;/span&gt;
    &lt;span class="na"&gt;AllowedValues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;t3.micro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;t3.small&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;t3.medium&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;t3.large&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EC2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;instance&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;web&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;server"&lt;/span&gt;

  &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
    &lt;span class="na"&gt;AllowedValues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deployment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;environment"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Parameter types:&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;Type&lt;/th&gt;
&lt;th&gt;Use for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text values, names, identifiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Numeric values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CommaDelimitedList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Multiple strings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AWS::EC2::KeyPair::KeyName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Validates the key pair exists before deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AWS::EC2::VPC::Id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Validates the VPC ID exists before deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AWS::EC2::Subnet::Id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Validates the subnet ID exists before deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AWS::SSM::Parameter::Value&amp;lt;String&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pulls value directly from SSM Parameter Store&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview tip:&lt;/strong&gt; AWS-specific parameter types (like &lt;code&gt;AWS::EC2::VPC::Id&lt;/code&gt;) are validated before the stack starts deploying — an invalid VPC ID gets rejected immediately, not halfway through.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Limit: max 200 parameters per template.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  CF Rules
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Rules&lt;/strong&gt; are the validation layer for Parameters — they ensure parameter combinations are valid, going beyond what &lt;code&gt;AllowedValues&lt;/code&gt; alone can check.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;AllowedValues&lt;/code&gt; can restrict a single parameter to a fixed list. But what if you need: "when Environment is &lt;code&gt;prod&lt;/code&gt;, InstanceType must not be &lt;code&gt;t3.micro&lt;/code&gt;"? That cross-parameter logic is what Rules handle.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ProdRequiresLargeInstance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Assertions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="kt"&gt;!Or&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Not&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;!Equals&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;]]&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Not&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;!Equals&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;InstanceType&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;t3.micro&lt;/span&gt;&lt;span class="pi"&gt;]]&lt;/span&gt;
        &lt;span class="na"&gt;AssertDescription&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Production&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cannot&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;t3.micro"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Note from class:&lt;/strong&gt; Rules are marked as "UNDER-USED" in practice — most teams use Parameters with &lt;code&gt;AllowedValues&lt;/code&gt; for simple validation and rely on Conditions for conditional resource creation. But Rules are the correct tool when you need to validate that multiple parameter values make sense together.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  CF Mappings
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mappings&lt;/strong&gt; are lookup tables baked into your template. The classic use case — AMI IDs per region, so the person deploying doesn't need to know them.&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;Mappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;RegionAMIMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ap-south-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AMI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ami-0abcdef1234567890&lt;/span&gt;
    &lt;span class="na"&gt;us-east-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AMI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ami-0987654321fedcba&lt;/span&gt;
    &lt;span class="na"&gt;eu-west-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AMI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ami-0a1b2c3d4e5f67890&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyEC2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::Instance&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ImageId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!FindInMap&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;RegionAMIMap&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;AWS&lt;/span&gt;&lt;span class="pi"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;Region&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;AMI&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When deployed in Mumbai → uses Mumbai AMI automatically. The deployer doesn't touch AMI IDs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other uses:&lt;/strong&gt; environment-specific instance sizes (prod gets &lt;code&gt;t3.large&lt;/code&gt;, dev gets &lt;code&gt;t3.micro&lt;/code&gt;), region-specific endpoint URLs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limit: max 200 mappings, 200 attributes each.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  CF Outputs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Outputs&lt;/strong&gt; are values CloudFormation surfaces after a stack is created — important resource identifiers made easy to find and reuse.&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;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;BucketName&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;S3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bucket"&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyBucket&lt;/span&gt;

  &lt;span class="na"&gt;BucketARN&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ARN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;S3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bucket"&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;MyBucket.Arn&lt;/span&gt;
    &lt;span class="na"&gt;Export&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${AWS::StackName}-BucketARN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Three uses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Console visibility&lt;/strong&gt; — after stack creation, Outputs appear in CloudFormation console. Easy to find the ALB DNS name or RDS endpoint without digging through service consoles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-stack references&lt;/strong&gt; — one stack exports an Output, another imports it with &lt;code&gt;Fn::ImportValue&lt;/code&gt;. Networking stack exports VPC ID → all application stacks import it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt; — scripts query Outputs to get resource identifiers dynamically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limit: max 200 outputs per template.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  CF Conditions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Conditions&lt;/strong&gt; add boolean logic — create resources or set property values only when certain conditions are true.&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;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
    &lt;span class="na"&gt;AllowedValues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;Conditions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;IsProduction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Equals&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyBucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::S3::Bucket&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;VersioningConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!If&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;IsProduction&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Enabled&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Suspended&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;ReadReplica&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::RDS::DBInstance&lt;/span&gt;
    &lt;span class="na"&gt;Condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IsProduction&lt;/span&gt;    &lt;span class="c1"&gt;# Only created when IsProduction is true&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# read replica config...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Condition functions:&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;Function&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!Equals [a, b]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;True if a equals b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!Not [condition]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inverts a condition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!And [c1, c2]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;True if both are true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!Or [c1, c2]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;True if either is true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!If [condition, true_value, false_value]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Conditional value&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Limit: max 512 conditions per template.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  CF Intrinsic Functions
&lt;/h2&gt;

&lt;p&gt;Built-in functions that build dynamic values, reference resources, and manipulate strings.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Short form&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ref&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;!Ref&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Default identifier of a resource or parameter value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Fn::GetAtt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;!GetAtt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specific attribute of a resource (ARN, DNS name, IP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Fn::Sub&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;!Sub&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String substitution — inserts variable values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Fn::Join&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;!Join&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Joins a list with a delimiter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Fn::Select&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;!Select&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns one item from a list by index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Fn::FindInMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;!FindInMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Looks up a value in a Mappings table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Fn::If&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;!If&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns one of two values based on a Condition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Fn::ImportValue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Imports an Output exported by another stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Fn::Base64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;!Base64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Encodes string as Base64 (used for EC2 User Data)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Fn::Split&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;!Split&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Splits a string into a list&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;!Ref&lt;/code&gt; vs &lt;code&gt;!GetAtt&lt;/code&gt; — the key distinction:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;!Ref MyBucket&lt;/code&gt; → bucket name (the default identifier for S3)&lt;br&gt;
&lt;code&gt;!GetAtt MyBucket.Arn&lt;/code&gt; → bucket ARN (a specific attribute)&lt;br&gt;
&lt;code&gt;!GetAtt MyInstance.PublicIp&lt;/code&gt; → public IP of EC2 (a specific attribute)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Practical example:&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="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyInstance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::Instance&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;InstanceType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;InstanceTypeParam&lt;/span&gt;          &lt;span class="c1"&gt;# value from Parameter&lt;/span&gt;
      &lt;span class="na"&gt;ImageId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!FindInMap&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;AMIMap&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;AWS&lt;/span&gt;&lt;span class="pi"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;Region&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;AMI&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# from Mappings&lt;/span&gt;
      &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Name&lt;/span&gt;
          &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${AWS::StackName}-webserver"&lt;/span&gt;  &lt;span class="c1"&gt;# string substitution&lt;/span&gt;
      &lt;span class="na"&gt;UserData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="kt"&gt;!Base64&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;#!/bin/bash&lt;/span&gt;
          &lt;span class="s"&gt;yum update -y&lt;/span&gt;
          &lt;span class="s"&gt;yum install -y httpd&lt;/span&gt;
          &lt;span class="s"&gt;systemctl start httpd&lt;/span&gt;

&lt;span class="na"&gt;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PublicIP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;MyInstance.PublicIp&lt;/span&gt;
  &lt;span class="na"&gt;FullDNS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Join&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tejascloud.com"&lt;/span&gt;&lt;span class="pi"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  CF Service Roles
&lt;/h2&gt;

&lt;p&gt;By default, CloudFormation uses your own IAM permissions to create resources. A &lt;strong&gt;Service Role&lt;/strong&gt; is an IAM Role that CloudFormation assumes instead — decoupling what humans can do from what CloudFormation can do.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; A developer needs to deploy a stack that creates VPCs, security groups, and EC2 instances. You could give the developer those IAM permissions directly — but then they could also manually create those resources outside CloudFormation, bypassing governance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With a Service Role:&lt;/strong&gt; The developer has &lt;code&gt;cloudformation:CreateStack&lt;/code&gt; and &lt;code&gt;iam:PassRole&lt;/code&gt; only. The Service Role has &lt;code&gt;ec2:*&lt;/code&gt;, &lt;code&gt;vpc:*&lt;/code&gt;, &lt;code&gt;s3:*&lt;/code&gt; — whatever the stack needs. CloudFormation assumes the role and creates resources. The developer gets the infrastructure without direct access to the underlying services.&lt;/p&gt;

&lt;p&gt;🎯 &lt;strong&gt;Security benefit:&lt;/strong&gt; Least privilege for humans, necessary permissions for automation. Humans can't bypass the CloudFormation process to create resources manually.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  CF Stack Policies and Deletion Policies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Stack Policy
&lt;/h3&gt;

&lt;p&gt;A JSON document attached to a stack that controls which resources can be updated or replaced during a stack update.&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;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="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;"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;"Update:Replace"&lt;/span&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="s2"&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;"LogicalResourceId/ProductionDatabase"&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 blocks &lt;code&gt;Replace&lt;/code&gt; operations on the &lt;code&gt;ProductionDatabase&lt;/code&gt; resource — even if a template change would normally require it. CloudFormation fails the update rather than replace the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deletion Policy
&lt;/h3&gt;

&lt;p&gt;Set on individual resources inside the template — controls what happens when the stack is deleted or the resource is removed from the template.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Deletion Policy&lt;/th&gt;
&lt;th&gt;What happens when stack is deleted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Delete&lt;/code&gt; (default)&lt;/td&gt;
&lt;td&gt;Resource is deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Retain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resource stays, CloudFormation stops managing it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Snapshot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AWS takes a backup before deleting (RDS, EBS, Redshift)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyDatabase&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::RDS::DBInstance&lt;/span&gt;
    &lt;span class="na"&gt;DeletionPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Snapshot&lt;/span&gt;    &lt;span class="c1"&gt;# backup before delete&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;

  &lt;span class="na"&gt;MyBucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::S3::Bucket&lt;/span&gt;
    &lt;span class="na"&gt;DeletionPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Retain&lt;/span&gt;      &lt;span class="c1"&gt;# keep the bucket, just stop managing it&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Production rule:&lt;/strong&gt; Always &lt;code&gt;DeletionPolicy: Snapshot&lt;/code&gt; on RDS. Always &lt;code&gt;DeletionPolicy: Retain&lt;/code&gt; on S3 buckets with important data. Losing a production database because of the default &lt;code&gt;Delete&lt;/code&gt; policy is a real and painful mistake.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  CF Change Sets
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Change Set&lt;/strong&gt; is a preview of what CloudFormation will do before it actually does it — shows every resource action: Add, Modify, Remove, Replace.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A &lt;code&gt;Replace&lt;/code&gt; action means the resource is deleted and recreated — downtime for databases, EC2 instances, etc. A Change Set lets you catch this before it happens.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The Change Set workflow:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Modify your YAML template → Upload new template → Create Change Set → CloudFormation computes the diff → Review every proposed change → If correct: Execute → CloudFormation applies → If risky or wrong: Delete the Change Set → nothing applied, stack untouched.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;When to always use Change Sets:&lt;/strong&gt; Before updating any production stack — catching a &lt;code&gt;Replace&lt;/code&gt; before it runs is the difference between planned maintenance and an unplanned outage.&lt;/p&gt;




&lt;h2&gt;
  
  
  CF Stack Sets
&lt;/h2&gt;

&lt;p&gt;Deploys the same CloudFormation stack across &lt;strong&gt;multiple AWS accounts and/or regions&lt;/strong&gt; in a single operation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Without Stack Sets: log into 10 accounts, switch to 3 regions in each, deploy 30 times manually.&lt;br&gt;
With Stack Sets: define target accounts and regions once → CloudFormation deploys to all 30 in parallel.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Common uses:&lt;/strong&gt; security baseline (CloudTrail, Config, GuardDuty) to every account, centralized logging in every region, standard networking across all accounts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two modes:&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;Mode&lt;/th&gt;
&lt;th&gt;How targets are defined&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-managed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You explicitly list account IDs and regions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service-managed (AWS Organizations)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-deploys to all accounts in an OU — new accounts added to the OU automatically get the stack&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Stack Instance&lt;/strong&gt; = one deployed stack in one specific account + region combination.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧪 Lab — Full Build-Up with Real Failures
&lt;/h2&gt;

&lt;p&gt;This is the complete lab sequence from class — building a full networking + EC2 stack resource by resource using Change Sets. The failures are included because they're part of what makes CloudFormation real — and knowing how to fix them is the skill.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overall Lab Flow
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;YAML Blueprint → Parameters → VPC → Subnet → Internet Gateway → Gateway Attachment → Route Table → Security Group → Key Pair Parameter → EC2 → Dependencies via &lt;code&gt;!Ref&lt;/code&gt; → Change Sets → Execute → Failures → Fix Template/Parameter → New Change Set → Execute → Final Infrastructure ✅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step-by-Step Lab — Exact Sequence
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Start: Create VPC + CIDR&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create Stack → initial template with just VPC and CIDR → &lt;code&gt;CREATE_COMPLETE&lt;/code&gt; ✅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Add Subnet via Change Set&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Modify template — add Subnet resource referencing VPC via &lt;code&gt;!Ref&lt;/code&gt; → Create Change Set → Execute Change Set&lt;br&gt;
❌ &lt;strong&gt;FAILED: Invalid Availability Zone&lt;/strong&gt; — AZ name in template was wrong&lt;br&gt;
Fix AZ in template → Create New Change Set → Execute Change Set&lt;br&gt;
Subnet Created ✅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Add Internet Gateway&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Modify template — add &lt;code&gt;AWS::EC2::InternetGateway&lt;/code&gt;, add &lt;code&gt;AWS::EC2::VPCGatewayAttachment&lt;/code&gt; using &lt;code&gt;!Ref VPC&lt;/code&gt; and &lt;code&gt;!Ref IGW&lt;/code&gt; → Create Change Set → Execute&lt;br&gt;
MyInternetGateway Created ✅&lt;br&gt;
AttachGateway Created ✅&lt;br&gt;
Internet Gateway Attached to VPC ✅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Add Route Table&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Modify template — add &lt;code&gt;AWS::EC2::RouteTable&lt;/code&gt;, add Route with destination &lt;code&gt;0.0.0.0/0&lt;/code&gt; → IGW, add Subnet Route Table Association → Create Change Set → Execute&lt;br&gt;
Route Table Created + Associated ✅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Add Security Group&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Modify template — add &lt;code&gt;AWS::EC2::SecurityGroup&lt;/code&gt; with inbound rules (SSH 22, HTTP 80) → Create Change Set → Execute&lt;br&gt;
Security Group Created ✅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Add Key Pair as Parameter + Create EC2&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Modify template — add KeyPair Parameter (&lt;code&gt;Type: AWS::EC2::KeyPair::KeyName&lt;/code&gt;), add EC2 instance referencing AMI from Mappings, SG from &lt;code&gt;!Ref&lt;/code&gt;, subnet from &lt;code&gt;!Ref&lt;/code&gt;, key pair from &lt;code&gt;!Ref KeyPairParam&lt;/code&gt; → Create Change Set → Execute&lt;br&gt;
❌ &lt;strong&gt;FAILED: Invalid/Non-existent Key Pair&lt;/strong&gt; — wrong key pair name entered&lt;br&gt;
Correct Key Pair Parameter → Create New Change Set → Execute Change Set&lt;br&gt;
EC2 Created ✅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  What the Lab Teaches
&lt;/h3&gt;

&lt;p&gt;These failures are not bugs in the process — they're the lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Invalid AZ failure:&lt;/strong&gt; Template contained a hardcoded AZ that doesn't exist in the target region. Fix: use a Parameter for AZ name, or use &lt;code&gt;!Select [0, !GetAZs !Ref AWS::Region]&lt;/code&gt; to dynamically pick the first available AZ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invalid Key Pair failure:&lt;/strong&gt; AWS-specific parameter types (&lt;code&gt;AWS::EC2::KeyPair::KeyName&lt;/code&gt;) validate that the key pair exists in the account — if you type a name that doesn't exist, it fails at parameter validation before any resource is created. Fix: enter the exact name of a key pair that exists in your account.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Change Set → Execute → Fail → Fix → New Change Set → Execute cycle is normal production CloudFormation workflow. The key is that failures don't leave your infrastructure in an unknown state — CloudFormation's rollback mechanism returns you to the last stable state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Template Structure (Everything Together)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2010-09-09"&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VPC&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;EC2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;networking&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stack"&lt;/span&gt;

&lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;KeyPairName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::KeyPair::KeyName&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;existing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;EC2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pair"&lt;/span&gt;

  &lt;span class="na"&gt;AvailabilityZone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::AvailabilityZone::Name&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AZ&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;subnet&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in"&lt;/span&gt;

&lt;span class="na"&gt;Mappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;RegionAMI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ap-south-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AMI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ami-0f5ee92e2d63afc18&lt;/span&gt;
    &lt;span class="na"&gt;us-east-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AMI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ami-0abcdef1234567890&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyVPC&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::VPC&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CidrBlock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.0.0.0/16"&lt;/span&gt;
      &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Name&lt;/span&gt;
          &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${AWS::StackName}-VPC"&lt;/span&gt;

  &lt;span class="na"&gt;MySubnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::Subnet&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;VpcId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyVPC&lt;/span&gt;
      &lt;span class="na"&gt;CidrBlock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.0.1.0/24"&lt;/span&gt;
      &lt;span class="na"&gt;AvailabilityZone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;AvailabilityZone&lt;/span&gt;
      &lt;span class="na"&gt;MapPublicIpOnLaunch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;MyIGW&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::InternetGateway&lt;/span&gt;

  &lt;span class="na"&gt;AttachGateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::VPCGatewayAttachment&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;VpcId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyVPC&lt;/span&gt;
      &lt;span class="na"&gt;InternetGatewayId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyIGW&lt;/span&gt;

  &lt;span class="na"&gt;MyRouteTable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::RouteTable&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;VpcId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyVPC&lt;/span&gt;

  &lt;span class="na"&gt;PublicRoute&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::Route&lt;/span&gt;
    &lt;span class="na"&gt;DependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AttachGateway&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;RouteTableId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyRouteTable&lt;/span&gt;
      &lt;span class="na"&gt;DestinationCidrBlock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0/0"&lt;/span&gt;
      &lt;span class="na"&gt;GatewayId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyIGW&lt;/span&gt;

  &lt;span class="na"&gt;SubnetRouteTableAssoc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::SubnetRouteTableAssociation&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SubnetId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MySubnet&lt;/span&gt;
      &lt;span class="na"&gt;RouteTableId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyRouteTable&lt;/span&gt;

  &lt;span class="na"&gt;MySecurityGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::SecurityGroup&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GroupDescription&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Allow&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;SSH&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;HTTP"&lt;/span&gt;
      &lt;span class="na"&gt;VpcId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyVPC&lt;/span&gt;
      &lt;span class="na"&gt;SecurityGroupIngress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;IpProtocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
          &lt;span class="na"&gt;FromPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
          &lt;span class="na"&gt;ToPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
          &lt;span class="na"&gt;CidrIp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0/0"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;IpProtocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
          &lt;span class="na"&gt;FromPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;ToPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;CidrIp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0/0"&lt;/span&gt;

  &lt;span class="na"&gt;MyEC2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::Instance&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;InstanceType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;t2.micro&lt;/span&gt;
      &lt;span class="na"&gt;KeyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;KeyPairName&lt;/span&gt;
      &lt;span class="na"&gt;ImageId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!FindInMap&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;RegionAMI&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;AWS&lt;/span&gt;&lt;span class="pi"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;Region&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;AMI&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;SubnetId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MySubnet&lt;/span&gt;
      &lt;span class="na"&gt;SecurityGroupIds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MySecurityGroup&lt;/span&gt;
      &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Name&lt;/span&gt;
          &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${AWS::StackName}-WebServer"&lt;/span&gt;

&lt;span class="na"&gt;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;InstancePublicIP&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Public&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;IP&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;EC2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;instance"&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;MyEC2.PublicIp&lt;/span&gt;

  &lt;span class="na"&gt;VPCID&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VPC&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID"&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyVPC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Useful Pseudo Parameters
&lt;/h2&gt;

&lt;p&gt;Available automatically — no declaration needed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pseudo Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!Ref AWS::Region&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Current region (e.g., &lt;code&gt;ap-south-1&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!Ref AWS::AccountId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Current AWS account ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!Ref AWS::StackName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name of the current stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!Ref AWS::StackId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full ARN of the current stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!Ref AWS::NoValue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes a property (used in Conditions)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;IaC&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define infrastructure as code → version, review, reproduce, automate&lt;/li&gt;
&lt;li&gt;Template is the blueprint. Stack is the deployed infrastructure.&lt;/li&gt;
&lt;li&gt;Same template → multiple stacks → identical environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Template Structure (with limits)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AWSTemplateFormatVersion&lt;/code&gt; → format version, always &lt;code&gt;"2010-09-09"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Metadata&lt;/code&gt; → UI hints, cfn-init config&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Parameters&lt;/code&gt; → max 200, user input at deploy time&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Rules&lt;/code&gt; → cross-parameter validation logic (under-used but important)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Mappings&lt;/code&gt; → max 200 mappings × 200 attrs each, lookup tables&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Conditions&lt;/code&gt; → max 512, boolean logic for conditional resources&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Transform&lt;/code&gt; → SAM/macro expansion&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Resources&lt;/code&gt; → REQUIRED, max 500, the actual AWS resources&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Outputs&lt;/code&gt; → max 200, values surfaced after stack creation&lt;/li&gt;
&lt;/ul&gt;

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

&lt;blockquote&gt;
&lt;p&gt;[1] Parse + Validate → [2] Transform Expansion → [3] Pre-deployment Validation → [4] Build DAG → [5] Compute Change Set → [6] Evaluate Hooks → [7] Parallel Execution (each resource: Resource Provider → API call → Stabilization Loop 20-90 min) → [8] Success: Commit State / Failure: Rollback in reverse DAG order&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Key Intrinsic Functions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;!Ref&lt;/code&gt; → default identifier (bucket name, instance ID, parameter value)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!GetAtt&lt;/code&gt; → specific attribute (ARN, DNS, public IP)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!Sub&lt;/code&gt; → string substitution with variables&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!FindInMap&lt;/code&gt; → lookup value in Mappings table&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!If&lt;/code&gt; → conditional value&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!Base64&lt;/code&gt; → encode string for User Data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Deletion Policy&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Delete&lt;/code&gt; (default) → resource deleted with stack&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Retain&lt;/code&gt; → resource stays, CloudFormation stops managing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Snapshot&lt;/code&gt; → backup before deletion (RDS, EBS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Change Set&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preview before applying — shows Add, Modify, Remove, Replace&lt;/li&gt;
&lt;li&gt;Replace = resource deleted and recreated = downtime&lt;/li&gt;
&lt;li&gt;Always use before updating production stacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stack Sets&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same stack → multiple accounts/regions in one operation&lt;/li&gt;
&lt;li&gt;Service-managed mode: auto-deploys to new accounts in an OU&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: What is the difference between a CloudFormation Template and a Stack?&lt;/strong&gt;&lt;br&gt;
A template is a YAML or JSON file — the blueprint that creates nothing by itself. A stack is what CloudFormation creates when it executes a template — a group of AWS resources managed together as one unit. The same template can create multiple stacks (dev, staging, prod), each independently managed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: How does CloudFormation handle resource dependencies?&lt;/strong&gt;&lt;br&gt;
CloudFormation builds a Directed Acyclic Graph (DAG) from &lt;code&gt;DependsOn&lt;/code&gt; attributes, &lt;code&gt;!Ref&lt;/code&gt; references, and &lt;code&gt;!GetAtt&lt;/code&gt; calls. Resources with no dependencies are created in parallel. Dependent resources are created in the correct order. On failure, rollback follows the reverse DAG order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: What happens during the Stabilization Loop?&lt;/strong&gt;&lt;br&gt;
After making a Create/Update/Delete API call through a Resource Provider, CloudFormation doesn't assume the resource is immediately ready. It polls the resource until it reaches its required stable state. Some resources like RDS or Elasticsearch can take 20–90 minutes to stabilize. This is why CloudFormation stacks spend a long time in &lt;code&gt;CREATE_IN_PROGRESS&lt;/code&gt; — they're waiting for stabilization, not making slow API calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: What is a Change Set and why should you use one before updating a production stack?&lt;/strong&gt;&lt;br&gt;
A Change Set previews every change CloudFormation will make — which resources will be Added, Modified, Removed, or Replaced. A &lt;code&gt;Replace&lt;/code&gt; action deletes and recreates a resource, causing downtime. Using a Change Set catches this before applying, preventing unplanned outages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: What is the difference between a Stack Policy and a Deletion Policy?&lt;/strong&gt;&lt;br&gt;
A Stack Policy is a JSON document attached to a stack that prevents specific resources from being updated or replaced during stack updates — protecting critical resources from accidental replacement. A Deletion Policy is a property on individual resources inside the template that controls what happens when the stack is deleted: Delete (remove), Retain (keep), or Snapshot (backup before deleting).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: What is the difference between CF Parameters and CF Rules?&lt;/strong&gt;&lt;br&gt;
Parameters define the input values a deployer provides at stack creation or update. &lt;code&gt;AllowedValues&lt;/code&gt; can restrict a single parameter to a fixed list. Rules are validation logic that works across multiple parameters — for example, "when Environment is &lt;code&gt;prod&lt;/code&gt;, InstanceType must not be &lt;code&gt;t3.micro&lt;/code&gt;." Parameters control individual inputs; Rules validate that combinations of inputs make sense together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: What is &lt;code&gt;!Ref&lt;/code&gt; vs &lt;code&gt;!GetAtt&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;!Ref&lt;/code&gt; returns the default identifier of a resource — for S3 it returns the bucket name, for EC2 it returns the instance ID. &lt;code&gt;!GetAtt&lt;/code&gt; returns a specific named attribute — &lt;code&gt;!GetAtt MyBucket.Arn&lt;/code&gt; returns the ARN, &lt;code&gt;!GetAtt MyInstance.PublicIp&lt;/code&gt; returns the public IP. Use &lt;code&gt;!Ref&lt;/code&gt; for the primary identifier, &lt;code&gt;!GetAtt&lt;/code&gt; for everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q8: What is a CloudFormation Service Role and why would you use it?&lt;/strong&gt;&lt;br&gt;
A Service Role is an IAM Role that CloudFormation assumes to create resources, instead of using the deploying user's permissions. It enforces governance — developers can deploy CloudFormation stacks without having direct IAM permissions to create EC2s or VPCs. CloudFormation assumes the role and creates resources, keeping least privilege for humans while enabling necessary automation.&lt;/p&gt;




&lt;h2&gt;
  
  
  📝 Assignment
&lt;/h2&gt;

&lt;p&gt;Create a CloudFormation stack that deploys an Auto Scaling Group with a Launch Template and hosts a web server using a User Data script. The web server should be accessible via an Application Load Balancer. All resources created and managed by a single CloudFormation template.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AWS Session 16 — AWS CloudFormation | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AWS Route 53 — DNS Fundamentals, Hosted Zones, Routing Policies &amp; Resolvers</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Sat, 08 Aug 2026 12:15:57 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-route-53-dns-fundamentals-hosted-zones-routing-policies-resolvers-2727</link>
      <guid>https://dev.to/tejas_shinkar/aws-route-53-dns-fundamentals-hosted-zones-routing-policies-resolvers-2727</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. Route 53 is where networking meets the internet — how domain names reach your applications, how traffic gets distributed intelligently, and how AWS and on-premises networks resolve each other's names.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;DNS Pre-Requisites — How DNS Works&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Complete DNS Resolution Flow&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;What is Route 53&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Hosted Zones — Public vs Private&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Hosted Zone ID&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;DNS Record Types and Use Cases&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;NS and SOA Records — Auto-Created, Never Delete&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Alias Record — AWS-Specific&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Landing Zone — Brief Context&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Route 53 Routing Policies — All 8&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Route 53 Traffic Policies&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Route 53 Resolvers&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Inbound vs Outbound Resolver Endpoints&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Route 53 Forwarders&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Split-Horizon DNS&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Interview Questions&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Practice Tasks&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  DNS Pre-Requisites — How DNS Works
&lt;/h2&gt;

&lt;p&gt;DNS is the reason you type &lt;code&gt;google.com&lt;/code&gt; instead of &lt;code&gt;142.250.195.46&lt;/code&gt;. Before understanding Route 53, these fundamentals must be solid.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Vocabulary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Domain&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Human-readable name — &lt;code&gt;google.com&lt;/code&gt;, &lt;code&gt;tejascloud.in&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IP Address&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Machine address — &lt;code&gt;54.21.11.90&lt;/code&gt; — what computers actually use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DNS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The translation system — converts domain names → IP addresses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TLD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Top-Level Domain — the last part after the final dot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Time To Live — how long a DNS response is cached before re-querying&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Recursive Resolver&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Finds the answer for the client by querying other DNS servers, caches the result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authoritative DNS Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stores the official DNS records for a domain — returns the definitive answer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Common TLDs:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;.com&lt;/code&gt; → commercial · &lt;code&gt;.org&lt;/code&gt; → organizations · &lt;code&gt;.net&lt;/code&gt; → network · &lt;code&gt;.in&lt;/code&gt; → India · &lt;code&gt;.uk&lt;/code&gt; → United Kingdom · &lt;code&gt;.edu&lt;/code&gt; → education · &lt;code&gt;.gov&lt;/code&gt; → government&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  The Complete DNS Resolution Flow
&lt;/h3&gt;

&lt;p&gt;This is the full journey a DNS query takes — from the moment you type a URL to the moment your browser connects to a web server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;User types&lt;/strong&gt; &lt;code&gt;tejascloud.com&lt;/code&gt; in browser&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Browser Cache:&lt;/strong&gt; Does my browser already know this IP from a recent visit? → Cache Miss → continue&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — OS Cache:&lt;/strong&gt; Does the operating system's DNS cache have it? → Cache Miss → continue&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Recursive Resolver (your ISP or 8.8.8.8):&lt;/strong&gt; Does the resolver's cache have it? → Cache Miss → it begins querying on your behalf&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Root DNS Server:&lt;/strong&gt; "Who manages &lt;code&gt;.com&lt;/code&gt; domains?" → returns address of the &lt;code&gt;.com&lt;/code&gt; TLD server&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — TLD DNS Server (.com):&lt;/strong&gt; "Who manages &lt;code&gt;tejascloud.com&lt;/code&gt;?" → reads the NS record → returns Route 53 name server addresses&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6 — Authoritative DNS Server (Route 53):&lt;/strong&gt; "What's the IP for &lt;code&gt;tejascloud.com&lt;/code&gt;?" → checks the Hosted Zone → finds the A Record → returns &lt;code&gt;54.21.11.90&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7 — Recursive Resolver&lt;/strong&gt; caches the result for the TTL duration → returns IP to the browser&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8 — Browser&lt;/strong&gt; connects to &lt;code&gt;54.21.11.90&lt;/code&gt; → TCP Handshake → TLS Handshake (for HTTPS) → HTTP Request → Web Server → Website loads&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why TTL matters:&lt;/strong&gt; If your A record has TTL = 300 seconds, every DNS resolver in the world caches your IP for 5 minutes. If you change your server's IP, some users will still reach the old IP for up to 5 minutes. Before planned migrations, lower the TTL to 60 seconds a day in advance so the change propagates faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Route 53
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Amazon Route 53&lt;/strong&gt; is AWS's fully managed, highly available, and scalable DNS service. It serves as the &lt;strong&gt;Authoritative DNS Server&lt;/strong&gt; for your domains — when the TLD server (.com) asks "who manages &lt;code&gt;tejascloud.com&lt;/code&gt;?", it's Route 53 name servers that answer.&lt;/p&gt;

&lt;p&gt;The name "Route 53" comes from port 53 — the standard port DNS uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Route 53 does three things:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domain Registration&lt;/strong&gt; — buy and manage domain names directly through AWS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS Routing&lt;/strong&gt; — host DNS records and route traffic to the correct resources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Checking&lt;/strong&gt; — monitor endpoints and route traffic away from unhealthy ones&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Hosted Zones — Public vs Private
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Hosted Zone&lt;/strong&gt; is the container for all DNS records belonging to one domain. It's Route 53's way of organizing and storing the DNS configuration for &lt;code&gt;tejascloud.com&lt;/code&gt; — all the A records, CNAME records, MX records, and so on live inside the Hosted Zone for that domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The library analogy:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Route 53 is the library. A Hosted Zone is one bookshelf — dedicated to a single domain. The DNS records (A, CNAME, MX, TXT) are the individual books on that shelf. When someone asks "what's the IP for tejascloud.com?" — the librarian (Route 53) goes to the right bookshelf (Hosted Zone) and finds the right book (A record).&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route 53
   │
   ├── Hosted Zone: tejascloud.com (bookshelf)
   │       ├── A Record: tejascloud.com → 54.21.11.90
   │       ├── CNAME: www → tejascloud.com
   │       ├── MX Record: mail server
   │       └── TXT Record: verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Public Hosted Zone
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Answers DNS queries coming from the &lt;strong&gt;public internet&lt;/strong&gt;. Anyone anywhere can query it. This is what makes &lt;code&gt;tejascloud.com&lt;/code&gt; accessible to the world.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Used for: public websites, customer-facing APIs, any domain that should be reachable from the internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Private Hosted Zone
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Answers DNS queries only from within &lt;strong&gt;associated AWS VPCs&lt;/strong&gt;. Invisible to the public internet. Used for internal service discovery and private naming.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Used for: internal microservices (&lt;code&gt;payment.internal&lt;/code&gt;, &lt;code&gt;db.internal&lt;/code&gt;), RDS endpoints with friendly names, any resource that should only be accessible within the VPC.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview scenario:&lt;/strong&gt; "How do you give your internal services readable DNS names inside a VPC?" → Create a Private Hosted Zone associated with your VPC. Add A records like &lt;code&gt;payment-service.internal → 10.200.1.45&lt;/code&gt;. EC2 instances in the VPC resolve the name automatically. The internet can't see it at all.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Hosted Zone ID
&lt;/h2&gt;

&lt;p&gt;Every Hosted Zone has a unique ID (format: &lt;code&gt;Z1234567890ABC&lt;/code&gt;). Used in automation tools to target the exact Hosted Zone when creating or updating DNS records.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In Terraform:&lt;/strong&gt; &lt;code&gt;zone_id = "Z1234567890ABC"&lt;/code&gt; → tells Terraform which Hosted Zone to add the record to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In AWS CLI:&lt;/strong&gt; &lt;code&gt;aws route53 change-resource-record-sets --hosted-zone-id Z1234567890ABC ...&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In CloudFormation:&lt;/strong&gt; referenced when creating Route 53 resources programmatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's not a concept you interact with in the console much — it matters most in infrastructure-as-code pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  DNS Record Types and Use Cases
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;DNS Record&lt;/strong&gt; is an entry inside a Hosted Zone that tells DNS how to resolve or handle a domain name.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Record Type&lt;/th&gt;
&lt;th&gt;Purpose&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;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Domain → IPv4 address&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tejascloud.com → 54.21.11.90&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AAAA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Domain → IPv6 address&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tejascloud.com → 2001:db8::1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CNAME&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Domain → Another domain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;www.tejascloud.com → tejascloud.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mail server for the domain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tejascloud.com → mail.google.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TXT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Verification, SPF, DKIM, DMARC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"v=spf1 include:amazon.com ~all"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Which name servers manage the domain&lt;/td&gt;
&lt;td&gt;Auto-created by Route 53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SOA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DNS zone metadata&lt;/td&gt;
&lt;td&gt;Auto-created by Route 53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PTR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;IP → Domain (reverse DNS)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;54.21.11.90 → tejascloud.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Real-world example — hosting a website on EC2:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EC2 Public IP: &lt;code&gt;54.21.11.90&lt;/code&gt;&lt;br&gt;
You create an A Record in the Hosted Zone: &lt;code&gt;tejascloud.com → 54.21.11.90&lt;/code&gt;&lt;br&gt;
User types &lt;code&gt;tejascloud.com&lt;/code&gt; → Route 53 checks the Hosted Zone → finds the A Record → returns &lt;code&gt;54.21.11.90&lt;/code&gt; → browser connects to your EC2&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  CNAME Limitations
&lt;/h3&gt;

&lt;p&gt;CNAME cannot be used on a &lt;strong&gt;root domain (apex domain)&lt;/strong&gt;. You cannot create a CNAME for &lt;code&gt;tejascloud.com&lt;/code&gt; itself — only for subdomains like &lt;code&gt;www.tejascloud.com&lt;/code&gt;. This is a DNS standard limitation, not just Route 53. For pointing the root domain to an AWS resource, use an &lt;strong&gt;Alias Record&lt;/strong&gt; instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  NS and SOA Records — Auto-Created, Never Delete
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NS Record (Name Server):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Automatically created when you create a Hosted Zone. Contains the four Route 53 name servers responsible for your domain (e.g., &lt;code&gt;ns-1234.awsdns-23.com&lt;/code&gt;). During DNS resolution, the TLD server (.com) uses the NS record to know which Route 53 name servers manage your domain and where to send queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never delete the NS record.&lt;/strong&gt; Deleting it would break DNS resolution for your entire domain — the TLD servers would have nowhere to point queries.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;SOA Record (Start of Authority):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Also automatically created. Stores DNS zone metadata — primary name server, serial number (increments on every zone change), refresh and retry intervals, expire time, and default TTL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never delete or modify the SOA record&lt;/strong&gt; unless you know exactly why. It's used internally by DNS infrastructure to manage zone transfers and cache behavior.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Alias Record — AWS-Specific
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Alias Record&lt;/strong&gt; is Route 53's solution to two problems: the CNAME root domain limitation and the need to point domains to AWS resources that don't have fixed IP addresses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Alias records can point to:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application Load Balancers (ALB)&lt;/li&gt;
&lt;li&gt;CloudFront distributions&lt;/li&gt;
&lt;li&gt;S3 static websites&lt;/li&gt;
&lt;li&gt;API Gateway endpoints&lt;/li&gt;
&lt;li&gt;Elastic Beanstalk environments&lt;/li&gt;
&lt;li&gt;Another Route 53 record in the same Hosted Zone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why you need Alias instead of CNAME for AWS resources:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An ALB doesn't have a fixed IP — it has a DNS name like &lt;code&gt;my-alb-123456789.ap-south-1.elb.amazonaws.com&lt;/code&gt;. And you want &lt;code&gt;tejascloud.com&lt;/code&gt; (the root domain) to point to it. A CNAME can't be used on a root domain. An Alias record can. AWS resolves the Alias internally to the ALB's current IP, always fresh — you never manage IPs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Key difference from CNAME:&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;&lt;/th&gt;
&lt;th&gt;CNAME&lt;/th&gt;
&lt;th&gt;Alias&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Works on root domain?&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Points to&lt;/td&gt;
&lt;td&gt;Any domain&lt;/td&gt;
&lt;td&gt;Only supported AWS resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health check support&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (Route 53 can health check Alias targets)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS query charges&lt;/td&gt;
&lt;td&gt;Normal query charges&lt;/td&gt;
&lt;td&gt;Free when pointing to AWS resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Returns to client&lt;/td&gt;
&lt;td&gt;Another domain (CNAME chain)&lt;/td&gt;
&lt;td&gt;Actual IP directly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert and interview tip:&lt;/strong&gt; "How do you point your root domain &lt;code&gt;example.com&lt;/code&gt; to an ALB?" → Use an Alias record (not a CNAME — CNAMEs can't be on root domains). Alias is AWS-specific and resolves directly to the resource's current IPs.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Landing Zone — Brief Context
&lt;/h2&gt;

&lt;p&gt;You'll hear this term in enterprise AWS contexts. A &lt;strong&gt;Landing Zone&lt;/strong&gt; is a secure, pre-built AWS foundation for an entire organization — not a single account but a structured multi-account setup.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's built using &lt;strong&gt;AWS Control Tower&lt;/strong&gt; and includes: AWS Organizations (for multiple accounts), Organizational Units (OUs), IAM Identity Center (SSO), CloudTrail for audit logging, security guardrails, centralized networking, and standardized logging.&lt;/p&gt;

&lt;p&gt;The goal: before any workload is deployed, the organization has a secure, governed, scalable starting point. Route 53 is part of this — centralized DNS for the entire organization's accounts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is more relevant at the architecture and governance level than day-to-day engineering — worth knowing the term and what it represents.&lt;/p&gt;




&lt;h2&gt;
  
  
  Route 53 Routing Policies — All 8
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Routing Policy&lt;/strong&gt; decides which DNS answer Route 53 returns when a client queries for a domain. Route 53 supports 8 policies, each solving a different problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Simple Routing
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Returns one or a few IP addresses without any logic — basic DNS. No health checks, no traffic splitting, no awareness of location or latency.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use for: a single resource (one EC2, one ALB) with no need for intelligent routing.&lt;/p&gt;

&lt;p&gt;Example: &lt;code&gt;tejascloud.com → ALB DNS name&lt;/code&gt; — every user everywhere gets the same answer.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Weighted Routing
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Splits traffic across multiple resources according to specified weights. Route 53 sends a percentage of DNS responses to each endpoint based on the weight assigned.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Weight 90 → Production ALB (version 1.0)&lt;br&gt;
Weight 10 → Staging ALB (version 2.0)&lt;br&gt;
Result: 90% of users hit the old version, 10% hit the new one — canary deployment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use for: A/B testing, canary deployments, gradually shifting traffic to a new version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weight calculation:&lt;/strong&gt; Route 53 uses the ratio — if you have weights 90 and 10, it returns the first record to 90/(90+10) = 90% of queries.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Latency-Based Routing
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Routes users to the AWS Region that provides the lowest network latency for them, based on AWS's measured latency data between the user's location and AWS Regions.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;A user in India → Route 53 measures latency to Mumbai (ap-south-1) vs N. Virginia (us-east-1) → returns Mumbai record because it's closest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use for: any application with users in multiple geographic locations where latency matters.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Important distinction:&lt;/strong&gt; Latency routing is based on actual measured network latency — not geographic distance. A user in Sri Lanka might have lower latency to Singapore than to Mumbai depending on network paths. Route 53 uses real data, not map distance.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  4. Failover Routing
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Routes traffic to a Primary resource normally. If the primary fails a health check, Route 53 automatically returns the Secondary (backup) record instead.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Primary → Production ALB in ap-south-1 (health checked)&lt;br&gt;
Secondary → Backup static S3 website showing "maintenance page"&lt;br&gt;
If Primary health check fails → Route 53 returns Secondary automatically&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use for: disaster recovery, high availability with a fallback endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requires health checks on the primary record&lt;/strong&gt; — Route 53 needs to know when to failover.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Geolocation Routing
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Routes traffic based on the &lt;strong&gt;geographic location of the user&lt;/strong&gt; — specifically their country or continent as determined by their IP address.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Requests from India → Mumbai ALB&lt;br&gt;
Requests from United States → N. Virginia ALB&lt;br&gt;
Requests from EU countries → Frankfurt ALB&lt;br&gt;
Default → Global ALB (for any location not explicitly mapped)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use for: serving localized content (language, pricing, regulations), data residency compliance (EU users must stay in EU), regional product catalogs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always configure a Default record&lt;/strong&gt; — without it, users from unmapped locations will get no DNS response.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Geolocation vs Latency — the key difference:&lt;/strong&gt; Geolocation routes based on where the user is located (country/continent). Latency routes based on which region responds fastest (actual network performance). A UK user might have lower latency to US East than EU-West due to network paths — Geolocation always sends them to the EU record regardless. Latency would send them to wherever is actually faster.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  6. Geoproximity Routing
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Routes traffic based on geographic distance between the user and the resource, with an optional &lt;strong&gt;bias&lt;/strong&gt; that expands or shrinks the effective area of a region.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key feature is &lt;strong&gt;bias&lt;/strong&gt; — a positive bias makes a region "bigger" (attracts more users from surrounding areas), a negative bias makes it "smaller."&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Mumbai region with bias +50 → expands Mumbai's effective reach → attracts users from South Asia, Middle East, East Africa&lt;br&gt;
Singapore region with no bias → normal reach&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use for: fine-grained geographic traffic control beyond simple country/continent boundaries, gradually shifting traffic from one region to another by adjusting biases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requires Route 53 Traffic Flow&lt;/strong&gt; to configure — it's the only policy that uses the visual traffic flow editor.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Geolocation vs Geoproximity:&lt;/strong&gt; Geolocation is rigid — "India → Mumbai, USA → Virginia." Geoproximity is flexible — "route based on distance, but adjust the effective range of each region using bias." Use Geoproximity when you need to fine-tune exactly which geographic area each endpoint serves.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  7. IP-Based Routing
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Routes traffic based on the &lt;strong&gt;source IP address (CIDR range)&lt;/strong&gt; of the DNS query, not the geographic location.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Your corporate network uses IPs in the range &lt;code&gt;203.0.113.0/24&lt;/code&gt; → you want all employees on that network routed to a private internal version of the app&lt;br&gt;
Everyone else → public version&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use for: routing corporate network users differently from public internet users, routing specific ISP blocks to specific endpoints, network-specific traffic management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Difference from Geolocation:&lt;/strong&gt; Geolocation infers location from IP geography. IP-Based routing uses exact CIDR ranges you define — much more precise for specific known IP ranges.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. Multivalue Answer Routing
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Returns up to 8 healthy records in response to a DNS query. Clients (browsers, load balancers) can pick any one and retry with another if it fails.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;tejascloud.com&lt;/code&gt; has 5 EC2 instances each with their own health check&lt;br&gt;
Route 53 returns up to 8 healthy IPs → client picks one → if it fails, client retries with another&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use for: basic DNS-level load distribution without a dedicated load balancer, improving availability by giving clients multiple options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not a replacement for ALB:&lt;/strong&gt; Multivalue answer is client-side load distribution via DNS. ALB provides sophisticated server-side load balancing with health checks, connection draining, SSL termination, and more. Multivalue is a lightweight fallback — not a production load balancing strategy on its own.&lt;/p&gt;




&lt;h2&gt;
  
  
  Route 53 Routing Policies — Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Policy&lt;/th&gt;
&lt;th&gt;Routes based on&lt;/th&gt;
&lt;th&gt;Health check support&lt;/th&gt;
&lt;th&gt;Use for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple&lt;/td&gt;
&lt;td&gt;Nothing — just returns the record&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;td&gt;Single resource, basic DNS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weighted&lt;/td&gt;
&lt;td&gt;Percentage split&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;A/B testing, canary deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Measured network latency&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Global apps, latency-sensitive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failover&lt;/td&gt;
&lt;td&gt;Primary/Secondary health&lt;/td&gt;
&lt;td&gt;Yes (required)&lt;/td&gt;
&lt;td&gt;DR, high availability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Geolocation&lt;/td&gt;
&lt;td&gt;User's country/continent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Localization, compliance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Geoproximity&lt;/td&gt;
&lt;td&gt;Geographic distance + bias&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Fine-grained geographic control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP-Based&lt;/td&gt;
&lt;td&gt;User's source IP CIDR&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Known IP range routing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multivalue&lt;/td&gt;
&lt;td&gt;None — returns multiple healthy IPs&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Basic DNS-level distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Route 53 Traffic Policies
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Traffic Policy&lt;/strong&gt; is an advanced Route 53 feature that lets you build complex routing configurations visually — combining multiple routing policies, health checks, and endpoints into one reusable policy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For example: Use Latency routing at the top level to send users to their nearest region, then within each region use Weighted routing to split between a canary (10%) and production (90%) endpoint. This nested, multi-level routing is what Traffic Policies enable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Traffic Policies are version-controlled — you can create a new version without affecting the live configuration, then apply it when ready. Useful for large organizations with complex, global routing needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Route 53 Resolvers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Route 53 Resolver&lt;/strong&gt; is the DNS resolver built into every AWS VPC. By default, it answers DNS queries from EC2 instances for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public domains (via the internet)&lt;/li&gt;
&lt;li&gt;Private Hosted Zones associated with the VPC&lt;/li&gt;
&lt;li&gt;AWS service endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Resolver is available at the VPC's base IP + 2 address (e.g., if your VPC CIDR is &lt;code&gt;10.200.0.0/16&lt;/code&gt;, the resolver is at &lt;code&gt;10.200.0.2&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The challenge in hybrid environments:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you have both an AWS VPC and an on-premises network connected via VPN or Direct Connect, two separate DNS worlds exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Route 53 knows about AWS resources (&lt;code&gt;payment.internal&lt;/code&gt;, RDS endpoints)&lt;/li&gt;
&lt;li&gt;On-premises DNS knows about on-premises resources (&lt;code&gt;server.company.local&lt;/code&gt;, &lt;code&gt;printer.office.local&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither side can resolve the other's names by default. &lt;strong&gt;Resolver Endpoints&lt;/strong&gt; bridge this gap.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inbound vs Outbound Resolver Endpoints
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Inbound Resolver Endpoint
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Allows DNS queries &lt;strong&gt;coming from outside AWS&lt;/strong&gt; (on-premises network) to enter the VPC and be resolved by Route 53.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;On-premises server needs to resolve &lt;code&gt;app.internal&lt;/code&gt; (an AWS Private Hosted Zone record) → On-premises DNS server forwards the query to the Inbound Endpoint IP → Inbound Endpoint accepts it → forwards to Route 53 Resolver → Route 53 checks Private Hosted Zone → returns &lt;code&gt;10.200.1.45&lt;/code&gt; → on-premises server connects to the private AWS resource&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Key point:&lt;/strong&gt; The Inbound Endpoint gives on-premises networks a target IP they can send DNS queries to — an ENI inside your VPC that acts as the entry door for external DNS queries.&lt;/p&gt;




&lt;h3&gt;
  
  
  Outbound Resolver Endpoint
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Allows DNS queries &lt;strong&gt;originating inside the VPC&lt;/strong&gt; to leave AWS and reach an on-premises DNS server.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;EC2 instance needs to resolve &lt;code&gt;server.company.local&lt;/code&gt; (managed by on-premises DNS) → EC2 queries Route 53 Resolver → Resolver checks Forwarding Rules → matches &lt;code&gt;*.company.local&lt;/code&gt; → Outbound Endpoint forwards the query to the on-premises DNS server IP → on-premises DNS returns the IP → Resolver returns it to EC2&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Key point:&lt;/strong&gt; The Outbound Endpoint is the exit door for DNS queries that need to leave AWS and reach on-premises DNS servers. It's always paired with Forwarding Rules that define which domain names should be forwarded (and to which on-premises DNS server IP).&lt;/p&gt;




&lt;h2&gt;
  
  
  Route 53 Forwarders
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Forwarding Rule&lt;/strong&gt; (Resolver Rule) is what tells Route 53 Resolver which DNS queries to forward through the Outbound Endpoint instead of resolving locally.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Forwarding Rule: &lt;code&gt;*.company.local → forward to 192.168.10.10 (on-premises DNS server IP) via the Outbound Endpoint&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Any VPC query for a &lt;code&gt;.company.local&lt;/code&gt; domain → Route 53 Resolver sees the rule → forwards to the on-premises DNS server through the Outbound Endpoint → gets the answer → returns to the EC2 instance&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Types of Resolver Rules:&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;Rule Type&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Forwarding Rule&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Forward matching DNS queries to a specified IP (usually on-premises DNS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System Rule&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-created by AWS — handles Private Hosted Zones and AWS-internal domains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Recursive Rule&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Default — queries not matching any rule are resolved normally via internet DNS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Sharing Resolver Rules:&lt;/strong&gt; Rules can be shared across multiple VPCs using AWS Resource Access Manager (RAM) — so a single set of forwarding rules can be applied to an entire organization's VPCs centrally.&lt;/p&gt;




&lt;h2&gt;
  
  
  Split-Horizon DNS
&lt;/h2&gt;

&lt;p&gt;One domain, two different DNS answers depending on who's asking — without the client ever knowing.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;app.company.com&lt;/code&gt; — the same domain name returns different answers based on where the DNS query originates.&lt;/p&gt;

&lt;p&gt;Query from inside the AWS VPC → Private Hosted Zone answers → returns &lt;code&gt;10.0.1.50&lt;/code&gt; (private IP, internal traffic stays inside the network)&lt;/p&gt;

&lt;p&gt;Query from the public internet → Public Hosted Zone answers → returns &lt;code&gt;54.20.30.40&lt;/code&gt; (public IP, routed through the internet)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;How it works in Route 53:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a &lt;strong&gt;Private Hosted Zone&lt;/strong&gt; for &lt;code&gt;app.company.com&lt;/code&gt; associated with your VPC → add an A record pointing to the private IP.&lt;br&gt;
Create a &lt;strong&gt;Public Hosted Zone&lt;/strong&gt; for &lt;code&gt;app.company.com&lt;/code&gt; → add an A record pointing to the public IP.&lt;/p&gt;

&lt;p&gt;When an EC2 instance inside the VPC queries &lt;code&gt;app.company.com&lt;/code&gt; → Route 53 Resolver checks the Private Hosted Zone first → returns the private IP.&lt;/p&gt;

&lt;p&gt;When an internet user queries &lt;code&gt;app.company.com&lt;/code&gt; → hits the public DNS infrastructure → Public Hosted Zone answers → returns the public IP.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why Private Hosted Zone wins for VPC queries:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Private Hosted Zone always overrides the Public Hosted Zone for queries originating inside the associated VPC. This is by design — internal traffic should stay internal, even if a public record also exists for the same domain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why this pattern is valuable:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal users access the application over the private network — lower latency, no egress costs, more secure&lt;/li&gt;
&lt;li&gt;External users still reach the same application via its public endpoint&lt;/li&gt;
&lt;li&gt;The application doesn't need to change — the DNS layer handles the routing transparently&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview definition:&lt;/strong&gt; Split-Horizon DNS = same domain name + different DNS answer based on where the query originates. Public zone for internet users, Private zone for VPC users, private zone always wins for internal queries.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DNS Resolution Flow&lt;/strong&gt;&lt;br&gt;
Browser Cache → OS Cache → Recursive Resolver → Root DNS → TLD DNS → Authoritative DNS (Route 53) → IP → Browser connects&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Route 53 Basics&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authoritative DNS service for AWS-managed domains&lt;/li&gt;
&lt;li&gt;Port 53 = standard DNS port&lt;/li&gt;
&lt;li&gt;Does: Domain Registration + DNS Routing + Health Checking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hosted Zones&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public Hosted Zone: internet-facing DNS, answers from anywhere&lt;/li&gt;
&lt;li&gt;Private Hosted Zone: VPC-only DNS, invisible to internet, overrides public for VPC queries&lt;/li&gt;
&lt;li&gt;One Hosted Zone per domain, contains all DNS records for that domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Record Types&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A → IPv4, AAAA → IPv6, CNAME → another domain (not root), MX → mail, TXT → verification/email security, NS → name servers (auto-created, never delete), SOA → zone metadata (auto-created, never delete), PTR → reverse DNS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Alias Record&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS-specific, works on root domain (CNAME can't), points to ALB/CloudFront/S3/API GW, free DNS queries for AWS resource targets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Routing Policies&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;Policy&lt;/th&gt;
&lt;th&gt;Routes based on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple&lt;/td&gt;
&lt;td&gt;Nothing — just returns record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weighted&lt;/td&gt;
&lt;td&gt;Percentage split&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Measured network latency (not distance)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failover&lt;/td&gt;
&lt;td&gt;Primary/Secondary health status&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Geolocation&lt;/td&gt;
&lt;td&gt;User's country/continent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Geoproximity&lt;/td&gt;
&lt;td&gt;Distance + adjustable bias&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP-Based&lt;/td&gt;
&lt;td&gt;Source IP CIDR range&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multivalue&lt;/td&gt;
&lt;td&gt;Returns multiple healthy IPs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Resolvers&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inbound Endpoint: on-premises → AWS (resolve AWS Private Hosted Zone records from outside)&lt;/li&gt;
&lt;li&gt;Outbound Endpoint: AWS → on-premises (resolve on-premises domain names from VPC)&lt;/li&gt;
&lt;li&gt;Forwarding Rules: define which domains get forwarded through Outbound Endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Split-Horizon DNS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same domain, different answer based on query origin&lt;/li&gt;
&lt;li&gt;Private Hosted Zone (VPC queries) → private IP&lt;/li&gt;
&lt;li&gt;Public Hosted Zone (internet queries) → public IP&lt;/li&gt;
&lt;li&gt;Private always wins for VPC-originated queries&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: Walk through the complete DNS resolution flow.&lt;/strong&gt;&lt;br&gt;
User types domain in browser → browser cache miss → OS cache miss → Recursive Resolver checked → if cache miss, Resolver queries Root DNS Server ("who manages .com?") → Root returns TLD server address → Recursive Resolver queries TLD server (.com) → TLD returns Route 53 name servers from the NS record → Resolver queries Route 53 (authoritative) → Route 53 checks the Hosted Zone → returns the A record (IP) → Resolver caches the result for the TTL duration → browser receives IP → TCP/TLS handshake → HTTP request → website loads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What is the difference between a Public and Private Hosted Zone?&lt;/strong&gt;&lt;br&gt;
A Public Hosted Zone answers DNS queries from the public internet — it's how your domain is reachable by anyone. A Private Hosted Zone answers queries only from within associated AWS VPCs — it's invisible to the internet and used for internal service discovery. When a query originates from inside the VPC, the Private Hosted Zone always overrides the Public one for the same domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: Why can't you use a CNAME for a root domain and what do you use instead?&lt;/strong&gt;&lt;br&gt;
CNAME is a DNS standard limitation — it cannot be placed on the apex (root) domain like &lt;code&gt;example.com&lt;/code&gt;, only on subdomains like &lt;code&gt;www.example.com&lt;/code&gt;. For pointing a root domain to an AWS resource (ALB, CloudFront, S3), Route 53 provides Alias records — an AWS-specific extension that works on root domains, supports health checks, and doesn't charge for DNS queries to AWS targets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: What is Split-Horizon DNS and how is it implemented in Route 53?&lt;/strong&gt;&lt;br&gt;
Split-Horizon DNS returns different DNS answers for the same domain depending on where the query originates. In Route 53, you create a Private Hosted Zone associated with your VPC (returns private IP for internal users) and a Public Hosted Zone (returns public IP for internet users) for the same domain. VPC queries always resolve via the Private Hosted Zone — the Private Hosted Zone overrides the Public for any query originating inside the associated VPC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: What is the difference between Route 53 Inbound and Outbound Resolver Endpoints?&lt;/strong&gt;&lt;br&gt;
An Inbound Endpoint allows DNS queries coming from outside AWS (typically an on-premises network) to enter the VPC and be resolved by Route 53 — so on-premises servers can resolve Private Hosted Zone names. An Outbound Endpoint allows DNS queries originating inside the VPC to leave AWS and reach an on-premises DNS server — so EC2 instances can resolve on-premises domain names. Outbound Endpoints work with Forwarding Rules that specify which domain patterns to forward and to which DNS server IP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: What is the difference between Geolocation and Latency-Based routing?&lt;/strong&gt;&lt;br&gt;
Geolocation routing directs traffic based on the user's geographic location (country or continent) — a UK user always goes to the EU endpoint regardless of which is actually faster. Latency-based routing directs traffic based on measured network latency — the user goes to whichever AWS Region responds fastest for them. A UK user might have lower latency to US East than EU West depending on network conditions — Latency routing would send them there; Geolocation would not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: What is Failover routing and when would you use it?&lt;/strong&gt;&lt;br&gt;
Failover routing designates one record as Primary and another as Secondary. Route 53 monitors the Primary endpoint via health checks. As long as the primary is healthy, all traffic goes there. If the health check fails, Route 53 automatically returns the Secondary record. Used for disaster recovery setups — for example, Primary is a production ALB, Secondary is an S3 static page showing a maintenance message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q8: What is the difference between Weighted and Multivalue routing?&lt;/strong&gt;&lt;br&gt;
Weighted routing sends specific percentages of traffic to specific endpoints — controlled, intentional splits like 90% to production and 10% to canary. Multivalue routing returns up to 8 healthy IP addresses in response to a query, and the client picks one — it's DNS-level distribution with health filtering, not traffic splitting by percentage. Weighted is for deliberate traffic control; Multivalue is for basic resilience by giving clients multiple options.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔬 Practice Tasks
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hosted Zone lab:&lt;/strong&gt; Register or transfer a domain to Route 53 (or use an existing one). Create a Public Hosted Zone. Add an A record pointing to an EC2 public IP. Verify DNS resolution works from your browser. Change the IP and observe how long TTL takes to propagate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Private Hosted Zone:&lt;/strong&gt; Create a Private Hosted Zone for &lt;code&gt;internal.local&lt;/code&gt; associated with your VPC. Add an A record &lt;code&gt;payment.internal.local → 10.200.1.45&lt;/code&gt;. SSH into an EC2 in the VPC and run &lt;code&gt;nslookup payment.internal.local&lt;/code&gt; — verify it resolves to the private IP.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Split-Horizon:&lt;/strong&gt; Create both a Public and Private Hosted Zone for the same domain. Add different A records in each. From within the VPC, verify the Private Hosted Zone answer. From your home internet, verify the Public Hosted Zone answer. Confirm they're different.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Weighted routing for canary deployment:&lt;/strong&gt; Create two A records for the same domain with weights 90 and 10 pointing to different resources. Query the domain repeatedly and track which endpoint is returned — verify approximately 10% of responses point to the new resource.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Failover routing:&lt;/strong&gt; Create a Primary record pointing to a running EC2 with a health check. Create a Secondary record pointing to a different endpoint. Manually stop the primary EC2. Watch the health check fail in Route 53. Verify that queries now return the Secondary record.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resolver Endpoints (conceptual exercise):&lt;/strong&gt; Draw the complete flow for: (a) an on-premises server resolving &lt;code&gt;app.internal&lt;/code&gt; (an AWS Private Hosted Zone), and (b) an EC2 instance resolving &lt;code&gt;server.company.local&lt;/code&gt; (an on-premises domain). For each, identify which endpoint (Inbound or Outbound) is used and in which direction the DNS query travels.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;AWS Session 15 — Amazon Route 53 | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AWS SQS &amp; SNS — Decoupling Services with Queues, Topics &amp; Fan-Out Patterns</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Sat, 08 Aug 2026 01:37:58 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-sqs-sns-decoupling-services-with-queues-topics-fan-out-patterns-bdk</link>
      <guid>https://dev.to/tejas_shinkar/aws-sqs-sns-decoupling-services-with-queues-topics-fan-out-patterns-bdk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This session covers how distributed systems communicate reliably — the backbone of every production-grade AWS architecture.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Monolithic vs Microservices Architecture&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Why Messaging Services Exist&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Amazon SQS — What It Is&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;SQS — Pull-Based vs Push-Based&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;SQS Configuration Settings&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Visibility Timeout — Deep Dive&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;SQS Message Lifecycle — With and Without Failure&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Key SQS APIs&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Dead Letter Queue (DLQ)&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;SQS Standard vs FIFO Queue&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;SQS FIFO Deduplication&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Two Core SQS Integration Patterns&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Amazon SNS — What It Is&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;SNS vs SQS — Push vs Pull&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;SNS Security and Message Filtering&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;SNS + SQS Fan-Out Pattern&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Amazon SNS FIFO&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;SNS FIFO + SQS FIFO Fan-Out&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;Production Architecture View&lt;/td&gt;
&lt;td&gt;DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Lab — Deploy SNS and SQS Queues&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;Interview Questions&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;Assignment&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Monolithic vs Microservices Architecture
&lt;/h2&gt;

&lt;p&gt;Before queues and topics make sense, you need to understand the problem they solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monolithic Architecture
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The entire application — UI, business logic, database layer, authentication, notifications, payment — is packaged and deployed as a single unit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Early advantages:&lt;/strong&gt; simple to develop, test, and deploy — everything is in one place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it breaks down at scale:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bug in the notification module can crash the entire application — including payments&lt;/li&gt;
&lt;li&gt;Scaling one feature (payments under high load) requires scaling the entire application&lt;/li&gt;
&lt;li&gt;Deploying any small change means redeploying everything — risk and coordination overhead&lt;/li&gt;
&lt;li&gt;The codebase grows into something nobody fully understands anymore&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Microservices Architecture
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The application is divided into small, independently deployable services, each responsible for exactly one business capability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What this looks like:&lt;/strong&gt; Order Service → Payment Service → Notification Service → Inventory Service → User Service — each deployed separately, scaled independently, owned by its own team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt; fault isolation (Notifications failing doesn't affect Payments), independent scaling, each service uses the best technology for its job, teams deploy without coordinating with each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The challenge microservices introduce:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If Payment needs to tell Notification "payment completed, send email" — what happens if Notification is temporarily down? Does Payment fail too? This tight synchronous coupling defeats the purpose of separation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is exactly the problem SQS and SNS solve.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview tip:&lt;/strong&gt; When asked "why microservices?" — cover independent deployment, fault isolation, team autonomy, and technology flexibility. When asked "what challenges come with microservices?" — inter-service communication, distributed tracing, and data consistency are the honest answers.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Messaging Services Exist
&lt;/h2&gt;

&lt;p&gt;In a direct synchronous call: Order Service → HTTP → Notification Service → if Notification is down, Order fails too. Tight coupling.&lt;/p&gt;

&lt;p&gt;With a queue: Order Service → puts message in queue → returns success immediately → Notification reads from queue when ready.&lt;/p&gt;

&lt;p&gt;The queue acts as a &lt;strong&gt;buffer and decoupler&lt;/strong&gt; — the producer doesn't care when the consumer processes, just that it eventually does. Traffic spikes are absorbed by the queue instead of crashing downstream services.&lt;/p&gt;




&lt;h2&gt;
  
  
  Amazon SQS — What It Is
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Amazon SQS (Simple Queue Service)&lt;/strong&gt; is a fully managed, distributed message queue that temporarily stores messages until a consumer is ready to process them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bank token system analogy:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You walk into a bank. You don't go directly to the cashier — you take a numbered token. The token machine doesn't solve your problem. It organizes the waiting line so cashiers can handle customers at a manageable pace. Customers (producers) keep arriving. Cashiers (consumers) process at their own pace. SQS is the token machine.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;Fully managed — no servers, clusters, or infrastructure to provision&lt;/li&gt;
&lt;li&gt;Automatically distributed across multiple backend partitions&lt;/li&gt;
&lt;li&gt;Replicated across multiple AZs — messages are durable even if one AZ fails&lt;/li&gt;
&lt;li&gt;Scales to billions of messages automatically&lt;/li&gt;
&lt;li&gt;Once a message is consumed and deleted, it's gone&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SQS — Pull-Based vs Push-Based
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SQS is pull-based.&lt;/strong&gt; Consumers actively poll the queue using &lt;code&gt;ReceiveMessage()&lt;/code&gt; when they're ready. SQS never pushes messages.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Think of it like checking your email inbox — you open it when you're ready. Your inbox doesn't force your browser open when mail arrives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why pull-based is powerful:&lt;/strong&gt; consumers control their own processing rate, each consumer scales independently based on queue depth, if a consumer goes down messages safely queue up until it recovers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long Polling vs Short Polling:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Short Polling (Wait Time = 0, default):&lt;/strong&gt; Asks "any messages?" → SQS responds immediately, even if empty → you pay for the API call even when nothing was there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long Polling (Wait Time up to 20s):&lt;/strong&gt; Asks "any messages?" → SQS waits up to 20 seconds → if a message arrives, it returns immediately → if nothing arrives, it returns at timeout. Fewer API calls, lower cost, faster response when messages exist.&lt;/p&gt;

&lt;p&gt;🎯 &lt;strong&gt;Production tip:&lt;/strong&gt; Always use Long Polling (20 seconds). It reduces empty responses, lowers costs, and responds faster when messages actually arrive.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  SQS Configuration Settings
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;One-Line Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Visibility Timeout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hides a message while one consumer is processing it — prevents double-processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Message Retention Period&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How long an unprocessed message stays before being auto-deleted (1 min – 14 days, default 4 days)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delivery Delay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How long after sending before consumers can see the message (0 – 15 minutes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maximum Message Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Max size per message — 256 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Receive Message Wait Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How long ReceiveMessage waits when queue is empty — enables Long Polling (0 – 20 seconds)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Visibility Timeout — Deep Dive
&lt;/h2&gt;

&lt;p&gt;The mechanism that prevents two consumers from processing the same message simultaneously.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Consumer A calls &lt;code&gt;ReceiveMessage()&lt;/code&gt; → SQS returns Message X → SQS hides Message X from all other consumers for the Visibility Timeout duration → Consumer A processes → Consumer A calls &lt;code&gt;DeleteMessage()&lt;/code&gt; → Message permanently gone.&lt;/p&gt;

&lt;p&gt;If Consumer A crashes or takes too long → Visibility Timeout expires → Message reappears → Consumer B picks it up and retries.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The ChangeMessageVisibility pattern for long-running jobs:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Consumer picks the message → starts processing → periodically calls &lt;code&gt;ChangeMessageVisibility()&lt;/code&gt; to extend the timeout before it expires → processing completes → &lt;code&gt;DeleteMessage()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;🎯 &lt;strong&gt;Interview tip:&lt;/strong&gt; It's the consumer itself (Lambda/EC2/ECS) that calls &lt;code&gt;ChangeMessageVisibility&lt;/code&gt; — not a separate monitoring process. The consumer extends its own timeout while processing, then deletes the message on success.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Setting the right Visibility Timeout:&lt;/strong&gt; If your job typically takes 45 seconds, set the timeout to at least 90 seconds — a 2× buffer accounts for occasional slowness without triggering false retries.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQS Message Lifecycle — With and Without Failure
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Without failure (happy path):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Customer → Producer → &lt;code&gt;SendMessage()&lt;/code&gt; → SQS Queue → Consumer calls &lt;code&gt;ReceiveMessage()&lt;/code&gt; → Visibility Timeout starts → Consumer processes (DB write, email, payment) → Consumer calls &lt;code&gt;DeleteMessage()&lt;/code&gt; → Done ✅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;With failure (retry path):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Customer → Producer → SQS → Consumer picks message → Processing fails (crash, exception) → Visibility Timeout expires → Message reappears → Another consumer retries → Success → &lt;code&gt;DeleteMessage()&lt;/code&gt; ✅&lt;/p&gt;

&lt;p&gt;If it keeps failing after maxReceiveCount attempts → Message moves to the Dead Letter Queue&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Key SQS APIs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SendMessage()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Put a message into the queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ReceiveMessage()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read messages from the queue (up to 10 at a time)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ChangeMessageVisibility()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extend the Visibility Timeout for a message being processed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DeleteMessage()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove a successfully processed message permanently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PurgeQueue()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete all messages in the queue at once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DeleteQueue()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove the entire queue&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Dead Letter Queue (DLQ)
&lt;/h2&gt;

&lt;p&gt;Some messages genuinely cannot be processed — corrupt data, an unrecoverable bug, an unexpected format. Without a DLQ, these messages retry forever, consuming consumer capacity and blocking healthy messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Dead Letter Queue&lt;/strong&gt; is a separate SQS queue that receives messages which have failed after exceeding the configured &lt;strong&gt;maxReceiveCount&lt;/strong&gt; (maximum retry attempts).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Also called the &lt;strong&gt;poisoned pill defense&lt;/strong&gt; — a message that would kill your consumer over and over gets quarantined instead of contaminating normal flow.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Producer → SQS Queue → Consumer → Failure → Retry → Retry → Retry (maxReceiveCount reached) → Message moved to DLQ → Normal queue continues processing healthy messages&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What to do with DLQ messages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspect the failed message — understand what was in it&lt;/li&gt;
&lt;li&gt;Find the root cause — bug in consumer? Bad data from producer?&lt;/li&gt;
&lt;li&gt;Fix the application or the message data&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Redrive&lt;/strong&gt; to replay the message back to the source queue after the fix is deployed&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview Q: Does DLQ automatically fix failed messages?&lt;/strong&gt; → No. DLQ only stores them safely. A human (or automation) must investigate, fix the root cause, and replay the message.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Production DLQ best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always configure a DLQ on every production queue — without one, failed messages silently disappear after retention expires&lt;/li&gt;
&lt;li&gt;Set DLQ retention to &lt;strong&gt;14 days&lt;/strong&gt; (maximum) — maximum time to investigate&lt;/li&gt;
&lt;li&gt;Set CloudWatch alarm on DLQ's &lt;code&gt;ApproximateNumberOfMessagesVisible&lt;/code&gt; metric — get paged the moment any message lands there&lt;/li&gt;
&lt;li&gt;After fixing root cause, use &lt;strong&gt;Redrive&lt;/strong&gt; to replay — don't manually reprocess&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;maxReceiveCount:&lt;/strong&gt; Typical production value is 3–5 retries. Too low = transient errors moved to DLQ prematurely. Too high = a bad message wastes consumer resources for too long.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQS Standard vs FIFO Queue
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Standard Queue&lt;/th&gt;
&lt;th&gt;FIFO Queue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ordering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Best-effort (may arrive out of order)&lt;/td&gt;
&lt;td&gt;Strict first-in, first-out — guaranteed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delivery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;At-least-once (duplicates possible)&lt;/td&gt;
&lt;td&gt;Exactly-once (within 5-minute deduplication window)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nearly unlimited&lt;/td&gt;
&lt;td&gt;300 messages/second (3,000 with batching)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deduplication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Queue name&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Any name&lt;/td&gt;
&lt;td&gt;Must end in &lt;code&gt;.fifo&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Notifications, logs, parallel jobs where order doesn't matter&lt;/td&gt;
&lt;td&gt;Financial transactions, order processing, inventory — where order and exactly-once matter&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;How to choose:&lt;/strong&gt; Is message order critical to correctness? → FIFO. Can you afford occasional duplicates? → Standard. Need maximum throughput? → Standard.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQS FIFO Deduplication
&lt;/h2&gt;

&lt;p&gt;FIFO queues guarantee exactly-once processing within a 5-minute deduplication window. If the same message is sent twice within 5 minutes, the duplicate is silently discarded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two deduplication methods:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Content-Based Deduplication:&lt;/strong&gt; SQS generates a SHA-256 hash of the message body. Identical hash within 5 minutes = duplicate, dropped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Message Deduplication ID:&lt;/strong&gt; The producer explicitly sends a unique ID with each message. Same ID within 5 minutes = duplicate, dropped. Gives producers explicit control over deduplication.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Message Group ID:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Within a FIFO queue, messages with the same Group ID are processed in strict order. Messages with different Group IDs can be processed in parallel.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Example: order processing queue where Group ID = order_id. All events for order 1001 (created → paid → shipped → delivered) are in strict order. Events for order 1002 process in parallel with order 1001's events — FIFO within groups, parallelism across groups.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Two Core SQS Integration Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern 1 — Producer-Consumer Decoupling
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Order Service sends a message to SQS when an order is placed → returns success to user immediately → Fulfillment Service reads from the queue at its own pace → processes independently.&lt;/p&gt;

&lt;p&gt;If Fulfillment is slow or down, orders safely queue up — no data lost, no error shown to the user. When Fulfillment recovers, it processes the backlog.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The core value of SQS:&lt;/strong&gt; services don't need to know about each other, don't need to be available at the same time, and traffic spikes are absorbed by the queue instead of crashing downstream services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 2 — Workflow Orchestration
&lt;/h3&gt;

&lt;p&gt;For multi-step processes (Order → Payment → Warehouse → Shipping → Notification), SQS handles message passing between steps while &lt;strong&gt;AWS Step Functions&lt;/strong&gt; orchestrates the overall workflow — managing state, retries, and branching logic.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQS: the messaging between steps. Step Functions: the sequencing, state tracking, and failure handling at the workflow level.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Amazon SNS — What It Is
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Amazon SNS (Simple Notification Service)&lt;/strong&gt; is a fully managed &lt;strong&gt;push-based Publish/Subscribe&lt;/strong&gt; messaging service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pub/sub model:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Publishers send a message to an &lt;strong&gt;SNS Topic&lt;/strong&gt; — not to individual consumers. SNS immediately fans out a copy to every subscriber of that topic simultaneously. Each subscription receives its own independent copy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;SNS is not a queue.&lt;/strong&gt; It doesn't store messages long-term — once published, SNS immediately delivers to subscribers and moves on. SNS is a broadcasting megaphone, not a holding room.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supported subscriber types:&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;Subscriber&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SQS queue&lt;/td&gt;
&lt;td&gt;Message dropped into the queue for async processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda function&lt;/td&gt;
&lt;td&gt;Function invoked immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;Message sent to the address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMS&lt;/td&gt;
&lt;td&gt;Text message sent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP/HTTPS endpoint&lt;/td&gt;
&lt;td&gt;POST request sent to the URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile push (APNs, GCM)&lt;/td&gt;
&lt;td&gt;Push notification to iOS/Android&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  SNS vs SQS — Push vs Pull
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;SQS&lt;/th&gt;
&lt;th&gt;SNS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Pull-based — consumers poll&lt;/td&gt;
&lt;td&gt;Push-based — SNS delivers immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;Yes — stores until consumed (up to 14 days)&lt;/td&gt;
&lt;td&gt;No — delivers and moves on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consumers&lt;/td&gt;
&lt;td&gt;One message → typically one consumer&lt;/td&gt;
&lt;td&gt;One message → all subscribers simultaneously&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use for&lt;/td&gt;
&lt;td&gt;Decoupling, buffering, async work queues&lt;/td&gt;
&lt;td&gt;Broadcasting, fan-out, real-time notifications&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They're complementary, not competing. SQS for reliable async processing; SNS for immediate fan-out broadcasting.&lt;/p&gt;




&lt;h2&gt;
  
  
  SNS Security and Message Filtering
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Encryption in transit:&lt;/strong&gt; HTTPS by default on all SNS API calls and deliveries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encryption at rest:&lt;/strong&gt; SSE via AWS KMS — encrypts message content before delivery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access control:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SNS Topic Policy:&lt;/strong&gt; Resource-based policy on the topic — defines who can publish and subscribe. Used for cross-account access (similar to S3 bucket policies).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Policy:&lt;/strong&gt; Controls which IAM users/roles can call SNS APIs (&lt;code&gt;sns:Publish&lt;/code&gt;, &lt;code&gt;sns:Subscribe&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Message Filtering
&lt;/h3&gt;

&lt;p&gt;By default, every subscriber receives every message. &lt;strong&gt;Filter policies&lt;/strong&gt; let each subscription define which messages it actually receives, based on message attributes.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;An Order topic receives all order events. Fulfillment only cares about &lt;code&gt;status: "PLACED"&lt;/code&gt;. Analytics wants everything. Fraud only cares about &lt;code&gt;amount &amp;gt; 10000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Without filtering: every service processes every event and discards irrelevant ones — wasteful.&lt;br&gt;
With filtering: each subscription only receives matching messages — SNS filters before delivery.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Fulfillment filter policy:&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;"status"&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;"PLACED"&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;SNS evaluates this filter before delivery. Messages where &lt;code&gt;status&lt;/code&gt; is not &lt;code&gt;"PLACED"&lt;/code&gt; are not delivered to the Fulfillment subscription at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  SNS + SQS Fan-Out Pattern
&lt;/h2&gt;

&lt;p&gt;One of the most important architectural patterns in AWS — appears constantly in interviews and certifications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; When an order is placed, Fulfillment, Inventory, Notifications, and Analytics all need to react. If you call each directly, you're back to tight coupling — one slow service makes the entire order placement slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fan-out solution:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Producer publishes one message to an SNS Topic → SNS immediately delivers to all subscribed SQS queues simultaneously → each service has its own queue → each service processes at its own pace, independently.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;New Order Event → SNS Topic (order-events)&lt;br&gt;
      → SQS Queue → Fulfillment Service (has its own DLQ, scaling, retry logic)&lt;br&gt;
      → SQS Queue → Inventory Service (has its own DLQ, scaling, retry logic)&lt;br&gt;
      → SQS Queue → Notification Service (has its own DLQ)&lt;br&gt;
      → SQS Queue → Analytics Service (has its own DLQ)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why this pattern is powerful:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publish once, SNS handles delivery to all consumers&lt;/li&gt;
&lt;li&gt;Each consumer is fully isolated — if Analytics is slow, Fulfillment is unaffected&lt;/li&gt;
&lt;li&gt;Adding a new consumer = add a new SQS subscription to the topic, no producer changes needed&lt;/li&gt;
&lt;li&gt;Each queue has independent DLQ, retry logic, and scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview Q:&lt;/strong&gt; "How would you design a system where one event triggers multiple independent services?" → SNS + SQS fan-out. One SNS topic, one SQS queue per service, each with its own consumer and DLQ.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Amazon SNS FIFO
&lt;/h2&gt;

&lt;p&gt;For cases where message order matters at the broadcasting level.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Standard SNS&lt;/th&gt;
&lt;th&gt;SNS FIFO&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ordering&lt;/td&gt;
&lt;td&gt;Best-effort&lt;/td&gt;
&lt;td&gt;Strict ordering within a Message Group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deduplication&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — same mechanism as SQS FIFO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;td&gt;Up to 300 published messages/second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subscribers&lt;/td&gt;
&lt;td&gt;Any type&lt;/td&gt;
&lt;td&gt;Only SQS FIFO queues&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key constraint:&lt;/strong&gt; SNS FIFO can only fan out to SQS FIFO queues. If you need FIFO ordering, the entire delivery chain must be FIFO.&lt;/p&gt;




&lt;h2&gt;
  
  
  SNS FIFO + SQS FIFO Fan-Out
&lt;/h2&gt;

&lt;p&gt;The most controlled and ordered fan-out pattern:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Publisher → SNS FIFO Topic → SQS FIFO Queue A (Fulfillment) → strict order guaranteed&lt;br&gt;
                           → SQS FIFO Queue B (Inventory) → strict order guaranteed&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;When to use this vs standard fan-out:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If order of events matters for correctness downstream — inventory updates where "add 10 units" then "remove 15 units" must process in exact order — use SNS FIFO + SQS FIFO.&lt;/p&gt;

&lt;p&gt;If order doesn't matter (analytics, notifications), use standard SNS + SQS — higher throughput, simpler, cheaper.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Production Architecture View
&lt;/h2&gt;

&lt;p&gt;This is what a real event-driven system looks like combining everything from this session:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;User places order → Order Service → publishes to SNS Topic&lt;/p&gt;

&lt;p&gt;SNS fans out simultaneously to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQS Queue (Fulfillment) → consumer: ECS service, has DLQ monitored by CloudWatch&lt;/li&gt;
&lt;li&gt;SQS Queue (Inventory) → consumer: Lambda, has DLQ&lt;/li&gt;
&lt;li&gt;SQS FIFO Queue (Payment Audit) → strict order for audit trail&lt;/li&gt;
&lt;li&gt;Email subscription → sends order confirmation directly to customer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each SQS queue uses Long Polling (20s), Visibility Timeout set to 2× expected processing time, DLQ with 14-day retention, CloudWatch alarm on DLQ depth, Redrive configured for replay after fixes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧪 Lab — Deploy SNS and SQS Queues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 — Create SQS Queue + DLQ
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;SQS Console → Create queue → Type: Standard → Name: &lt;code&gt;order-processing-queue&lt;/code&gt; → Visibility Timeout: 30s → Message Retention: 4 days → Receive Message Wait Time: 20s → Create queue.&lt;/p&gt;

&lt;p&gt;Create second queue: Name: &lt;code&gt;order-processing-dlq&lt;/code&gt; → Create queue.&lt;/p&gt;

&lt;p&gt;Go back to &lt;code&gt;order-processing-queue&lt;/code&gt; → Edit → Dead-letter queue → Enable → Select &lt;code&gt;order-processing-dlq&lt;/code&gt; → maxReceiveCount: 3 → Save.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 2 — Create SNS Topic
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;SNS Console → Topics → Create topic → Type: Standard → Name: &lt;code&gt;order-events&lt;/code&gt; → Create topic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 3 — Subscribe SQS to SNS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;SNS topic page → Create subscription → Protocol: SQS → Endpoint: ARN of &lt;code&gt;order-processing-queue&lt;/code&gt; → Create subscription.&lt;/p&gt;

&lt;p&gt;Then: SQS queue → Access Policy → add the SNS topic ARN as a principal with &lt;code&gt;sqs:SendMessage&lt;/code&gt; permission on this queue (allows SNS to write to SQS).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 4 — Test Fan-Out
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;SNS topic → Publish message → enter test payload → Publish.&lt;br&gt;
Go to &lt;code&gt;order-processing-queue&lt;/code&gt; → Send and receive messages → Poll for messages → see the message SNS delivered ✅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 5 — Test DLQ
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Receive a message from the queue 3 times without deleting it (simulating failure) → Check &lt;code&gt;order-processing-dlq&lt;/code&gt; → message should have moved there ✅&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Monolithic vs Microservices&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monolithic: single unit, simple initially, hard to scale and maintain&lt;/li&gt;
&lt;li&gt;Microservices: per-capability services, independent deployment, fault isolation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SQS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pull-based distributed queue — consumers poll, SQS never pushes&lt;/li&gt;
&lt;li&gt;At-least-once delivery, best-effort ordering (Standard) vs exactly-once, strict FIFO (FIFO queue)&lt;/li&gt;
&lt;li&gt;Standard: unlimited throughput. FIFO: 300/sec (3000 with batching), name ends in &lt;code&gt;.fifo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Long Polling (20s): always use in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SQS Key Config&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visibility Timeout: hides message during processing, prevents double-processing&lt;/li&gt;
&lt;li&gt;DLQ: receives messages after maxReceiveCount failures, does NOT auto-fix, set 14-day retention, monitor with CloudWatch alarm&lt;/li&gt;
&lt;li&gt;ChangeMessageVisibility: consumer extends its own timeout for long jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SQS API Flow&lt;/strong&gt;&lt;br&gt;
SendMessage → ReceiveMessage → ChangeMessageVisibility (if needed) → DeleteMessage (on success)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SNS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Push-based Pub/Sub — one publish → immediate delivery to all subscribers simultaneously&lt;/li&gt;
&lt;li&gt;Not a queue — no long-term storage&lt;/li&gt;
&lt;li&gt;Subscribers: SQS, Lambda, Email, SMS, HTTP, Mobile Push&lt;/li&gt;
&lt;li&gt;Message Filtering: per-subscription JSON filter policy on message attributes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fan-Out (SNS + SQS)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One SNS topic → multiple SQS queues → each consumer independent&lt;/li&gt;
&lt;li&gt;Best pattern for: one event → multiple independent services&lt;/li&gt;
&lt;li&gt;Add a new consumer: just add a new SQS subscription — no producer changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;FIFO&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SNS FIFO: strict ordering, deduplication, only delivers to SQS FIFO queues&lt;/li&gt;
&lt;li&gt;Use SNS FIFO + SQS FIFO when order correctness matters end-to-end&lt;/li&gt;
&lt;li&gt;Use Standard SNS + SQS when order doesn't matter — higher throughput, cheaper&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: What is the difference between SQS and SNS?&lt;/strong&gt;&lt;br&gt;
SQS is pull-based — consumers poll for messages, messages are stored until consumed (up to 14 days), and typically one consumer processes each message. SNS is push-based pub/sub — publishers send to a topic, SNS immediately pushes to all subscribers simultaneously, and messages aren't stored long-term. They're complementary: SNS for fan-out broadcasting, SQS for reliable async processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What is the Visibility Timeout in SQS and why is it important?&lt;/strong&gt;&lt;br&gt;
When a consumer reads a message, it becomes invisible to all other consumers for the Visibility Timeout duration. This prevents double-processing — only one consumer handles the message at a time. If the consumer succeeds and deletes the message, it's gone. If the consumer fails, the timeout expires, the message reappears, and another consumer can retry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: What is a Dead Letter Queue and when would a message end up there?&lt;/strong&gt;&lt;br&gt;
A DLQ is a separate SQS queue that receives messages which have failed processing after exceeding maxReceiveCount. A message lands there when a consumer repeatedly can't process it — due to a bug, corrupt data, or an unrecoverable error. The DLQ doesn't fix anything — it safely stores failed messages for investigation, root cause analysis, and optional replay via Redrive after the issue is fixed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: What is the difference between SQS Standard and SQS FIFO?&lt;/strong&gt;&lt;br&gt;
Standard offers at-least-once delivery (duplicates possible) and best-effort ordering with nearly unlimited throughput. FIFO guarantees exactly-once delivery (5-minute deduplication window) and strict ordering, but is limited to 300 messages/second (3,000 with batching). Use Standard when order and deduplication don't matter. Use FIFO for financial transactions, order processing, inventory — where sequence is critical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: Explain the SNS + SQS Fan-Out pattern and why it's useful.&lt;/strong&gt;&lt;br&gt;
A producer publishes one message to an SNS topic. SNS immediately delivers a copy to all subscribed SQS queues simultaneously. Each downstream service has its own queue, consumer, DLQ, and retry logic — completely independent. This means one event triggers multiple services without tight coupling. Adding a new consumer is just adding a new SQS subscription — no changes to the producer. Failures in one consumer don't affect others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: Your SQS consumer takes 10 minutes to process a message but the Visibility Timeout is 30 seconds. What happens and how do you fix it?&lt;/strong&gt;&lt;br&gt;
After 30 seconds, the timeout expires and the message reappears. Another consumer picks it up — now two consumers process the same message, risking data corruption or duplicate operations. Fix: the consumer should periodically call &lt;code&gt;ChangeMessageVisibility()&lt;/code&gt; to extend the timeout before it expires. Alternatively, set Visibility Timeout to at least 2× the maximum expected processing time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: How does message filtering work in SNS?&lt;/strong&gt;&lt;br&gt;
Each subscription defines a filter policy — a JSON document specifying which message attribute values the subscription cares about. SNS evaluates the filter before delivery. If the message attributes don't match, SNS doesn't deliver that message to that subscription. This prevents every consumer from receiving and discarding irrelevant messages, reducing processing cost and simplifying consumer logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q8: What is the difference between SNS FIFO and Standard SNS?&lt;/strong&gt;&lt;br&gt;
Standard SNS delivers with best-effort ordering at very high throughput and supports all subscriber types (SQS, Lambda, Email, SMS, HTTP). SNS FIFO guarantees strict ordering and deduplication within a Message Group, but is limited to 300 messages/second and can only deliver to SQS FIFO queues. Use SNS FIFO when the order of events matters for correctness downstream and the entire delivery chain must be ordered.&lt;/p&gt;




&lt;h2&gt;
  
  
  📝 Assignment
&lt;/h2&gt;

&lt;p&gt;Create a CloudWatch alarm (on any metric — EC2 CPU, SQS queue depth, or a custom metric) and configure it to send notifications to a list of email addresses or aliases via an SNS topic. Verify that when the alarm triggers, all email addresses receive the notification.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AWS Session 14 — SQS &amp;amp; SNS Messaging | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AWS Lambda — Serverless Compute, Cold Starts, Invocation Models &amp; VPC Integration</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Fri, 07 Aug 2026 07:27:54 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-lambda-serverless-compute-cold-starts-invocation-models-vpc-integration-48lh</link>
      <guid>https://dev.to/tejas_shinkar/aws-lambda-serverless-compute-cold-starts-invocation-models-vpc-integration-48lh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This session moves from managed databases into serverless compute — a fundamentally different execution model that changes how you think about infrastructure entirely.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;What is Serverless — The Mental Model&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Lambda vs EC2 — When to Use What&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Key Components of Lambda&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Lambda Handler — Entry Point&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Firecracker — What Actually Runs Lambda&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Cold Start vs Warm Invocation&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;The Lambda Init Phase&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Provisioned Concurrency — Eliminating Cold Starts&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Best Practices for Production Lambda&lt;/td&gt;
&lt;td&gt;DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Lambda Triggers&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Invocation Models — Synchronous, Asynchronous, Poll-Based&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Execution Roles and Permissions&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Lambda in a VPC — Private Resource Access&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Lambda Pricing&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Lab — Lambda Triggered by S3&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Interview Questions&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Assignment&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What is Serverless — The Mental Model
&lt;/h2&gt;

&lt;p&gt;"Serverless" doesn't mean there are no servers. It means &lt;strong&gt;you don't see, manage, or think about servers&lt;/strong&gt; — AWS runs them invisibly on your behalf.&lt;/p&gt;

&lt;p&gt;With traditional compute (EC2), you provision a server, choose its size, install the OS, deploy your code, and pay for that server 24/7 whether it's handling traffic or sitting idle. Serverless inverts all of that — you write a function, upload it, and AWS runs it only when something triggers it. The infrastructure appears and disappears automatically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The core shift:&lt;/strong&gt; In EC2, you manage servers that run your code. In Lambda, you write code and AWS manages everything that runs it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Three defining traits of serverless:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No server provisioning or management — AWS handles all infrastructure&lt;/li&gt;
&lt;li&gt;Automatic scaling — from zero to thousands of concurrent executions, instantly&lt;/li&gt;
&lt;li&gt;Pay only for what you use — billed per request + per millisecond of execution time (when idle, you pay nothing)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Lambda vs EC2 — When to Use What
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Chef Analogy
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;EC2 = Hire a full-time chef.&lt;/strong&gt; The chef is at the restaurant all day, every day — whether three customers arrive or three hundred. You pay the chef's salary regardless of how many orders come in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda = Call a chef only when an order arrives.&lt;/strong&gt; No orders → no cost. An order comes in → the chef cooks → leaves. You pay only for the cooking time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Side-by-Side Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;EC2&lt;/th&gt;
&lt;th&gt;Lambda&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Server management&lt;/td&gt;
&lt;td&gt;You manage (patching, scaling, monitoring)&lt;/td&gt;
&lt;td&gt;AWS manages everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup model&lt;/td&gt;
&lt;td&gt;Always running&lt;/td&gt;
&lt;td&gt;Runs only on trigger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max runtime&lt;/td&gt;
&lt;td&gt;Indefinite&lt;/td&gt;
&lt;td&gt;15 minutes per invocation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Manual or ASG&lt;/td&gt;
&lt;td&gt;Automatic, instant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing&lt;/td&gt;
&lt;td&gt;Per hour (even when idle)&lt;/td&gt;
&lt;td&gt;Per request + per GB-second of execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use for&lt;/td&gt;
&lt;td&gt;Long-running apps, stateful services, databases&lt;/td&gt;
&lt;td&gt;Event-driven, short-lived tasks, automation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Can maintain state on disk&lt;/td&gt;
&lt;td&gt;Stateless — no persistent state between invocations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  When Lambda is the right choice
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Responding to S3 file uploads (resize an image, parse a CSV)&lt;/li&gt;
&lt;li&gt;Processing messages from SQS, SNS, or Kinesis&lt;/li&gt;
&lt;li&gt;API backends triggered by API Gateway&lt;/li&gt;
&lt;li&gt;Scheduled tasks (run a report every night at midnight via EventBridge)&lt;/li&gt;
&lt;li&gt;Real-time stream processing (DynamoDB Streams, Kinesis)&lt;/li&gt;
&lt;li&gt;Glue code between services in an event-driven pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When Lambda is NOT the right choice
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Long-running processes (more than 15 minutes — use EC2 or Fargate)&lt;/li&gt;
&lt;li&gt;Applications that hold state in memory across requests&lt;/li&gt;
&lt;li&gt;Workloads requiring persistent local disk (use EC2 with EBS)&lt;/li&gt;
&lt;li&gt;High-performance compute (HPC, ML training — use GPU EC2)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview Q: Can Lambda run forever?&lt;/strong&gt; → No. Maximum execution timeout is 15 minutes. If your task takes longer, it must be broken into smaller steps (Step Functions) or moved to EC2/Fargate.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Key Components of Lambda
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Function&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your code — packaged and deployed to Lambda&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The language environment (Python 3.12, Node.js 20, Java 21, Go, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Handler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The specific function inside your code that Lambda calls on each invocation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Layers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reusable packages (libraries, dependencies) shared across multiple functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Execution Role&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An IAM Role that grants Lambda permission to call other AWS services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trigger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The event source that invokes the function (S3, API Gateway, SQS, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Log Streams&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CloudWatch Log Streams where function output and errors are captured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Function Settings&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Memory (128 MB to 10 GB), timeout (up to 15 min), environment variables, concurrency limits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Lambda Handler — The Entry Point
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;handler&lt;/strong&gt; is the entry point to your Lambda function — the specific function in your code that AWS calls when the function is triggered. You define it in the Lambda console as &lt;code&gt;filename.function_name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example in Python:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Event received:&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&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello from Lambda!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The two parameters every handler receives:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;event:&lt;/strong&gt; A dictionary/object containing all the information about what triggered this invocation — for an S3 trigger, it contains the bucket name and object key; for API Gateway, it contains the HTTP method, path, and request body; for SQS, it contains the message contents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;context:&lt;/strong&gt; An object with metadata about the invocation itself — the function name, the remaining execution time, the request ID, the CloudWatch log stream name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The return value of the handler becomes the response — for a synchronous invocation like API Gateway, the response is returned to the caller. For asynchronous invocations, the return value is ignored.&lt;/p&gt;




&lt;h2&gt;
  
  
  Firecracker — What Actually Runs Lambda
&lt;/h2&gt;

&lt;p&gt;When you invoke a Lambda function, something needs to execute your code in an isolated, secure environment. AWS built &lt;strong&gt;Firecracker&lt;/strong&gt; specifically for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firecracker&lt;/strong&gt; is AWS's lightweight microVM (micro virtual machine) technology — it provides the security and isolation of a full virtual machine with the speed and low resource usage needed for serverless workloads.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Think of it this way: a traditional VM takes seconds to start because it boots a full OS. A Docker container starts faster but shares the host kernel (less isolation). Firecracker hits the middle — it's a minimal VM with its own kernel that starts in milliseconds, uses very little memory, and is fully isolated from all other Lambda functions running on the same physical server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why Firecracker matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda functions from different customers can run on the same physical hardware with full isolation between them — security without sacrificing density&lt;/li&gt;
&lt;li&gt;Cold starts happen in milliseconds instead of seconds because Firecracker microVMs boot extremely fast&lt;/li&gt;
&lt;li&gt;The same technology powers AWS Fargate (serverless containers)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Cold Start vs Warm Invocation
&lt;/h2&gt;

&lt;p&gt;This is one of the most important Lambda concepts for both interviews and production use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cold Start
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;cold start&lt;/strong&gt; happens when Lambda needs to create a brand new execution environment to run your function — because no existing one is available (the function hasn't been invoked recently, or concurrency is ramping up faster than existing environments can handle).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens during a cold start:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AWS allocates a Firecracker microVM → loads the Lambda runtime (Python, Node, etc.) → downloads and unpacks your function code → runs all code outside the handler (the Init Phase) → then finally runs the handler.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This entire process adds latency — typically 100ms to a few seconds depending on the runtime, package size, and how much initialization code runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Warm Invocation
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;warm invocation&lt;/strong&gt; happens when Lambda reuses an existing execution environment that's already fully initialized from a previous invocation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AWS already has the Firecracker microVM running, the runtime loaded, and your code in memory → it directly invokes the handler → skips the entire Init Phase.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is significantly faster — no initialization overhead, just the handler execution time.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Lifecycle Visualized
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;First invocation (cold):&lt;/strong&gt;&lt;br&gt;
Create microVM → Load runtime → Download code → Run Init Phase (code outside handler) → Run handler → Return response → Environment stays warm for a while&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second invocation (warm):&lt;/strong&gt;&lt;br&gt;
Reuse existing environment → Run handler directly → Return response&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After extended idle period:&lt;/strong&gt;&lt;br&gt;
AWS recycles the environment → Next invocation is cold again&lt;/p&gt;

&lt;p&gt;🎯 &lt;strong&gt;Interview Q: After how much idle time does a Lambda become cold again?&lt;/strong&gt; → AWS doesn't guarantee a fixed idle timeout — the execution environment may be recycled at any time after being idle. The timing isn't published and isn't guaranteed. If consistent low latency is critical, use Provisioned Concurrency.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Lambda Init Phase
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Init Phase&lt;/strong&gt; occurs only during a cold start. It's the window between "Lambda decided to run your function" and "Lambda actually calls your handler."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What runs during Init Phase:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything outside the handler function — connection setup, SDK initialization, loading configuration, reading secrets from Secrets Manager or Parameter Store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example — good production pattern:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;# This runs ONCE during Init Phase (cold start only)
&lt;/span&gt;&lt;span class="n"&gt;dynamodb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dynamodb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;TABLE_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;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# loaded once, reused on warm invocations
&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="c1"&gt;# This runs on EVERY invocation
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_item&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters in production:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you initialize your DynamoDB client inside the handler, it gets created on every single invocation — wasting time and compute. If you initialize it outside (at module level), it's created once during the cold start and reused on all subsequent warm invocations — dramatically reducing per-invocation latency and cost.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Best practice:&lt;/strong&gt; Always initialize reusable resources (DB connections, SDK clients, loaded configuration, decrypted secrets) outside the handler. They survive across warm invocations. Inside the handler, only put code that must run per-invocation.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Provisioned Concurrency — Eliminating Cold Starts
&lt;/h2&gt;

&lt;p&gt;For latency-sensitive production workloads (APIs where users are waiting for a response), cold starts are unacceptable. &lt;strong&gt;Provisioned Concurrency&lt;/strong&gt; is the solution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Provisioned Concurrency keeps a specified number of Lambda execution environments &lt;strong&gt;pre-initialized and ready&lt;/strong&gt; — the Firecracker microVM is running, the runtime is loaded, and your Init Phase code has already executed. When a request arrives, it immediately hits a warm environment, with zero cold start latency.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Important distinction:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Provisioned Concurrency does NOT send dummy requests to keep the function warm — it physically keeps initialized execution environments alive in a ready state. There's no artificial traffic, just pre-warmed infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The cost trade-off:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You pay for provisioned concurrency even when no requests are coming in — you're paying to keep those environments alive. This is a deliberate trade-off: spend slightly more to guarantee consistent low latency for your users.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Customer-facing APIs where p99 latency matters&lt;/li&gt;
&lt;li&gt;Payment or checkout flows where slow responses lose sales&lt;/li&gt;
&lt;li&gt;Any Lambda function behind API Gateway serving real users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When NOT to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Background processing (users aren't waiting)&lt;/li&gt;
&lt;li&gt;Dev/test environments&lt;/li&gt;
&lt;li&gt;Functions with predictable low traffic&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Best Practices for Production Lambda
&lt;/h2&gt;

&lt;p&gt;These come directly from what you learned in class, framed as actionable rules:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;1. Keep your deployment package small.&lt;/strong&gt; Every MB of package size adds cold start latency because Lambda must download and unpack it. Use Lambda Layers for shared dependencies and avoid bundling unused libraries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Initialize reusable resources outside the handler.&lt;/strong&gt; Database connections, SDK clients, secrets — put them at module level so they're created once (Init Phase) and reused across warm invocations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Set timeouts deliberately.&lt;/strong&gt; Don't leave the default timeout at 3 seconds blindly — understand your function's expected runtime and set a meaningful timeout. Too short = unexpected failures. Too long = runaway functions costing money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Keep logging meaningful.&lt;/strong&gt; Every log line your function emits goes to CloudWatch and is charged. In production, avoid debug-level logging on every invocation — log only errors, warnings, and meaningful business events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Use environment variables for configuration.&lt;/strong&gt; Never hardcode secrets, endpoints, or table names in your code. Use environment variables (or Secrets Manager for sensitive values) so the same code works across dev/staging/prod environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Limit function scope.&lt;/strong&gt; One Lambda function should do one thing. A function that tries to do too much becomes hard to test, debug, and maintain.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Lambda Triggers
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;trigger&lt;/strong&gt; is an event source that invokes your Lambda function. Lambda functions don't run on their own — they always respond to something.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;What causes it&lt;/th&gt;
&lt;th&gt;Common use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S3&lt;/td&gt;
&lt;td&gt;File uploaded, deleted, or modified in a bucket&lt;/td&gt;
&lt;td&gt;Image processing, ETL, notifications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API Gateway&lt;/td&gt;
&lt;td&gt;HTTP request (GET, POST, PUT, DELETE)&lt;/td&gt;
&lt;td&gt;REST APIs, webhooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DynamoDB Streams&lt;/td&gt;
&lt;td&gt;Item inserted, updated, or deleted in a DynamoDB table&lt;/td&gt;
&lt;td&gt;Real-time data sync, audit logging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQS&lt;/td&gt;
&lt;td&gt;Message added to a queue&lt;/td&gt;
&lt;td&gt;Decoupled background processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SNS&lt;/td&gt;
&lt;td&gt;Message published to a topic&lt;/td&gt;
&lt;td&gt;Fan-out notifications, email, SMS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EventBridge&lt;/td&gt;
&lt;td&gt;Scheduled rule or custom event&lt;/td&gt;
&lt;td&gt;Cron jobs, automation pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kinesis&lt;/td&gt;
&lt;td&gt;Records added to a data stream&lt;/td&gt;
&lt;td&gt;Real-time analytics, log processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALB&lt;/td&gt;
&lt;td&gt;HTTP request via Application Load Balancer&lt;/td&gt;
&lt;td&gt;Serverless web applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cognito&lt;/td&gt;
&lt;td&gt;User pool events (signup, login)&lt;/td&gt;
&lt;td&gt;Custom auth flows, post-signup triggers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview Q: What triggers a Lambda function?&lt;/strong&gt; → Events from AWS services — S3 uploads, API Gateway requests, DynamoDB Streams, SQS messages, SNS notifications, EventBridge schedules, and more. Lambda is fundamentally event-driven.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Invocation Models — Synchronous, Asynchronous, Poll-Based
&lt;/h2&gt;

&lt;p&gt;Not all Lambda triggers work the same way — the invocation model determines who calls Lambda, who waits for the response, and who handles retries on failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Three Models
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Synchronous&lt;/th&gt;
&lt;th&gt;Asynchronous&lt;/th&gt;
&lt;th&gt;Poll-Based&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API Gateway, ALB, SDK direct call&lt;/td&gt;
&lt;td&gt;S3, SNS, EventBridge&lt;/td&gt;
&lt;td&gt;SQS, Kinesis, Kafka, DynamoDB Streams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Who triggers Lambda?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Caller directly&lt;/td&gt;
&lt;td&gt;AWS service (push)&lt;/td&gt;
&lt;td&gt;Lambda (polls the source)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Caller waits?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes — waits for response&lt;/td&gt;
&lt;td&gt;No — fire and forget&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retry on failure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Caller's responsibility&lt;/td&gt;
&lt;td&gt;Lambda retries automatically (up to 2 times)&lt;/td&gt;
&lt;td&gt;Event source + Lambda polling logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failure destination&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Error returned to caller&lt;/td&gt;
&lt;td&gt;Dead Letter Queue or EventBridge&lt;/td&gt;
&lt;td&gt;DLQ or failure destination&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Memory Tricks
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Synchronous → 📞 Phone Call&lt;/strong&gt;&lt;br&gt;
You call someone and stay on the line waiting for their answer. If they don't pick up, you know immediately — you handle the retry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asynchronous → 📧 Email&lt;/strong&gt;&lt;br&gt;
You send the email and move on with your day. The recipient processes it when they're ready. If delivery fails, the mail system retries, not you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Poll-Based → 📬 Checking Your Mailbox&lt;/strong&gt;&lt;br&gt;
You walk to the mailbox every few minutes to see if anything arrived. That's exactly what Lambda does with SQS, Kinesis, Kafka, and DynamoDB Streams — Lambda continuously polls the source and processes whatever it finds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Deep Dive Per Model
&lt;/h3&gt;

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

&lt;p&gt;The caller waits for Lambda to finish and return a response. If Lambda throws an error, the error is returned directly to the caller — the caller (application code or API Gateway) decides whether to retry.&lt;/p&gt;

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

&lt;p&gt;AWS queues the event and returns a 202 Accepted to the caller immediately — Lambda processes it when it can. If Lambda fails, AWS automatically retries up to 2 times. After all retries are exhausted, the failed event goes to a configured Dead Letter Queue (SQS or SNS) or an EventBridge failure destination for investigation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Poll-Based:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lambda itself polls the event source (SQS queue, Kinesis stream, DynamoDB Stream) in a loop. When it finds records, it invokes itself with a batch of them. This model is used for queue and stream processing — Lambda reads as fast as the source produces events.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview tip:&lt;/strong&gt; "How does S3 invoke Lambda?" → Asynchronously — S3 pushes an event notification to Lambda, doesn't wait for a response, and Lambda retries automatically on failure. "How does API Gateway invoke Lambda?" → Synchronously — the HTTP response the user sees depends on Lambda's return value.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Execution Roles and Permissions
&lt;/h2&gt;

&lt;p&gt;Lambda functions need IAM permissions to interact with other AWS services — they can't access S3, DynamoDB, or CloudWatch by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execution Role&lt;/strong&gt; = an IAM Role attached to a Lambda function that defines what AWS services and actions the function is allowed to call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: Lambda triggered by S3, writing results to DynamoDB&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Execution Role for this function needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;s3:GetObject&lt;/code&gt; on the source bucket (to read the uploaded file)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dynamodb:PutItem&lt;/code&gt; on the target table (to write the result)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;logs:CreateLogGroup&lt;/code&gt;, &lt;code&gt;logs:CreateLogStream&lt;/code&gt;, &lt;code&gt;logs:PutLogEvents&lt;/code&gt; on CloudWatch (to write logs)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The last three — CloudWatch log permissions — are included in the AWS managed policy &lt;code&gt;AWSLambdaBasicExecutionRole&lt;/code&gt;, which is the minimum every Lambda function needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Principle of Least Privilege applies here too:&lt;/strong&gt; Give Lambda only the permissions it actually needs, nothing more. A function that reads from S3 shouldn't also have DynamoDB write permissions unless it genuinely needs them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Common interview scenario:&lt;/strong&gt; "Your Lambda function is failing with an AccessDenied error when trying to write to DynamoDB. What do you check first?" → Check the Lambda function's Execution Role — verify it has &lt;code&gt;dynamodb:PutItem&lt;/code&gt; permission on that specific table ARN.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Lambda in a VPC — Private Resource Access
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Default Setup
&lt;/h3&gt;

&lt;p&gt;By default, Lambda runs inside an &lt;strong&gt;AWS-managed VPC&lt;/strong&gt; — this gives it internet access for calling public AWS service endpoints or external APIs, but it &lt;strong&gt;cannot reach resources inside your private VPC&lt;/strong&gt; — your RDS database, ElastiCache cluster, or private EC2 instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting Lambda to Your VPC
&lt;/h3&gt;

&lt;p&gt;To access private resources, you attach Lambda to your VPC by selecting subnets and Security Groups — similar to how you'd configure any other EC2-based resource.&lt;/p&gt;

&lt;p&gt;Under the hood, AWS uses &lt;strong&gt;Hyperplane ENIs&lt;/strong&gt; (pre-created, managed elastic network interfaces) to connect Lambda to your VPC. This is significantly faster than the older model where Lambda created a new ENI on every cold start — Hyperplane ENIs are shared and pre-warmed, which reduces cold start latency for VPC-attached functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Internet Access Trade-Off
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key point:&lt;/strong&gt; Once Lambda is attached to your VPC, it loses direct internet access. It now operates like any other private subnet resource.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This creates a dependency decision:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lambda needs to access&lt;/th&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Private resources (RDS, ElastiCache, internal EC2)&lt;/td&gt;
&lt;td&gt;Attach Lambda to your VPC, select private subnets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public internet (third-party APIs, external services)&lt;/td&gt;
&lt;td&gt;Route traffic through a &lt;strong&gt;NAT Gateway&lt;/strong&gt; in a public subnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS services (S3, DynamoDB, Secrets Manager)&lt;/td&gt;
&lt;td&gt;Use &lt;strong&gt;VPC Endpoints&lt;/strong&gt; — private connectivity without internet or NAT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview Q:&lt;/strong&gt; "Your Lambda function is attached to your VPC and can access your RDS database, but it can't reach an external payment API. What do you configure?" → Add a NAT Gateway to a public subnet in the same VPC. Update the private subnet's route table to route &lt;code&gt;0.0.0.0/0&lt;/code&gt; through the NAT Gateway. Lambda can then reach the internet through NAT while still accessing private resources directly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Flow with full connectivity:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lambda (in private subnet) → needs internet (external API) → route table sends to NAT Gateway (in public subnet) → NAT Gateway → Internet Gateway → external API&lt;/p&gt;

&lt;p&gt;Lambda (in private subnet) → needs S3 or DynamoDB → VPC Gateway/Interface Endpoint → directly to AWS service, no internet involved&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;Lambda billing has two components plus one commonly overlooked cost:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Component 1 — Requests:&lt;/strong&gt;&lt;br&gt;
You're charged per million invocations. The first 1 million requests per month are free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Component 2 — Duration (GB-seconds):&lt;/strong&gt;&lt;br&gt;
Execution time multiplied by the memory you allocated. A function with 512 MB of memory running for 2 seconds = 1 GB-second of compute.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;More memory = higher cost per second, but often faster execution (Lambda allocates CPU proportionally to memory). Sometimes giving a function more memory actually reduces cost because it finishes faster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Component 3 — CloudWatch Logs (often overlooked):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every &lt;code&gt;print()&lt;/code&gt; statement, every log line your Lambda function emits goes to CloudWatch Logs — and CloudWatch charges for log ingestion and storage. For functions invoked millions of times per day, excessive logging becomes a real cost.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Production discipline:&lt;/strong&gt; In production, log errors and important business events. Remove or disable debug-level logging that fires on every invocation. The savings add up at scale.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Free Tier (permanent, not just 12 months):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 million requests/month&lt;/li&gt;
&lt;li&gt;400,000 GB-seconds of compute/month&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧪 Lab — Lambda Triggered by S3
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What you're building:&lt;/strong&gt; Upload an image to S3 → Lambda is triggered → (in the assignment, Lambda resizes the image and saves the thumbnail back to S3)&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Create the Lambda Function
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;AWS Console → Lambda → Create function&lt;/p&gt;

&lt;p&gt;Choose: Author from scratch&lt;br&gt;
Function name: &lt;code&gt;s3-image-processor&lt;/code&gt;&lt;br&gt;
Runtime: Python 3.12&lt;br&gt;
Architecture: x86_64&lt;/p&gt;

&lt;p&gt;Execution role: Create a new role with basic Lambda permissions (adds CloudWatch Logs access automatically)&lt;br&gt;
→ Create function&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 2 — Configure S3 Trigger
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;In the Lambda function page → Add trigger → Select S3&lt;br&gt;
Bucket: select your source bucket&lt;br&gt;
Event type: &lt;code&gt;PUT&lt;/code&gt; (triggers on file upload)&lt;br&gt;
Prefix/Suffix: optionally filter to &lt;code&gt;.jpg&lt;/code&gt; or &lt;code&gt;.png&lt;/code&gt; files only&lt;br&gt;
→ Add&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 3 — Write the Handler
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&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="c1"&gt;# Extract bucket and object key from the S3 event
&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;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="mi"&gt;0&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;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="mi"&gt;0&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="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;New file uploaded: s3://&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="s"&gt;/&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="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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Processed: &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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4 — Add Required Permissions to Execution Role
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Lambda function → Configuration → Permissions → Click the execution role link → IAM console opens&lt;br&gt;
Add inline policy or attach managed policy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;s3:GetObject&lt;/code&gt; on the source bucket&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;s3:PutObject&lt;/code&gt; on the destination bucket (for the assignment)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 5 — Test
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Upload any file to your S3 bucket → Go to Lambda → Monitor → View CloudWatch logs&lt;br&gt;
You should see the log line: &lt;code&gt;New file uploaded: s3://your-bucket/your-file.jpg&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lambda Core&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serverless — no server management, auto-scales, pay per use&lt;/li&gt;
&lt;li&gt;Max timeout: 15 minutes&lt;/li&gt;
&lt;li&gt;Stateless — no persistent state between invocations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cold Start vs Warm&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold: create microVM → load runtime → download code → Init Phase → run handler (slow)&lt;/li&gt;
&lt;li&gt;Warm: reuse existing environment → run handler directly (fast)&lt;/li&gt;
&lt;li&gt;AWS may recycle idle environments at any time — no guaranteed idle timeout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Init Phase&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs only on cold start&lt;/li&gt;
&lt;li&gt;Executes all code outside the handler (DB connections, SDK clients, secrets)&lt;/li&gt;
&lt;li&gt;Initialize reusable resources here — they survive across warm invocations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Provisioned Concurrency&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeps N environments pre-initialized → zero cold starts for those N concurrent requests&lt;/li&gt;
&lt;li&gt;Costs money even when idle — pay for consistent latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Invocation Models&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Memory trick&lt;/th&gt;
&lt;th&gt;Who retries?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Synchronous&lt;/td&gt;
&lt;td&gt;📞 Phone call — wait for answer&lt;/td&gt;
&lt;td&gt;Caller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asynchronous&lt;/td&gt;
&lt;td&gt;📧 Email — send and move on&lt;/td&gt;
&lt;td&gt;Lambda (auto, up to 2x)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Poll-Based&lt;/td&gt;
&lt;td&gt;📬 Check mailbox — Lambda polls&lt;/td&gt;
&lt;td&gt;Event source + polling logic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;VPC Integration&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default: Lambda in AWS VPC — has internet, no private resource access&lt;/li&gt;
&lt;li&gt;VPC-attached: Lambda in your VPC — access private resources, loses internet&lt;/li&gt;
&lt;li&gt;Fix for internet: add NAT Gateway&lt;/li&gt;
&lt;li&gt;Fix for AWS services: use VPC Endpoints (private, no internet needed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Firecracker&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lightweight microVM — full isolation, millisecond startup&lt;/li&gt;
&lt;li&gt;Powers Lambda and Fargate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Per request + per GB-second (memory × duration)&lt;/li&gt;
&lt;li&gt;CloudWatch Logs cost is easy to overlook — keep logging lean in production&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: What is the difference between Lambda and EC2?&lt;/strong&gt;&lt;br&gt;
EC2 is a persistent virtual machine you provision, manage, and pay for continuously — whether handling traffic or idle. Lambda is serverless — it runs only when triggered, scales automatically, charges only for the execution time used, and has zero infrastructure management overhead. EC2 is for long-running stateful services; Lambda is for event-driven, short-lived tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What is a cold start in Lambda and how do you reduce it?&lt;/strong&gt;&lt;br&gt;
A cold start occurs when Lambda creates a new execution environment — it must initialize the Firecracker microVM, load the runtime, download the function code, and run the Init Phase before the handler executes. This adds latency. To reduce cold starts: keep the deployment package small, initialize reusable resources outside the handler, and use Provisioned Concurrency for latency-critical functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: What is the difference between synchronous, asynchronous, and poll-based invocation?&lt;/strong&gt;&lt;br&gt;
Synchronous invocation (API Gateway, ALB) means the caller waits for Lambda's response and handles retries itself. Asynchronous invocation (S3, SNS, EventBridge) means AWS queues the event, returns immediately to the caller, and Lambda retries automatically on failure. Poll-based invocation (SQS, Kinesis, DynamoDB Streams) means Lambda itself continuously polls the source and processes records in batches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: Why does Lambda lose internet access when attached to a VPC?&lt;/strong&gt;&lt;br&gt;
Lambda in a VPC operates like any resource in a private subnet — it routes traffic through the subnet's route table. Since private subnets don't have a direct route to the Internet Gateway, Lambda has no internet path. To restore internet access, you add a NAT Gateway in a public subnet and route the private subnet's &lt;code&gt;0.0.0.0/0&lt;/code&gt; traffic through it — the same pattern used for any private EC2 instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: What is Provisioned Concurrency and when would you use it?&lt;/strong&gt;&lt;br&gt;
Provisioned Concurrency keeps a specified number of Lambda execution environments pre-initialized — the microVM, runtime, and Init Phase code are already ready. When requests arrive, they immediately hit warm environments with zero cold start latency. You'd use it for customer-facing APIs or payment flows where consistent low latency is critical. It costs money even when idle, so it's not appropriate for background or batch processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: What should you initialize outside the Lambda handler vs inside it?&lt;/strong&gt;&lt;br&gt;
Outside the handler (at module level, runs once during Init Phase): database connection clients, SDK clients (boto3 sessions), secrets loaded from Secrets Manager, configuration loaded from environment variables, pre-computed static data. Inside the handler (runs on every invocation): logic that depends on the event itself — parsing the event, executing the business logic, returning the response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: Your Lambda function returns AccessDenied when calling DynamoDB. What do you check?&lt;/strong&gt;&lt;br&gt;
Check the Lambda function's Execution Role in IAM — verify it has the required DynamoDB permissions (&lt;code&gt;dynamodb:GetItem&lt;/code&gt;, &lt;code&gt;dynamodb:PutItem&lt;/code&gt;, etc.) on the correct table ARN. The Execution Role is the IAM identity Lambda uses when calling other AWS services, and missing permissions here is the most common cause of AccessDenied errors from Lambda.&lt;/p&gt;




&lt;h2&gt;
  
  
  📝 Assignment
&lt;/h2&gt;

&lt;p&gt;Build an image resize pipeline: when an image is uploaded to an S3 bucket, Lambda automatically resizes it to a thumbnail and saves the result to a different S3 prefix or bucket.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AWS Session 13 — Lambda Fundamentals | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AWS Aurora, ElastiCache Patterns &amp; DynamoDB — The Complete Data Layer</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Fri, 07 Aug 2026 00:54:38 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-aurora-elasticache-patterns-dynamodb-the-complete-data-layer-1k3e</link>
      <guid>https://dev.to/tejas_shinkar/aws-aurora-elasticache-patterns-dynamodb-the-complete-data-layer-1k3e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This session completes the database picture — Aurora's read/write architecture, ElastiCache caching strategies, and DynamoDB from table creation to production-ready query patterns.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Aurora Endpoints — Writer vs Reader&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;What Happens When the Aurora Writer Fails&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;ElastiCache Caching Patterns — Lazy Loading, Write Through, Session Store&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Cache Invalidation&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;DynamoDB — What It Is and When to Use It&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;DynamoDB Table Creation — Keys and Settings&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Table Classes — Standard vs Standard-IA&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Capacity Modes — On-Demand vs Provisioned&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Warm Throughput&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;DynamoDB Items &amp;amp; Attributes — CRUD Operations&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Query vs Scan — The Critical Difference&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Local Secondary Index (LSI) vs Global Secondary Index (GSI)&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Bonus Concepts — Streams, DAX, Consistency, Transactions&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Interview Questions&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Practice Tasks&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Aurora Endpoints — Writer vs Reader
&lt;/h2&gt;

&lt;p&gt;Aurora doesn't give you just one database endpoint — it gives you two, each serving a different purpose and routing to different parts of the cluster.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Writer Endpoint (Primary Endpoint):&lt;/strong&gt; Always points to the current primary/writer instance. All write operations (INSERT, UPDATE, DELETE) go here. If a failover happens and a replica is promoted, Aurora automatically redirects this endpoint to the new writer — your application's configuration never needs to change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reader Endpoint:&lt;/strong&gt; A load-balanced endpoint that distributes read-only queries (SELECT) across all available Aurora Replicas. You don't manage which replica serves each query — Aurora handles the routing, spreading read traffic evenly across however many replicas exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why this architecture matters:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a typical application, reads far outnumber writes — a product page might be read thousands of times per second but updated once a day. Without a reader endpoint, all reads and writes compete for the same primary instance. With Aurora's reader endpoint, reads are offloaded to replicas — the writer handles only writes, and read capacity scales horizontally by adding more replicas.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview Q1: Why do we need a Reader Endpoint?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To distribute read-only queries across multiple Aurora Replicas. This reduces load on the writer instance, improves response times for reads, and lets you scale read capacity horizontally by adding replicas — all without any change to the application connection string.&lt;/p&gt;

&lt;p&gt;🎯 &lt;strong&gt;Interview Q2: What happens if the Aurora writer fails?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Aurora automatically promotes one of the existing replicas to become the new writer. The Writer Endpoint automatically redirects to the new primary. Because the endpoint itself doesn't change (only what it resolves to), the application needs zero configuration changes. Aurora's failover is typically faster than standard RDS Multi-AZ — often under 30 seconds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Connection to last session:&lt;/strong&gt; This is exactly the same principle as RDS using a DNS endpoint instead of a static IP — the endpoint is a stable address that AWS reroutes behind the scenes. Your application always connects to the same string, and AWS handles where that string points.&lt;/p&gt;




&lt;h2&gt;
  
  
  ElastiCache Caching Patterns
&lt;/h2&gt;

&lt;p&gt;ElastiCache isn't just "put stuff in cache" — how you cache data matters significantly for consistency, performance, and staleness. There are three standard patterns, each designed for a different use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;th&gt;Stale data?&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;Lazy Loading&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cache data only on the first read (cache miss triggers DB query + cache write)&lt;/td&gt;
&lt;td&gt;Yes (possible)&lt;/td&gt;
&lt;td&gt;Product catalog, blogs, non-critical reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Write Through&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Update cache and database together on every write&lt;/td&gt;
&lt;td&gt;No (always in sync)&lt;/td&gt;
&lt;td&gt;Banking, inventory, user profiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Session Store&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Store temporary session data in cache with a TTL&lt;/td&gt;
&lt;td&gt;N/A (time-bound)&lt;/td&gt;
&lt;td&gt;Shopping carts, login sessions, user state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Lazy Loading (Cache-Aside)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Application checks cache → &lt;strong&gt;Cache Hit:&lt;/strong&gt; return data immediately → &lt;strong&gt;Cache Miss:&lt;/strong&gt; query the database, write result to cache, return data to caller.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The good:&lt;/strong&gt; Only data that's actually requested gets cached — no wasted cache memory on data nobody reads. Very simple to implement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trade-off:&lt;/strong&gt; On a cache miss, the call takes longer because it hits the database AND writes to the cache. And there's a window where the cache can serve stale data if the database was updated without invalidating the cache entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How stale data is handled in Lazy Loading:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When a database record is updated, the corresponding cache entry is explicitly deleted (invalidated). On the next read, a cache miss occurs, the latest data is fetched from the database, and the cache is refreshed with the fresh value. A TTL (Time To Live) is often set on cache entries as a backup — even if something isn't explicitly invalidated, the cache automatically expires it after the TTL elapses.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Write Through
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Every database write also updates the cache at the same time — both happen together, never independently.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The good:&lt;/strong&gt; The cache is always synchronized with the database. No stale data ever. When an application reads from cache, it always gets the current value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trade-off:&lt;/strong&gt; Every write incurs extra latency because two writes happen (DB + cache) instead of one. Cache memory may be used for data that's written often but rarely read.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;When to choose which:&lt;/strong&gt; If consistency is critical (banking, inventory, anything financial) → Write Through. If reads dominate and occasional staleness is acceptable (product listings, blog content) → Lazy Loading. If you're storing temporary state that shouldn't live in the database at all → Session Store.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Session Store
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Temporary session data (login tokens, shopping cart contents, user preferences) is stored in ElastiCache with a TTL. When the TTL expires, the session data is automatically deleted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why cache, not the database:&lt;/strong&gt; Session data is read and written on every request, changes frequently, and has a natural expiry — it doesn't need the durability guarantees of a relational database. Storing it in a distributed cache means any application server can retrieve any user's session without sticky sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Session store is like a coat check at a restaurant. You hand over your coat (session data) when you arrive, get a token (session ID), and the coat is held temporarily. If you don't come back within closing time (TTL), the restaurant donates it. You never permanently owned the storage — it was always meant to be temporary.&lt;/p&gt;




&lt;h2&gt;
  
  
  DynamoDB — What It Is and When to Use It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Amazon DynamoDB&lt;/strong&gt; is a fully managed, serverless, key-value and document NoSQL database. Unlike RDS, there are no servers to provision, no capacity planning for OS or engine, no connection limits to manage — you define a table, put data in it, and DynamoDB scales automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to choose DynamoDB over RDS:&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;Choose RDS when&lt;/th&gt;
&lt;th&gt;Choose DynamoDB when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data has complex relationships, needs JOINs&lt;/td&gt;
&lt;td&gt;Data access patterns are simple and predictable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strong ACID transactions across many tables&lt;/td&gt;
&lt;td&gt;Ultra-low latency at massive scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team is already SQL-native&lt;/td&gt;
&lt;td&gt;Flexible or evolving schema (semi-structured data)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reporting and analytics queries&lt;/td&gt;
&lt;td&gt;Serverless, no schema migration headaches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What DynamoDB is NOT for:&lt;/strong&gt; Complex analytical queries, ad-hoc SQL reporting, or workloads where you don't know your access patterns ahead of time. DynamoDB is optimized for known, repetitive access patterns — "give me user 1234's orders" not "give me the average order value grouped by region for Q4."&lt;/p&gt;




&lt;h2&gt;
  
  
  DynamoDB Table Creation — Keys and Settings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Primary Key — Two Flavors
&lt;/h3&gt;

&lt;p&gt;Every item in a DynamoDB table must have a unique primary key. You define this at table creation and cannot change it later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1 — Partition Key only (Simple Primary Key)&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The partition key alone uniquely identifies every item. DynamoDB uses this key to determine which physical partition stores the item (via internal hashing). No two items can share the same partition key value.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Good for: user profiles (user_id is unique per user), product catalog (product_id is unique per product).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2 — Partition Key + Sort Key (Composite Primary Key)&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Two attributes together form a unique identity. Items with the same partition key are grouped together and sorted by the sort key. This allows related items to live close together physically, making range queries efficient.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Good for: orders (partition key = user_id, sort key = order_timestamp → "give me all orders for user 1234, sorted by time"), messages (partition key = chat_id, sort key = message_timestamp → "give me all messages in this chat, newest first").&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview tip:&lt;/strong&gt; The partition key is the access gateway — your most common query pattern must be expressible using it. If you can't answer "what's my partition key?" you haven't finished designing your table.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Table Settings — What Each Option Does
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Table Class:&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;Class&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Cost note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standard&lt;/td&gt;
&lt;td&gt;Frequently accessed data&lt;/td&gt;
&lt;td&gt;Higher storage cost, optimized for read/write throughput&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard-IA (Infrequent Access)&lt;/td&gt;
&lt;td&gt;Data accessed rarely&lt;/td&gt;
&lt;td&gt;Lower storage cost (~60% cheaper), higher per-read/write cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Capacity Mode — the most important config decision:&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;&lt;/th&gt;
&lt;th&gt;On-Demand&lt;/th&gt;
&lt;th&gt;Provisioned&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;How it works&lt;/td&gt;
&lt;td&gt;DynamoDB auto-scales instantly per request&lt;/td&gt;
&lt;td&gt;You specify exact RCUs and WCUs upfront&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Traffic pattern&lt;/td&gt;
&lt;td&gt;Unpredictable, spiky, or new workloads&lt;/td&gt;
&lt;td&gt;Predictable, steady, well-known workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost model&lt;/td&gt;
&lt;td&gt;Pay per request (higher per-unit cost)&lt;/td&gt;
&lt;td&gt;Pay for reserved capacity (lower cost if fully utilized)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throttling risk&lt;/td&gt;
&lt;td&gt;None — handles any traffic instantly&lt;/td&gt;
&lt;td&gt;Yes — excess traffic is throttled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use&lt;/td&gt;
&lt;td&gt;Dev/test, early-stage products, variable traffic&lt;/td&gt;
&lt;td&gt;Production workloads with known baselines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;RCU and WCU — what they measure:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One &lt;strong&gt;Read Capacity Unit (RCU)&lt;/strong&gt; = one strongly consistent read per second for an item up to 4 KB. One &lt;strong&gt;Write Capacity Unit (WCU)&lt;/strong&gt; = one write per second for an item up to 1 KB.&lt;/p&gt;

&lt;p&gt;The Capacity Calculator in the console estimates how many RCUs and WCUs your workload needs — you input item size, read/write rate, and consistency requirement. Eventually consistent reads cost half a WCU each.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Warm Throughput
&lt;/h3&gt;

&lt;p&gt;When DynamoDB allocates capacity to a new table, it starts conservatively. If a sudden burst of traffic hits immediately — say, a product launch — DynamoDB may throttle requests until it has time to scale up internally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warm Throughput&lt;/strong&gt; pre-warms the table at creation time, telling DynamoDB to allocate higher baseline capacity from the start, so that sudden traffic spikes on day one don't cause throttling.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Think of it as pre-heating an oven before putting food in — you don't wait for it to warm up slowly after the food is already in there. You configure the starting temperature so it's ready when you need it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  DynamoDB Items &amp;amp; Attributes — CRUD Operations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Terms
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Table:&lt;/strong&gt; the container for your data (like a spreadsheet)&lt;br&gt;
&lt;strong&gt;Item:&lt;/strong&gt; a single record in the table (like a row)&lt;br&gt;
&lt;strong&gt;Attribute:&lt;/strong&gt; a field on an item (like a column) — but unlike SQL, different items in the same table can have different attributes (schema-flexible)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Example — two items in the same table with different attributes:&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;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"u-1234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tejas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tejas@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nashik"&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;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"u-5678"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Rahul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+91-9876543210"&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;No schema migration needed — DynamoDB doesn't enforce that every item has the same attributes.&lt;/p&gt;

&lt;h3&gt;
  
  
  CRUD in the DynamoDB Console
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Create:&lt;/strong&gt; Use "Create item" → add attribute name and value → can switch between form view and JSON view → save.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read:&lt;/strong&gt; Use Query or Scan (see next section) to retrieve items, or open an item directly from the results list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; Select an item → edit any attribute value → save. The partition key and sort key cannot be changed — they define the item's identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete:&lt;/strong&gt; Select an item → delete. This permanently removes it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The JSON view is particularly useful for seeing exactly how DynamoDB stores your data internally — every attribute has a type tag (S for string, N for number, BOOL for boolean, L for list, M for map).&lt;/p&gt;




&lt;h2&gt;
  
  
  Query vs Scan — The Critical Difference
&lt;/h2&gt;

&lt;p&gt;This is one of the most important performance decisions in DynamoDB, and it comes up in almost every DynamoDB interview question.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Scan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;How it works&lt;/td&gt;
&lt;td&gt;Retrieves items using the Partition Key (+ optional Sort Key)&lt;/td&gt;
&lt;td&gt;Reads the &lt;strong&gt;entire table&lt;/strong&gt;, then filters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Efficiency&lt;/td&gt;
&lt;td&gt;Very efficient — reads only the relevant partition&lt;/td&gt;
&lt;td&gt;Very inefficient — reads everything regardless&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Low — charged only for data actually returned&lt;/td&gt;
&lt;td&gt;High — charged for the entire table read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When to use&lt;/td&gt;
&lt;td&gt;Production workloads — always prefer this&lt;/td&gt;
&lt;td&gt;Occasional admin tasks, small tables, migrations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;The rule:&lt;/strong&gt; In production, you should almost never Scan. If you find yourself needing to Scan frequently, it's a signal that your table's primary key design doesn't match your access patterns — and you need either a redesign or an additional index (LSI/GSI).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Concrete example:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You have an Orders table with partition key = &lt;code&gt;user_id&lt;/code&gt; and sort key = &lt;code&gt;order_date&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Query:&lt;/strong&gt; "Give me all orders for user_id = 'u-1234' placed after 2026-01-01" → DynamoDB goes directly to that partition, reads only those items. Fast and cheap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scan:&lt;/strong&gt; "Give me all orders where order total &amp;gt; 5000" → DynamoDB reads every single order in the table across all partitions, then filters. Slow and expensive — the filter happens after reading everything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Filters with Query:&lt;/strong&gt; You can add filter expressions to a Query result, but filters are applied &lt;em&gt;after&lt;/em&gt; items are read from the partition — they reduce what you see, not what DynamoDB reads and charges for. Sort key conditions, however, genuinely reduce what is read.&lt;/p&gt;




&lt;h2&gt;
  
  
  Local Secondary Index (LSI) vs Global Secondary Index (GSI)
&lt;/h2&gt;

&lt;p&gt;Sometimes your query patterns require filtering or sorting by an attribute that isn't your primary key. Indexes let you do this efficiently without falling back to Scan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Indexes in DynamoDB are like the index at the back of a book — instead of reading every page to find mentions of "VPC", you flip to the index and jump directly to the relevant pages. DynamoDB maintains this alternative lookup structure automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local Secondary Index (LSI)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Same Partition Key as the base table, but a &lt;strong&gt;different Sort Key&lt;/strong&gt;. Allows you to sort or filter items within the same partition using a different attribute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Example: Orders table has partition key = &lt;code&gt;user_id&lt;/code&gt;, sort key = &lt;code&gt;order_date&lt;/code&gt;. You also need to query orders sorted by &lt;code&gt;order_total&lt;/code&gt; for the same user. Create an LSI with sort key = &lt;code&gt;order_total&lt;/code&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Must be created &lt;strong&gt;at table creation time&lt;/strong&gt; — cannot add later&lt;/li&gt;
&lt;li&gt;Maximum 5 LSIs per table&lt;/li&gt;
&lt;li&gt;Shares provisioned throughput with the base table&lt;/li&gt;
&lt;li&gt;Only queries within a single partition (same partition key as base table)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Global Secondary Index (GSI)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;A completely independent index with its own Partition Key and optional Sort Key — totally different from the base table's keys.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Example: Orders table has partition key = &lt;code&gt;user_id&lt;/code&gt;. You want "all orders with status = 'PENDING' sorted by order_date." Create a GSI with partition key = &lt;code&gt;status&lt;/code&gt; and sort key = &lt;code&gt;order_date&lt;/code&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Can be created &lt;strong&gt;at any time&lt;/strong&gt; — much more flexible than LSI&lt;/li&gt;
&lt;li&gt;Maximum 20 GSIs per table&lt;/li&gt;
&lt;li&gt;Has its own separate provisioned throughput (additional cost)&lt;/li&gt;
&lt;li&gt;Queries can span all partitions of the base table (truly global)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  LSI vs GSI Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;LSI&lt;/th&gt;
&lt;th&gt;GSI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Partition Key&lt;/td&gt;
&lt;td&gt;Same as base table&lt;/td&gt;
&lt;td&gt;Different (you define it)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sort Key&lt;/td&gt;
&lt;td&gt;Different from base table&lt;/td&gt;
&lt;td&gt;Optional, you define it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When created&lt;/td&gt;
&lt;td&gt;At table creation only&lt;/td&gt;
&lt;td&gt;Any time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;Shared with base table&lt;/td&gt;
&lt;td&gt;Separate (extra cost)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query scope&lt;/td&gt;
&lt;td&gt;Single partition&lt;/td&gt;
&lt;td&gt;Entire table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max per table&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use when&lt;/td&gt;
&lt;td&gt;Need different sort within same partition&lt;/td&gt;
&lt;td&gt;Need to query by a completely different attribute&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview trap:&lt;/strong&gt; "Can you add an LSI after creating a DynamoDB table?" → No. LSIs must be defined at creation. If you realize you need one later, you'd have to recreate the table and migrate data. GSIs can be added any time. Always think through your access patterns before creating a DynamoDB table.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Bonus Concepts — Worth Knowing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DynamoDB Streams
&lt;/h3&gt;

&lt;p&gt;DynamoDB can stream a record of every change (INSERT, UPDATE, DELETE) to items in a table — useful for event-driven architectures.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Common pattern: DynamoDB item changes → Stream captures the event → Lambda is triggered → processes the change (send a notification, update a search index, replicate to another system, populate a cache).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  DAX — DynamoDB Accelerator
&lt;/h3&gt;

&lt;p&gt;DynamoDB's own in-memory caching layer, built specifically for DynamoDB. Reduces read latency from milliseconds to microseconds. Unlike ElastiCache (which is general-purpose), DAX is DynamoDB-native and requires no application code changes — it's a drop-in cache using the exact same DynamoDB API.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;DAX vs ElastiCache for DynamoDB:&lt;/strong&gt; If your application already uses DynamoDB and just needs faster reads, DAX is simpler (same API, no code changes). ElastiCache gives more flexibility if you need to cache data from multiple sources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Eventually Consistent vs Strongly Consistent Reads
&lt;/h3&gt;

&lt;p&gt;DynamoDB replicates data across multiple AZs. On a write, data propagates across replicas within roughly one second.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Eventually Consistent (default)&lt;/th&gt;
&lt;th&gt;Strongly Consistent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data freshness&lt;/td&gt;
&lt;td&gt;Might be slightly stale&lt;/td&gt;
&lt;td&gt;Always the most up-to-date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RCU cost&lt;/td&gt;
&lt;td&gt;0.5 RCU per 4 KB&lt;/td&gt;
&lt;td&gt;1 full RCU per 4 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Lower&lt;/td&gt;
&lt;td&gt;Slightly higher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use when&lt;/td&gt;
&lt;td&gt;Most general reads&lt;/td&gt;
&lt;td&gt;Must read your own write immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When it matters:&lt;/strong&gt; For most use cases (product catalog, session data), eventual consistency is fine. For financial transactions or reservation systems where you must immediately read your own write, use strongly consistent reads.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  DynamoDB Transactions
&lt;/h3&gt;

&lt;p&gt;DynamoDB supports ACID transactions via &lt;code&gt;TransactGetItems&lt;/code&gt; and &lt;code&gt;TransactWriteItems&lt;/code&gt; — you can atomically read or write multiple items across multiple tables, either all succeeding or all failing together. This addresses the common misconception that NoSQL databases can't handle transactions.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Aurora Endpoints&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writer Endpoint → always points to current primary → handles all writes&lt;/li&gt;
&lt;li&gt;Reader Endpoint → load-balances reads across all replicas&lt;/li&gt;
&lt;li&gt;On writer failure → replica promoted automatically → Writer Endpoint reroutes → zero app config changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Caching Patterns&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;Pattern&lt;/th&gt;
&lt;th&gt;Stale data?&lt;/th&gt;
&lt;th&gt;Use for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lazy Loading&lt;/td&gt;
&lt;td&gt;Yes (until invalidated or TTL expires)&lt;/td&gt;
&lt;td&gt;Read-heavy, occasional stale ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write Through&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Consistency-critical (banking, inventory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session Store&lt;/td&gt;
&lt;td&gt;N/A (TTL-controlled)&lt;/td&gt;
&lt;td&gt;Login sessions, shopping carts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cache invalidation in Lazy Loading: delete cache entry on DB update → next read repopulates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DynamoDB Keys&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Partition Key only → must be unique per item&lt;/li&gt;
&lt;li&gt;Partition Key + Sort Key → partition groups related items, sort key orders them within a partition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Capacity Modes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On-Demand: pay per request, no throttle, higher unit cost → unpredictable workloads&lt;/li&gt;
&lt;li&gt;Provisioned: fixed RCUs/WCUs, lower cost if utilized, throttles on excess → predictable production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Query vs Scan&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query: uses Partition Key, reads only relevant data → fast, cheap → always prefer&lt;/li&gt;
&lt;li&gt;Scan: reads entire table → slow, expensive → admin tasks only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Indexes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LSI: same partition key, different sort key, create at table creation only, max 5&lt;/li&gt;
&lt;li&gt;GSI: own partition key, create any time, max 20, separate throughput cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bonus&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DAX: DynamoDB-native microsecond cache, no code changes needed&lt;/li&gt;
&lt;li&gt;Streams: captures item-level changes for Lambda/event-driven patterns&lt;/li&gt;
&lt;li&gt;Strongly Consistent Read: 2× the RCU cost vs eventually consistent, guaranteed fresh data&lt;/li&gt;
&lt;li&gt;Transactions: ACID across multiple items/tables — DynamoDB does support transactions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: What is the difference between the Writer Endpoint and the Reader Endpoint in Aurora?&lt;/strong&gt;&lt;br&gt;
The Writer Endpoint always points to the current primary instance and handles all write operations. The Reader Endpoint load-balances read queries across all Aurora Replicas. If the writer fails, Aurora promotes a replica and automatically redirects the Writer Endpoint to it — the application needs no configuration changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What are the three ElastiCache caching patterns and when would you use each?&lt;/strong&gt;&lt;br&gt;
Lazy Loading caches data only on a cache miss — good for read-heavy workloads where some staleness is acceptable. Write Through updates both the cache and database on every write — good when consistency is critical like banking or inventory. Session Store uses the cache with a TTL to hold temporary session data like login sessions and shopping carts — not persisted to a database at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: How do you handle stale data in a Lazy Loading cache?&lt;/strong&gt;&lt;br&gt;
Explicitly delete the cache entry whenever the underlying database record is updated. The next read triggers a cache miss, fetches the latest data from the database, and refreshes the cache. A TTL on cache entries provides automatic backup expiry even if explicit invalidation is missed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: What is the difference between a Query and a Scan in DynamoDB?&lt;/strong&gt;&lt;br&gt;
A Query uses the Partition Key to efficiently retrieve only the relevant items — fast, cheap, and the right choice for production. A Scan reads the entire table across all partitions before filtering — slow, expensive, and charged for all data read regardless of what the filter returns. In production, Scans should almost never be used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: What is the difference between an LSI and a GSI?&lt;/strong&gt;&lt;br&gt;
An LSI uses the same Partition Key as the base table but a different Sort Key — it allows alternative sorting within a single partition and must be created at table creation time. A GSI has its own completely independent Partition Key (and optional Sort Key), can query across all partitions using a different attribute, and can be created at any time after the table exists. GSIs have separate throughput (additional cost); LSIs share the base table's throughput.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: Can you add a Local Secondary Index after a DynamoDB table is created?&lt;/strong&gt;&lt;br&gt;
No. LSIs must be defined at table creation time. If you need one after the fact, you'd have to create a new table with the LSI defined and migrate the data. GSIs can be added at any time, making them far more flexible when requirements change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: When would you choose On-Demand capacity mode over Provisioned for DynamoDB?&lt;/strong&gt;&lt;br&gt;
On-Demand is best for unpredictable or spiky traffic, new products where read/write patterns aren't yet known, and development/testing environments. Provisioned capacity is better for steady, predictable production workloads where you know the baseline RCU/WCU requirements — it's cheaper per unit when fully utilized, but throttles if traffic exceeds the provisioned amount.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q8: What is the difference between DAX and ElastiCache for DynamoDB caching?&lt;/strong&gt;&lt;br&gt;
DAX is a DynamoDB-native in-memory cache that uses the same API as DynamoDB and requires no application code changes, reducing latency from milliseconds to microseconds. ElastiCache is a general-purpose caching layer (Redis/Valkey/Memcached) that can cache data from DynamoDB or any other source, but requires application-level code to check the cache and handle misses. DAX is simpler for pure DynamoDB acceleration; ElastiCache offers more flexibility for multi-source caching.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔬 Practice Tasks
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Aurora Endpoints lab:&lt;/strong&gt; Create an Aurora MySQL cluster with 2 replicas. Connect to the Writer Endpoint and insert some rows. Connect to the Reader Endpoint and SELECT the same rows — confirm it serves reads. Manually trigger a failover and verify the Writer Endpoint automatically redirects to the new primary within ~30 seconds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Caching pattern implementation:&lt;/strong&gt; Using Python and boto3, implement Lazy Loading against a DynamoDB table with an ElastiCache Redis cluster. Log every cache hit and miss. Observe the cache hit rate improve as the same keys are read repeatedly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DynamoDB design exercise:&lt;/strong&gt; You're building a messaging app. Users send messages to other users. Design the DynamoDB table: what's your partition key? Sort key? What LSI or GSI would you add to support "show all messages I received, sorted by timestamp"?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Query vs Scan cost experiment:&lt;/strong&gt; Create a DynamoDB table with 1000 items. Run a Query for a specific partition key — note the consumed RCUs. Run a Scan with a filter returning the same single item — note the consumed RCUs. Compare. The Scan should show RCU consumption for all 1000 items, not just the one that matched.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GSI creation on existing table:&lt;/strong&gt; Create a DynamoDB table with partition key = &lt;code&gt;user_id&lt;/code&gt;. Add items. After creation, add a GSI with partition key = &lt;code&gt;city&lt;/code&gt;. Query by city and confirm it works without touching the base table's key structure.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;AWS Session 12 — Aurora, ElastiCache Patterns &amp;amp; DynamoDB | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AWS RDS &amp; ElastiCache — Managed Databases, Multi-AZ Failover &amp; In-Memory Caching</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Wed, 05 Aug 2026 13:06:28 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-rds-elastic-cache-managed-databases-multi-az-failover-in-memory-caching-5dce</link>
      <guid>https://dev.to/tejas_shinkar/aws-rds-elastic-cache-managed-databases-multi-az-failover-in-memory-caching-5dce</guid>
      <description>&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This session moves from networking into the data layer — how AWS manages databases for you, and how caching keeps applications fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Why Separate Compute from Database&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Structured vs Semi-Structured vs Unstructured Data&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;What RDS Actually Is Under the Hood&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;RDS Deployment Options — Single-AZ vs Multi-AZ Instance vs Multi-AZ Cluster&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Self-Managed DB vs RDS — What AWS Automates&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Multi-AZ Failover — Step by Step&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Why RDS Uses a DNS Endpoint, Not an IP&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Amazon Aurora&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Sharding — Horizontal Scaling&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Amazon ElastiCache&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Lab — Creating an RDS Database&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Interview Questions&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Assignment&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why Separate Compute from Database
&lt;/h2&gt;

&lt;p&gt;An application server (EC2, Lambda) is built to &lt;strong&gt;process&lt;/strong&gt; data quickly — run logic, transform data, respond to requests. A database is built to &lt;strong&gt;store&lt;/strong&gt; data reliably — persist it, keep it searchable, keep it available to multiple clients at once, and survive restarts.&lt;/p&gt;

&lt;p&gt;If you put both on the same machine, a crash takes down your entire system — your data and your processing logic disappear together. Keeping them separate means your application servers can scale up, scale down, or crash and restart, while your data stays safe and available on its own dedicated, purpose-built infrastructure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The core principle:&lt;/strong&gt; Compute is optimized for processing. A database is optimized for persistent, reliable, searchable, shared storage. Separating them lets each scale independently and lets your application survive server failures without losing data.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Structured vs Semi-Structured vs Unstructured Data
&lt;/h2&gt;

&lt;p&gt;Not all data looks the same, and AWS has a different storage service optimized for each type.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data Type&lt;/th&gt;
&lt;th&gt;Schema&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;AWS Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Structured&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fixed schema — rows and columns defined upfront&lt;/td&gt;
&lt;td&gt;Customer table: id, name, email, order_date&lt;/td&gt;
&lt;td&gt;Amazon RDS (relational)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Semi-structured&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Flexible schema — fields can vary per record&lt;/td&gt;
&lt;td&gt;JSON, XML — a user profile with optional fields&lt;/td&gt;
&lt;td&gt;Amazon DynamoDB (NoSQL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unstructured&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No predefined format at all&lt;/td&gt;
&lt;td&gt;Images, videos, PDFs, log files&lt;/td&gt;
&lt;td&gt;Amazon S3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why this distinction matters in real architecture:&lt;/strong&gt; A retail app might use RDS for orders (structured, needs strong consistency), DynamoDB for a shopping cart (semi-structured, needs speed and flexibility), and S3 for product images (unstructured, needs cheap durable storage). Picking the right store for each data type is a core system design skill.&lt;/p&gt;




&lt;h2&gt;
  
  
  What RDS Actually Is Under the Hood
&lt;/h2&gt;

&lt;p&gt;This mental model makes RDS click immediately: &lt;strong&gt;RDS is not a magical new thing — it's an EC2 instance with attached EBS storage, running a database engine, wrapped in an AWS-managed control plane.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Think of it this way: underneath, RDS is still a virtual machine with a disk, just like any EC2 setup you could build yourself. What AWS adds on top is the &lt;strong&gt;automation layer&lt;/strong&gt; — automatic provisioning, backups, patching, monitoring, scaling, and failover — so you never have to SSH in and manage the database server yourself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What you get with RDS that you'd have to build yourself with self-managed 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;Task&lt;/th&gt;
&lt;th&gt;Self-Managed on EC2&lt;/th&gt;
&lt;th&gt;Amazon RDS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Install DB engine&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Pre-configured, just choose the engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS/DB patching&lt;/td&gt;
&lt;td&gt;Manual, scheduled downtime&lt;/td&gt;
&lt;td&gt;Automated maintenance windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backups&lt;/td&gt;
&lt;td&gt;You script and schedule them&lt;/td&gt;
&lt;td&gt;Automated daily snapshots + point-in-time recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failover&lt;/td&gt;
&lt;td&gt;You build and test it yourself&lt;/td&gt;
&lt;td&gt;Automatic (Multi-AZ)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring&lt;/td&gt;
&lt;td&gt;You set up CloudWatch agents&lt;/td&gt;
&lt;td&gt;Built-in metrics out of the box&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling storage&lt;/td&gt;
&lt;td&gt;Manual resize + downtime&lt;/td&gt;
&lt;td&gt;Can auto-scale storage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  RDS Deployment Options
&lt;/h2&gt;

&lt;p&gt;RDS offers three deployment models, and this is one of the most tested concepts in the SAA-C03 exam. The image from class (RDS Console's deployment screen) shows this exact choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory trick:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-AZ → One copy 🏠&lt;/li&gt;
&lt;li&gt;Multi-AZ Instance → One primary + one standby 🏠🏠&lt;/li&gt;
&lt;li&gt;Multi-AZ DB Cluster → One writer + multiple readers 🏠🏠🏠&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Side-by-Side Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Single-AZ&lt;/th&gt;
&lt;th&gt;Multi-AZ DB Instance&lt;/th&gt;
&lt;th&gt;Multi-AZ DB Cluster&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Instances&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2 (1 primary + 1 standby)&lt;/td&gt;
&lt;td&gt;3 (1 writer + 2 readable standbys)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Uptime SLA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;99.5%&lt;/td&gt;
&lt;td&gt;99.95%&lt;/td&gt;
&lt;td&gt;99.95%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Automatic failover&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Standby readable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;❌ No (standby is idle, failover-only)&lt;/td&gt;
&lt;td&gt;✅ Yes (readers serve read traffic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Read scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redundancy across AZs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dev, test, non-critical workloads&lt;/td&gt;
&lt;td&gt;Production, HA required&lt;/td&gt;
&lt;td&gt;Production, HA + read-heavy workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lowest&lt;/td&gt;
&lt;td&gt;Medium (2x compute)&lt;/td&gt;
&lt;td&gt;Highest (3x compute)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Reading the deployment screenshot from class:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-AZ DB Cluster (3 instances):&lt;/strong&gt; Primary instance + SSD in AZ 1, two "Readable standby + SSD" instances in AZ 2 and AZ 3. There's a separate "Reader endpoint" that load-balances read queries across the standbys, while the "Write/read endpoint" always points to the primary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-AZ DB Instance (2 instances):&lt;/strong&gt; Primary in AZ 1, a standby in AZ 2 that has &lt;strong&gt;no endpoint of its own&lt;/strong&gt; — it exists purely as a failover target, not for serving traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-AZ (1 instance):&lt;/strong&gt; Just the primary, no redundancy at all. If AZ 1 has an issue, there's no automatic failover — you'd be restoring from backup.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert tip:&lt;/strong&gt; The key differentiator that trips people up — in a &lt;strong&gt;Multi-AZ Instance&lt;/strong&gt; deployment, the standby is NOT readable. You cannot send read queries to it; it exists purely for failover. Only a &lt;strong&gt;Multi-AZ DB Cluster&lt;/strong&gt; gives you readable standbys that also help with read scaling.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Self-Managed DB vs RDS — What AWS Automates
&lt;/h2&gt;

&lt;p&gt;On a self-managed database (say, PostgreSQL installed manually on an EC2 instance), a DBA is responsible for installing the database binaries, configuring database parameters (memory allocation, connection limits, query optimization settings), and managing how clients connect.&lt;/p&gt;

&lt;p&gt;RDS automates all three of these:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Self-Managed Task&lt;/th&gt;
&lt;th&gt;RDS Equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Manually install DB binaries&lt;/td&gt;
&lt;td&gt;Choose an engine (PostgreSQL, MySQL, etc.) — AWS manages the binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manually edit config files (&lt;code&gt;postgresql.conf&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Parameter Groups&lt;/strong&gt; — a managed collection of engine settings you can tune without SSH access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manage static connection strings, update on every failover&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;DNS Endpoint&lt;/strong&gt; — a stable hostname that AWS keeps pointed at the current primary&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Parameter Groups&lt;/strong&gt; deserve a specific callout — instead of editing a config file directly on the server, you modify settings through a Parameter Group in the RDS Console, and AWS applies them to the instance (sometimes requiring a reboot, depending on the parameter).&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-AZ Failover — Step by Step
&lt;/h2&gt;

&lt;p&gt;This is the mechanism that makes Multi-AZ deployments valuable — understanding exactly what happens during a failure builds real confidence for both interviews and production incidents.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The failover sequence:&lt;/strong&gt;&lt;br&gt;
Primary Crash → Standby Promoted → DNS Endpoint Updated (TTL = 5 seconds) → Application Resolves New IP → Reconnect → Database Available&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total time: roughly 60–120 seconds&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Breaking down each step:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Primary Crash&lt;/strong&gt; — the primary instance becomes unreachable (hardware failure, AZ outage, or a manual failover for maintenance)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standby Promoted&lt;/strong&gt; — AWS automatically promotes the standby replica in the other AZ to become the new primary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS Endpoint Updated&lt;/strong&gt; — the RDS DNS endpoint's record is updated to point to the new primary's IP address, with a TTL (Time To Live) of about 5 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Resolves New IP&lt;/strong&gt; — because the DNS TTL is so short, client applications quickly pick up the new IP on their next DNS lookup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconnect&lt;/strong&gt; — the application's connection pool reconnects using the newly resolved IP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Available&lt;/strong&gt; — normal operations resume&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire process typically completes within &lt;strong&gt;60 to 120 seconds&lt;/strong&gt; — no manual intervention required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why RDS Uses a DNS Endpoint, Not an IP
&lt;/h2&gt;

&lt;p&gt;This is a genuinely good interview question because it tests whether you understand &lt;em&gt;why&lt;/em&gt; a design choice was made, not just &lt;em&gt;what&lt;/em&gt; it is.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Q: Why does RDS use a DNS endpoint instead of exposing the database IP directly?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;During a Multi-AZ failover, the database's IP address changes — the standby gets promoted and it has a different IP than the old primary. If applications connected using a hardcoded IP, every failover would require someone to manually update every application's configuration and restart it — completely defeating the purpose of "automatic" failover.&lt;/p&gt;

&lt;p&gt;Instead, applications connect using a stable RDS DNS endpoint (something like &lt;code&gt;mydb.abc123xyz.ap-south-1.rds.amazonaws.com&lt;/code&gt;) that never changes. Behind the scenes, AWS updates what that DNS name resolves to. With a TTL of about 5 seconds, client applications re-resolve the DNS quickly and reconnect to the new primary automatically — with zero configuration changes needed on the application side.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is exactly the same principle used by Elastic IPs and Route 53 health checks elsewhere in AWS — decouple the stable identifier from the underlying resource so the underlying resource can change without breaking anything upstream.&lt;/p&gt;




&lt;h2&gt;
  
  
  Amazon Aurora
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Amazon Aurora&lt;/strong&gt; is AWS's own cloud-native relational database, compatible with MySQL and PostgreSQL (meaning your existing MySQL/PostgreSQL drivers and tools work without changes) but built with fundamentally different internal architecture for much higher performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What makes Aurora different from standard RDS engines:&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;Feature&lt;/th&gt;
&lt;th&gt;Standard RDS (MySQL/PostgreSQL)&lt;/th&gt;
&lt;th&gt;Aurora&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compute + Storage&lt;/td&gt;
&lt;td&gt;Coupled together&lt;/td&gt;
&lt;td&gt;Separated — storage scales independently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replication&lt;/td&gt;
&lt;td&gt;You configure it&lt;/td&gt;
&lt;td&gt;Automatic, across 3 AZs, 6 copies of data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage auto-scaling&lt;/td&gt;
&lt;td&gt;Manual/limited&lt;/td&gt;
&lt;td&gt;Automatic, up to 128 TB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failover speed&lt;/td&gt;
&lt;td&gt;~60-120 seconds&lt;/td&gt;
&lt;td&gt;Faster (typically under 30 seconds)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;Significantly higher throughput&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Lower&lt;/td&gt;
&lt;td&gt;Higher (premium for the performance)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The "separates compute from storage" concept, explained simply:&lt;/strong&gt; In a standard database, if you need more storage, you often need to resize the whole instance. In Aurora, the storage layer is a separate, distributed system that grows automatically as your data grows — the compute instance (which runs the actual query engine) can scale independently. This is why Aurora can offer both faster failover (the storage layer already has 6 copies ready) and larger scale (storage isn't tied to a single disk).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert tip:&lt;/strong&gt; When a scenario mentions "MySQL-compatible," "PostgreSQL-compatible," "high performance," and "automatic storage scaling" together, the answer is almost always Aurora, not standard RDS.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Sharding — Horizontal Scaling for Databases
&lt;/h2&gt;

&lt;p&gt;As a database grows, at some point one server (even a very large one) can't handle the write load or storage anymore. &lt;strong&gt;Sharding&lt;/strong&gt; is the technique for scaling out — instead of one giant database, you split it into multiple smaller databases called &lt;strong&gt;shards&lt;/strong&gt;, each holding a portion of the data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; A large database is partitioned into multiple smaller databases (shards) based on some key — for example, splitting users A-M into Shard 1 and users N-Z into Shard 2. Each shard stores only its subset of the data. The application (or a routing layer) determines which shard to query based on the data being requested, so both storage and write load get distributed across multiple database servers instead of one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Vertical scaling (bigger instance) has a ceiling — eventually you run out of bigger instance types. Sharding is how systems scale writes and storage beyond what any single database server could handle, at the cost of added application complexity (your app needs to know which shard to query).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Where this connects:&lt;/strong&gt; This is conceptually the same "horizontal vs vertical scaling" idea from the ELB/ASG session — just applied to databases instead of compute. Vertical = bigger box. Horizontal (sharding) = more boxes, each handling a slice of the problem.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Amazon ElastiCache
&lt;/h2&gt;

&lt;p&gt;Even a well-tuned database has a limit to how many reads it can serve per second, and every query — even a fast one — has some latency. &lt;strong&gt;ElastiCache&lt;/strong&gt; is AWS's fully managed in-memory caching service — it stores frequently accessed data in RAM, which is dramatically faster than querying a database on disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How caching works in an application:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Application needs data → checks the cache first → if the data is there (&lt;strong&gt;cache hit&lt;/strong&gt;), return it instantly from RAM → if the data isn't there (&lt;strong&gt;cache miss&lt;/strong&gt;), query the database, get the result, store it in the cache for next time, then return it to the caller.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means the &lt;em&gt;first&lt;/em&gt; request for a piece of data is a normal database query, but every subsequent request for the same data is served from memory — orders of magnitude faster, and it takes load off the database entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three Caching Engines
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Persistence&lt;/th&gt;
&lt;th&gt;Replication&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Valkey&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;Open-source fork of Redis (community-driven, actively used going forward)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redis OSS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;Long-standing standard, still widely used&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memcached&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;Simpler, but largely fallen out of favor — no persistence or replication means data loss on restart&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Why Valkey exists:&lt;/strong&gt; After a licensing change to Redis, the open-source community forked the last fully open-source version of Redis into a new project called Valkey — it's Linux Foundation-backed and functionally very similar to Redis. AWS supports it as a first-class ElastiCache engine going forward.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Memcached is essentially legacy at this point&lt;/strong&gt; — no persistence means a restart wipes your cache entirely, and no replication means no high availability. Almost all new projects choose Redis OSS or Valkey.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Lab — Creating an RDS Database (Progress So Far)
&lt;/h2&gt;

&lt;p&gt;This is the practical work completed so far in class — continuing in a future session to connect it to EC2 and build the full application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Create a DB Subnet Group&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A DB Subnet Group tells RDS which subnets (across which AZs) it's allowed to place database instances in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Go to RDS Console → Subnet Groups → Create DB Subnet Group. Choose your existing VPC. Select the &lt;strong&gt;private subnets&lt;/strong&gt; across each of the 3 Availability Zones — databases should never sit in a public subnet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Create the Database&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;RDS Console → Create database → chose the &lt;strong&gt;PostgreSQL&lt;/strong&gt; engine → selected the engine version → set a DB instance identifier (a name for this specific database instance) → set the master username → chose self-managed password authentication → selected the DB instance class (Burstable class, e.g., &lt;code&gt;db.t3.micro&lt;/code&gt;) → chose storage type and allocated storage size → left storage auto-scaling disabled for now.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Connectivity&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Chose "Connect to an EC2 compute resource" during setup → this triggered creating an EC2 instance to connect to the database → created an Internet Gateway and attached it to the VPC → configured subnet routes → configured connectivity, including an additional VPC security group (&lt;code&gt;launch-wizard-2&lt;/code&gt;) → disabled Performance Insights for now (cost-saving on a lab account) → DB Subnet Group was auto-selected based on the earlier subnet group creation → clicked Create Database.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What's left for the next session:&lt;/strong&gt; actually connecting from the EC2 instance to the RDS database, verifying connectivity, and building out the application layer on top.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why Separate Compute &amp;amp; DB&lt;/strong&gt;&lt;br&gt;
Compute = optimized for processing. Database = optimized for persistent, reliable, shared storage. Separation lets each scale independently and survive failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Types&lt;/strong&gt;&lt;br&gt;
Structured (fixed schema) → RDS. Semi-structured (flexible schema, JSON/XML) → DynamoDB. Unstructured (no format) → S3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What RDS Really Is&lt;/strong&gt;&lt;br&gt;
EC2 + EBS + DB engine + AWS-managed automation layer (provisioning, backups, patching, monitoring, scaling, failover).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three Deployment Options&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-AZ 🏠 → one instance, no failover, 99.5% SLA&lt;/li&gt;
&lt;li&gt;Multi-AZ Instance 🏠🏠 → primary + non-readable standby, automatic failover, 99.95% SLA&lt;/li&gt;
&lt;li&gt;Multi-AZ Cluster 🏠🏠🏠 → one writer + 2 readable standbys, failover + read scaling, 99.95% SLA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Failover Flow&lt;/strong&gt;&lt;br&gt;
Primary Crash → Standby Promoted → DNS Updated (TTL 5s) → App Resolves New IP → Reconnect → Available. Total: 60-120 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why DNS Endpoint, Not IP&lt;/strong&gt;&lt;br&gt;
IP changes on failover. DNS endpoint stays constant; AWS updates what it resolves to. Apps never need reconfiguration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aurora&lt;/strong&gt;&lt;br&gt;
MySQL/PostgreSQL-compatible, but compute and storage are separated. 3 AZs, 6 copies of data automatically. Auto-scales storage to 128 TB. Faster failover, higher performance than standard RDS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharding&lt;/strong&gt;&lt;br&gt;
Horizontal scaling for databases — split one large DB into multiple shards, each holding a subset of data, distributing storage and write load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ElastiCache&lt;/strong&gt;&lt;br&gt;
In-memory caching. Cache hit = instant from RAM. Cache miss = query DB, store result in cache, return. Engines: Valkey (Redis fork, active), Redis OSS (standard), Memcached (legacy, no persistence/replication).&lt;/p&gt;




&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: Why does AWS keep compute and database storage separate instead of running everything on one server?&lt;/strong&gt;&lt;br&gt;
Compute is optimized for processing logic quickly, while databases are optimized for persistent, reliable, and shared storage. Keeping them separate allows application servers to scale independently, restart, or fail without losing data, since the database lives on its own dedicated, durable infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What is the difference between a Multi-AZ DB Instance and a Multi-AZ DB Cluster?&lt;/strong&gt;&lt;br&gt;
A Multi-AZ DB Instance has one primary and one standby — the standby is not readable and exists purely for automatic failover. A Multi-AZ DB Cluster has one writer and two readable standbys across different AZs, providing both automatic failover and read scaling, since the standbys can serve read traffic through a separate reader endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: Walk through what happens during an RDS Multi-AZ failover.&lt;/strong&gt;&lt;br&gt;
The primary instance becomes unavailable, so AWS automatically promotes the standby in another AZ to primary. The RDS DNS endpoint's record is updated to point to the new primary's IP, with a TTL of about 5 seconds. Applications re-resolve the DNS quickly and reconnect using the new IP — no manual configuration changes needed. The whole process typically takes 60 to 120 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: Why does RDS use a DNS endpoint instead of a static IP address?&lt;/strong&gt;&lt;br&gt;
Because the underlying IP address changes during failover — the standby that gets promoted has a different IP than the old primary. If applications connected via a hardcoded IP, every failover would require manual reconfiguration. The DNS endpoint stays constant while AWS updates what it resolves to, with a short TTL so clients pick up the change within seconds automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: What makes Amazon Aurora different from standard RDS engines like MySQL or PostgreSQL on RDS?&lt;/strong&gt;&lt;br&gt;
Aurora separates compute from storage — the storage layer is a distributed system that automatically replicates across 3 AZs with 6 copies of data and scales up to 128 TB automatically. This architecture also enables faster failover and significantly higher performance compared to standard RDS engines, while remaining compatible with existing MySQL and PostgreSQL tooling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: What is sharding and when would you use it?&lt;/strong&gt;&lt;br&gt;
Sharding is a horizontal scaling technique where a large database is split into multiple smaller databases (shards), each storing a subset of the data. It's used when a single database instance can no longer handle the write load or storage requirements — even with vertical scaling — because it distributes both storage and write traffic across multiple servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: How does ElastiCache improve application performance?&lt;/strong&gt;&lt;br&gt;
Applications check the cache before querying the database. On a cache hit, data is returned instantly from RAM, which is dramatically faster than a database query. On a cache miss, the application queries the database, then stores the result in the cache for future requests. This reduces database load and significantly improves response times for frequently accessed data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q8: Why is Memcached rarely chosen for new projects compared to Redis OSS or Valkey?&lt;/strong&gt;&lt;br&gt;
Memcached has no persistence (data is lost on restart) and no replication (no high availability). Redis OSS and Valkey both support persistence and replication, making them suitable for production caching layers where data durability and availability matter — which is why most new projects choose one of those two instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔬 Assignment
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create a web server on EC2, host a simple application on it, connect it to the RDS database created in this session's lab, build a basic frontend, and create a form that makes an entry into the database.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;AWS Session 11 — RDS &amp;amp; ElastiCache | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Practical RDS-to-EC2 connection and remaining lab steps to be completed in a follow-up practical session.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AWS VPC Extended — Route Tables, Blackhole Routes, Flow Logs, VPC Endpoints &amp; Transit Gateway</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Fri, 31 Jul 2026 09:11:31 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-vpc-extended-route-tables-blackhole-routes-flow-logs-vpc-endpoints-transit-gateway-og4</link>
      <guid>https://dev.to/tejas_shinkar/aws-vpc-extended-route-tables-blackhole-routes-flow-logs-vpc-endpoints-transit-gateway-og4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This builds directly on the previous VPC session — going deeper into how routing actually decides where traffic goes, and how VPCs connect privately to AWS services and to each other.&lt;/p&gt;

&lt;p&gt;This is dense, interconnected material — the goal here isn't to memorize every term on the first pass, but to build a reference to come back to once the full picture settles.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Route Table — The Traffic Director&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Destination vs Target&lt;/td&gt;
&lt;td&gt;Concept + Analogy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Longest Prefix Match — Deep Dive with Scenarios&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Route Propagation&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Static Routes vs Propagated Routes&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Blackhole Routes&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Managed Prefix Lists&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Edge Ingress Route Table Association&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Troubleshooting Checklist — Traffic Not Reaching EC2&lt;/td&gt;
&lt;td&gt;Interview + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;VPC Flow Logs&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;VPC Endpoints — Gateway vs Interface&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;VPC Endpoint Policy&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Transit Gateway&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Interview Questions&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Practice Scenarios&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Route Table — The Traffic Director
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Route Table&lt;/strong&gt; is the rulebook that decides where every packet goes next, based purely on its destination IP address. Every subnet is associated with exactly one route table, and every route table contains one or more rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; A route table is GPS navigation for your network. You type in a destination (IP address), and the GPS tells you which road (target) to take. If multiple roads could technically get you there, GPS picks the most direct, specific route — not the broadest general highway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Basic structure:&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;Destination&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10.0.0.0/16&lt;/td&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.0.0.0/0&lt;/td&gt;
&lt;td&gt;nat-0abc123&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Destination vs Target — The Core Distinction
&lt;/h2&gt;

&lt;p&gt;This is the single most important mental model for understanding routing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Destination&lt;/strong&gt; = WHERE you're going (the IP range you're trying to reach)&lt;br&gt;
&lt;strong&gt;Target&lt;/strong&gt; = HOW you get there (which gateway/component forwards it)&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;Destination: Mumbai → Target: Mumbai-Pune Expressway&lt;/li&gt;
&lt;li&gt;Destination: Pune → Target: Local roads (already there)&lt;/li&gt;
&lt;li&gt;Destination: Anywhere else → Target: National Highway (catch-all)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In AWS terms:&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;Destination&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10.200.0.0/16&lt;/td&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;td&gt;VPC's own network — no gateway needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.0.0.0/0&lt;/td&gt;
&lt;td&gt;igw-0a1b2c3d&lt;/td&gt;
&lt;td&gt;Anywhere else — go through Internet Gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;172.31.0.0/16&lt;/td&gt;
&lt;td&gt;pcx-0x1y2z3&lt;/td&gt;
&lt;td&gt;Peered VPC — go through Peering Connection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.0.0/16&lt;/td&gt;
&lt;td&gt;vgw-0p1q2r3&lt;/td&gt;
&lt;td&gt;On-premise network — go through VPN Gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Possible targets in a route table:&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;Target&lt;/th&gt;
&lt;th&gt;What it connects to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;local&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Always present — traffic within the VPC's own CIDR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internet Gateway (&lt;code&gt;igw-&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Public internet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAT Gateway (&lt;code&gt;nat-&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Internet, for private subnet outbound only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPC Peering Connection (&lt;code&gt;pcx-&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Another VPC (peered)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Virtual Private Gateway (&lt;code&gt;vgw-&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;On-premise network via VPN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transit Gateway (&lt;code&gt;tgw-&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Multiple VPCs/networks via central hub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPC Endpoint&lt;/td&gt;
&lt;td&gt;Specific AWS service, privately&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Longest Prefix Match — Deep Dive with Scenarios
&lt;/h2&gt;

&lt;p&gt;This is the rule AWS uses whenever a destination IP could match &lt;strong&gt;more than one route&lt;/strong&gt; in the table. AWS always picks the route with the &lt;strong&gt;most specific (longest) prefix&lt;/strong&gt; — not the broadest one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Core rule:&lt;/strong&gt; A more specific CIDR (larger number after the &lt;code&gt;/&lt;/code&gt;) always wins over a broader CIDR (smaller number). &lt;code&gt;/32&lt;/code&gt; beats &lt;code&gt;/24&lt;/code&gt; beats &lt;code&gt;/16&lt;/code&gt; beats &lt;code&gt;/0&lt;/code&gt; (most specific → least specific catch-all).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Scenario Comparisons:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario 1 — Basic internet vs local&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;Route Table&lt;/th&gt;
&lt;th&gt;Traffic to 10.200.0.55&lt;/th&gt;
&lt;th&gt;Traffic to 8.8.8.8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;10.200.0.0/16 → Local&lt;/code&gt;&lt;br&gt;&lt;code&gt;0.0.0.0/0 → IGW&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Matches &lt;code&gt;/16&lt;/code&gt; (more specific)&lt;br&gt;→ &lt;strong&gt;Local&lt;/strong&gt; ✅&lt;/td&gt;
&lt;td&gt;Doesn't match &lt;code&gt;/16&lt;/code&gt;&lt;br&gt;Matches &lt;code&gt;/0&lt;/code&gt; (catch-all)&lt;br&gt;→ &lt;strong&gt;IGW&lt;/strong&gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Because 8.8.8.8 doesn't fall within the VPC's CIDR, the only matching route is the default &lt;code&gt;0.0.0.0/0&lt;/code&gt;, so it goes to the Internet Gateway.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Scenario 2 — Peered VPC with overlapping catch-all&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;Route Table&lt;/th&gt;
&lt;th&gt;Traffic to 172.31.5.10&lt;/th&gt;
&lt;th&gt;Traffic to 142.250.1.1 (Google)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;10.200.0.0/16 → Local&lt;/code&gt;&lt;br&gt;&lt;code&gt;172.31.0.0/16 → pcx-xyz&lt;/code&gt;&lt;br&gt;&lt;code&gt;0.0.0.0/0 → IGW&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Matches &lt;code&gt;/16&lt;/code&gt; peering route&lt;br&gt;→ &lt;strong&gt;Peering Connection&lt;/strong&gt; ✅&lt;/td&gt;
&lt;td&gt;No specific match&lt;br&gt;Falls to &lt;code&gt;/0&lt;/code&gt; catch-all&lt;br&gt;→ &lt;strong&gt;IGW&lt;/strong&gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;The peering route is the only specific match for the peered VPC's range — it wins over the general internet catch-all route.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Scenario 3 — Specific /24 override within a /16&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;Route Table&lt;/th&gt;
&lt;th&gt;Traffic to 10.200.5.20&lt;/th&gt;
&lt;th&gt;Traffic to 10.200.1.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;10.200.0.0/16 → Local&lt;/code&gt;&lt;br&gt;&lt;code&gt;10.200.5.0/24 → tgw-abc&lt;/code&gt;&lt;br&gt;&lt;code&gt;0.0.0.0/0 → IGW&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Matches BOTH &lt;code&gt;/16&lt;/code&gt; and &lt;code&gt;/24&lt;/code&gt;&lt;br&gt;&lt;code&gt;/24&lt;/code&gt; is more specific → &lt;strong&gt;Transit Gateway&lt;/strong&gt; ✅&lt;/td&gt;
&lt;td&gt;Only matches &lt;code&gt;/16&lt;/code&gt;&lt;br&gt;→ &lt;strong&gt;Local&lt;/strong&gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;This is the key exam trap: even though &lt;code&gt;10.200.5.20&lt;/code&gt; falls inside the broader &lt;code&gt;/16&lt;/code&gt; local range, the more specific &lt;code&gt;/24&lt;/code&gt; route to the Transit Gateway wins. AWS always prefers the longest (most specific) prefix, regardless of which route was added first.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Scenario 4 — VPN + Internet coexisting&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;Route Table&lt;/th&gt;
&lt;th&gt;Traffic to 192.168.10.5 (on-prem)&lt;/th&gt;
&lt;th&gt;Traffic to Facebook.com&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;10.200.0.0/16 → Local&lt;/code&gt;&lt;br&gt;&lt;code&gt;192.168.0.0/16 → vgw-onprem&lt;/code&gt;&lt;br&gt;&lt;code&gt;0.0.0.0/0 → IGW&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Matches &lt;code&gt;/16&lt;/code&gt; VPN route&lt;br&gt;→ &lt;strong&gt;VPN Gateway&lt;/strong&gt; ✅&lt;/td&gt;
&lt;td&gt;No specific match&lt;br&gt;→ &lt;strong&gt;IGW&lt;/strong&gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview trap to remember:&lt;/strong&gt; Longest Prefix Match is about &lt;strong&gt;specificity of the CIDR&lt;/strong&gt;, not the order routes were added, not alphabetical order, not which target "seems more important." Just count the prefix length — bigger number after the &lt;code&gt;/&lt;/code&gt; always wins.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Route Propagation
&lt;/h2&gt;

&lt;p&gt;Static routes are ones you manually type into a route table. &lt;strong&gt;Propagated routes&lt;/strong&gt; are learned automatically — AWS discovers them from a dynamic routing source and adds them to your route table without manual entry.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;On-Premise Network → (VPN or Direct Connect) → Virtual Private Gateway / Transit Gateway → (BGP advertises routes) → AWS learns the on-prem CIDR ranges automatically → Route Table (with Route Propagation enabled) → Routes to on-premise networks appear automatically, no manual entry, updates as the on-prem network changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Without propagation, every time your on-premise network team adds a new subnet, someone would have to manually update every AWS route table. With propagation enabled, AWS learns the change automatically via BGP.&lt;/p&gt;




&lt;h2&gt;
  
  
  Static Routes vs Propagated Routes — Conflict Resolution
&lt;/h2&gt;

&lt;p&gt;What happens if a static route and a propagated route both match the same destination with the &lt;strong&gt;same prefix length&lt;/strong&gt;?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; Longest Prefix Match is applied first. If multiple routes have the same prefix length, the static route wins over the propagated route.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Example:&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;Route Type&lt;/th&gt;
&lt;th&gt;Destination&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Static (you typed this)&lt;/td&gt;
&lt;td&gt;10.50.0.0/16&lt;/td&gt;
&lt;td&gt;pcx-manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Propagated (learned via BGP)&lt;/td&gt;
&lt;td&gt;10.50.0.0/16&lt;/td&gt;
&lt;td&gt;vgw-learned&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same prefix length (&lt;code&gt;/16&lt;/code&gt;) → the &lt;strong&gt;static route wins&lt;/strong&gt;. Traffic to &lt;code&gt;10.50.5.5&lt;/code&gt; goes through &lt;code&gt;pcx-manual&lt;/code&gt;, not &lt;code&gt;vgw-learned&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This gives you an override mechanism — if AWS learns a route via propagation that conflicts with what you want, you can add a static route with the same prefix to take priority.&lt;/p&gt;




&lt;h2&gt;
  
  
  Blackhole Routes
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Blackhole Route&lt;/strong&gt; is a route that still exists in the table, but its target no longer exists or is unavailable. Traffic matching this route gets silently dropped — packets vanish into nowhere, hence "blackhole."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most common cause:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Route table has &lt;code&gt;0.0.0.0/0 → nat-0abc123&lt;/code&gt;. Someone deletes the NAT Gateway (&lt;code&gt;nat-0abc123&lt;/code&gt;). The route table &lt;em&gt;still&lt;/em&gt; has &lt;code&gt;0.0.0.0/0 → nat-0abc123&lt;/code&gt; — now shown as &lt;strong&gt;Blackhole&lt;/strong&gt;. Any traffic matching &lt;code&gt;0.0.0.0/0&lt;/code&gt; is dropped silently, with no error sent back to the sender.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; It's like your GPS still showing directions to a building that was demolished last week. The road exists, the GPS still routes you there, but there's nothing at the destination — you just vanish into a void.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to spot it:&lt;/strong&gt; In the AWS Console, Route Tables show a &lt;strong&gt;"Blackhole"&lt;/strong&gt; status directly next to the affected route — AWS flags it visually so you don't have to guess.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common triggers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NAT Gateway deleted while route table still references it&lt;/li&gt;
&lt;li&gt;VPC Peering connection deleted/rejected while routes still point to it&lt;/li&gt;
&lt;li&gt;VPN connection terminated while VGW routes remain&lt;/li&gt;
&lt;li&gt;Transit Gateway attachment removed&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Classic interview question:&lt;/strong&gt; &lt;em&gt;"Your EC2 in a private subnet suddenly cannot access the internet, but it was working yesterday. What would you check?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Answer — the troubleshooting sequence:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is the NAT Gateway still available? (check EC2 → NAT Gateways console)&lt;/li&gt;
&lt;li&gt;Does the route table still point to the correct NAT Gateway ID?&lt;/li&gt;
&lt;li&gt;Is the route marked "Blackhole"? (visible directly in Console)&lt;/li&gt;
&lt;li&gt;Are Security Groups and NACLs still allowing the traffic?&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Managed Prefix Lists
&lt;/h2&gt;

&lt;p&gt;Imagine you have 20 Security Groups and 10 Route Tables, all referencing the same set of 5 office IP ranges. If those office IPs change, you'd have to update 30 different places manually.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Managed Prefix List&lt;/strong&gt; solves this — it's a &lt;strong&gt;named, reusable group of CIDR blocks&lt;/strong&gt; you can reference in Security Groups and Route Tables. Update the list once, and every place referencing it gets the update automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example — Prefix List "company-offices":&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;Office&lt;/th&gt;
&lt;th&gt;CIDR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mumbai&lt;/td&gt;
&lt;td&gt;10.1.0.0/24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bangalore&lt;/td&gt;
&lt;td&gt;10.2.0.0/24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pune&lt;/td&gt;
&lt;td&gt;10.3.0.0/24&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You'd write a Security Group rule as "Allow SSH from &lt;code&gt;pl-company-offices&lt;/code&gt;" instead of listing all three CIDRs individually. Add a new office IP → update the Prefix List once → every Security Group and Route Table referencing it updates automatically. ✅&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS also provides pre-built Managed Prefix Lists&lt;/strong&gt; for its own services — e.g., a prefix list containing all CloudFront IP ranges, so you don't have to track them manually as AWS's infrastructure changes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;DevOps relevance:&lt;/strong&gt; Managed Prefix Lists are a genuine time-saver in infrastructure-as-code — instead of hardcoding IP ranges across dozens of Terraform resources, reference one prefix list ARN.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Edge Ingress Route Table Association
&lt;/h2&gt;

&lt;p&gt;This is an advanced feature — most engineers won't touch it often, but it's worth understanding conceptually.&lt;/p&gt;

&lt;p&gt;Normally, route tables are associated with &lt;strong&gt;subnets&lt;/strong&gt;. Edge Ingress lets you associate a route table with an &lt;strong&gt;Internet Gateway or Virtual Private Gateway&lt;/strong&gt; itself — meaning you can intercept and redirect traffic the moment it enters your VPC, before it even reaches a subnet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Without Edge Ingress:&lt;/strong&gt; Internet → IGW → directly to subnet → EC2&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Edge Ingress:&lt;/strong&gt; Internet → IGW → Edge Route Table → redirected through a Firewall/Inspection Appliance → then to subnet → EC2&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Use case:&lt;/strong&gt; Route all inbound internet traffic through a third-party firewall or intrusion detection appliance before it reaches your actual application servers — a centralized security inspection point at the very edge of your VPC.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert-level awareness only:&lt;/strong&gt; You're unlikely to configure this often in early-career roles, but knowing it exists (and what problem it solves — centralized traffic inspection at the VPC edge) is enough for interviews and certification.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Troubleshooting Checklist — Traffic Not Reaching EC2
&lt;/h2&gt;

&lt;p&gt;This is the master checklist that ties everything from both VPC sessions together. When traffic isn't reaching an EC2 instance, work through this &lt;strong&gt;in order&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Route Table Association&lt;/strong&gt; — Is the correct route table associated with this subnet? (or with the IGW, in edge ingress scenarios)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Longest Prefix Match&lt;/strong&gt; — Is AWS selecting the route you expect it to select? Check for a more specific route accidentally overriding your intended path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target Status&lt;/strong&gt; — Is the target still valid? Is the route marked "Blackhole"? (target deleted/unavailable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Address Translation Path&lt;/strong&gt; — Via Internet Gateway: does the EC2 have a Public IP or Elastic IP? Via NAT Gateway: is the NAT Gateway available, and is the route pointing to it correctly?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Layer&lt;/strong&gt; — Are Security Groups allowing the traffic? (stateful) Are NACLs allowing the traffic? (stateless — check BOTH inbound AND outbound, including ephemeral ports 1024-65535 for return traffic)&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 This exact sequence — Route Table → Prefix Match → Target Status → Address Translation → Security — is the professional troubleshooting flow used in real production incidents. Memorizing this order (not just the individual pieces) is what makes you effective in an actual outage, not just in an exam.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  VPC Flow Logs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;VPC Flow Logs&lt;/strong&gt; capture metadata about IP traffic flowing to and from network interfaces in your VPC. This is your &lt;strong&gt;forensic tool&lt;/strong&gt; — when something goes wrong (or when security needs to investigate), Flow Logs tell you exactly what traffic happened, where, and whether it was allowed or blocked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Flow Logs capture (metadata, NOT packet content):&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;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source IP&lt;/td&gt;
&lt;td&gt;Who sent it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination IP&lt;/td&gt;
&lt;td&gt;Who it was sent to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source Port&lt;/td&gt;
&lt;td&gt;Which port on sender&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination Port&lt;/td&gt;
&lt;td&gt;Which port on receiver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocol&lt;/td&gt;
&lt;td&gt;TCP, UDP, ICMP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Packets &amp;amp; Bytes&lt;/td&gt;
&lt;td&gt;Volume of traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action&lt;/td&gt;
&lt;td&gt;ACCEPT or REJECT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;td&gt;When it happened&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Flow Logs capture &lt;strong&gt;metadata only&lt;/strong&gt; — not the actual content/payload of the traffic. You'll know that 10.200.1.5 sent 500 bytes to 8.8.8.8 on port 443 and it was accepted — but not what was inside that HTTPS request.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Where Flow Logs can be published:&lt;/strong&gt; VPC / Subnet / ENI → sends a Flow Log to either &lt;strong&gt;CloudWatch Logs&lt;/strong&gt; (real-time monitoring, alarms, dashboards) or an &lt;strong&gt;S3 Bucket&lt;/strong&gt; (long-term storage, cost-effective, Athena queries).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup sequence (what was done in class):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a Log Group in CloudWatch → &lt;code&gt;CloudWatch → Log groups → Create log group&lt;/code&gt; → name it &lt;code&gt;/vpc/flowlogs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create the Flow Log → &lt;code&gt;VPC Console → Your VPC → Flow Logs → Create flow log&lt;/code&gt; → set filter (All / Accept / Reject) → destination: CloudWatch Logs → select the log group → attach an IAM role with permission to write logs&lt;/li&gt;
&lt;li&gt;Watch logs flow in → &lt;code&gt;CloudWatch → Log groups → /vpc/flowlogs → Log streams&lt;/code&gt; → real traffic metadata appears&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Sample Flow Log entry&lt;/strong&gt; (this one &lt;em&gt;is&lt;/em&gt; real syntax, kept as code):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 123456789010 eni-1235b8ca 172.31.16.139 172.31.16.21 20641 22 6 20 4249 1418530010 1418530070 ACCEPT OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking this down: version, account ID, ENI ID, source IP, dest IP, source port, dest port, protocol (6=TCP), packets, bytes, start time, end time, action (ACCEPT), log status.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Security use case:&lt;/strong&gt; If you suspect unauthorized access, filter Flow Logs for &lt;code&gt;REJECT&lt;/code&gt; actions from unfamiliar IPs — this shows every blocked connection attempt, which is exactly what a security investigation needs. This is one of the most useful forensic tools in AWS networking.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  VPC Endpoints — Private Connectivity to AWS Services
&lt;/h2&gt;

&lt;p&gt;Normally, when a private EC2 instance wants to talk to S3, DynamoDB, or another AWS service, that traffic would need to go through a NAT Gateway → Internet Gateway → out to the public internet → to the AWS service's public endpoint → back.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;VPC Endpoint&lt;/strong&gt; creates a private connection directly from your VPC to the AWS service — traffic never leaves the AWS network, never touches the public internet, no NAT Gateway or Internet Gateway required.&lt;/p&gt;

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

&lt;p&gt;🏠 Private EC2 = Your house&lt;br&gt;
🏢 AWS Service (S3, SSM, CloudWatch) = Office&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Without a VPC Endpoint:&lt;/strong&gt; House → Public Road (internet, via NAT + IGW) → Office. Longer path, exposed to the public internet, costs a NAT data processing fee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With a VPC Endpoint:&lt;/strong&gt; House → Private Tunnel (stays inside AWS network) → Office. Shorter, more secure, often cheaper — no NAT processing charge.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Two Types of VPC Endpoints
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Gateway Endpoint&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only supports &lt;strong&gt;S3&lt;/strong&gt; and &lt;strong&gt;DynamoDB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Works by adding a route table entry — a target pointing to the endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt; — no hourly charge&lt;/li&gt;
&lt;li&gt;Cannot be accessed from on-premises networks (VPN/Direct Connect)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Route table example: destination &lt;code&gt;pl-xxxxxx&lt;/code&gt; (the S3 prefix list) → target &lt;code&gt;vpce-gateway-xxxxx&lt;/code&gt;. A private EC2 making an S3 request matches the S3 prefix list route, goes through the Gateway Endpoint, and reaches S3 — no internet involved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interface Endpoint&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supports most other AWS services (SSM, CloudWatch, SNS, SQS, KMS, Secrets Manager, etc.)&lt;/li&gt;
&lt;li&gt;Works by creating an &lt;strong&gt;ENI with a private IP&lt;/strong&gt; inside your subnet&lt;/li&gt;
&lt;li&gt;Has an hourly charge + data processing charge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can&lt;/strong&gt; be accessed from on-premises networks via VPN/Direct Connect&lt;/li&gt;
&lt;li&gt;Optionally enables &lt;strong&gt;Private DNS&lt;/strong&gt; — makes the standard AWS service DNS name resolve to the private endpoint automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: an Interface Endpoint for SSM creates an ENI at &lt;code&gt;10.200.1.100&lt;/code&gt; in your private subnet. With Private DNS enabled, &lt;code&gt;ssm.ap-south-1.amazonaws.com&lt;/code&gt; resolves to &lt;code&gt;10.200.1.100&lt;/code&gt; instead of the public AWS IP. So when your EC2 calls the standard SSM endpoint name, DNS quietly resolves it privately, and traffic stays inside the VPC.&lt;/p&gt;
&lt;h3&gt;
  
  
  Gateway vs Interface — Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Gateway Endpoint&lt;/th&gt;
&lt;th&gt;Interface Endpoint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Supported services&lt;/td&gt;
&lt;td&gt;S3, DynamoDB only&lt;/td&gt;
&lt;td&gt;Most other AWS services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How it works&lt;/td&gt;
&lt;td&gt;Route table entry&lt;/td&gt;
&lt;td&gt;ENI with private IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Hourly + data charge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-premise access&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes (via VPN/DX)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Private DNS optional&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  The Interview Trap — Gateway vs Interface for S3
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interviewer:&lt;/strong&gt; &lt;em&gt;"You have both an S3 Gateway Endpoint AND an S3 Interface Endpoint, and Private DNS is enabled on the Interface Endpoint. Which one actually gets used?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; The &lt;strong&gt;Interface Endpoint&lt;/strong&gt; is used. Enabling Private DNS overrides the default S3 DNS resolution — so when your application calls the standard S3 endpoint name (&lt;code&gt;s3.ap-south-1.amazonaws.com&lt;/code&gt;), DNS resolves it to the Interface Endpoint's private IP instead of routing through the Gateway Endpoint via the route table. The Gateway Endpoint sits unused in this scenario even though it's still technically active.&lt;/p&gt;

&lt;p&gt;This is a genuinely tricky, DNS-resolution-based gotcha — worth remembering exactly because it's counterintuitive.&lt;/p&gt;


&lt;h2&gt;
  
  
  VPC Endpoint Policy
&lt;/h2&gt;

&lt;p&gt;Just like S3 has bucket policies and IAM has permission policies, a &lt;strong&gt;VPC Endpoint Policy&lt;/strong&gt; is a resource policy attached directly to the endpoint itself — controlling exactly which AWS resources and API actions can flow through that specific endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="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;"Principal"&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="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="s2"&gt;"arn:aws:s3:::company-approved-bucket/*"&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 policy says: &lt;em&gt;"Through this specific VPC Endpoint, only GetObject on this one specific bucket is allowed — nothing else, even if IAM would otherwise permit it."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; It's an additional restriction layer, not a replacement. A request must pass IAM policy AND the VPC Endpoint policy AND (if applicable) the bucket policy. This lets you say: "Even if someone has full S3 permissions via IAM, when they go through THIS endpoint, they can only touch THIS bucket."&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Production use case:&lt;/strong&gt; A company wants private subnets to reach only their own company's S3 buckets via the Gateway Endpoint — not any random public S3 bucket on the internet. The VPC Endpoint Policy restricts this precisely.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Transit Gateway
&lt;/h2&gt;

&lt;p&gt;As companies grow, they end up with many VPCs — one per team, per environment (dev/staging/prod), per project. Connecting all of them directly to each other via VPC Peering becomes a mess very quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The peering problem:&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;VPC Count&lt;/th&gt;
&lt;th&gt;Peering Connections Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5 VPCs&lt;/td&gt;
&lt;td&gt;10 connections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 VPCs&lt;/td&gt;
&lt;td&gt;45 connections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 VPCs&lt;/td&gt;
&lt;td&gt;190 connections&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each peering connection needs its own route table entries everywhere. This does &lt;strong&gt;not&lt;/strong&gt; scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transit Gateway solves this&lt;/strong&gt; — it's a fully managed, regional network hub that all your VPCs connect to &lt;strong&gt;once&lt;/strong&gt;, and it handles routing between them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Without Transit Gateway (Peering Mesh):&lt;/strong&gt; VPC-A ↔ VPC-B ↔ VPC-C ↔ VPC-D — every VPC needs a direct connection to every other VPC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Transit Gateway (Hub and Spoke):&lt;/strong&gt; VPC-A, VPC-B, VPC-C, and VPC-D each connect once to the central Transit Gateway. The Transit Gateway routes traffic between all of them centrally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What Transit Gateway connects:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple VPCs&lt;/li&gt;
&lt;li&gt;VPN connections (site-to-site)&lt;/li&gt;
&lt;li&gt;Direct Connect Gateways (dedicated network link to on-premise)&lt;/li&gt;
&lt;li&gt;Other Transit Gateways (even across Regions, via peering)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; VPC Peering is like every house in a neighborhood digging a private tunnel directly to every other house — expensive and chaotic to maintain. Transit Gateway is like a central roundabout — every house connects to the roundabout once, and the roundabout directs traffic to wherever it needs to go.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview/Cert scenario:&lt;/strong&gt; &lt;em&gt;"A company has 15 VPCs across different teams that all need to communicate with each other and with an on-premise data center. What's the best approach?"&lt;/em&gt; → Transit Gateway. VPC Peering would require dozens of individual connections and doesn't scale; Transit Gateway centralizes routing through one hub and also connects to on-premise via VPN/Direct Connect.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Route Table Core&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Destination = WHERE (the IP range) · Target = HOW (gateway/component used to get there)&lt;/li&gt;
&lt;li&gt;Longest Prefix Match = most specific CIDR always wins&lt;/li&gt;
&lt;li&gt;Same prefix length? Static route beats Propagated route&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Route Propagation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-learned routes from VGW/Transit Gateway via BGP — no manual entry needed, updates automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Blackhole Route&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route still exists, target no longer exists → traffic silently dropped&lt;/li&gt;
&lt;li&gt;Common cause: NAT Gateway or Peering connection deleted&lt;/li&gt;
&lt;li&gt;AWS flags it visibly as "Blackhole" in console&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Managed Prefix List&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reusable named CIDR group, reference in many SGs/Route Tables&lt;/li&gt;
&lt;li&gt;Update once → propagates everywhere it's referenced&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Troubleshooting Order (memorize this sequence)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Route Table Association → 2. Longest Prefix Match → 3. Target Status → 4. Address Translation Path → 5. Security (SG + NACL)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;VPC Flow Logs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Metadata only (not packet content) — source/dest IP+port, protocol, bytes, ACCEPT/REJECT&lt;/li&gt;
&lt;li&gt;Destinations: CloudWatch Logs or S3 — forensic + security investigation tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;VPC Endpoints&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gateway Endpoint: S3 + DynamoDB only, free, route table based&lt;/li&gt;
&lt;li&gt;Interface Endpoint: most other services, ENI + private IP, hourly cost&lt;/li&gt;
&lt;li&gt;Private DNS on Interface Endpoint OVERRIDES default DNS resolution&lt;/li&gt;
&lt;li&gt;Endpoint Policy = extra restriction layer on top of IAM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Transit Gateway&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Central hub connecting many VPCs + VPN + Direct Connect&lt;/li&gt;
&lt;li&gt;Solves the peering mesh scalability problem — one connection per VPC to the hub, not to every other VPC&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: What is Longest Prefix Match and why does it matter?&lt;/strong&gt;&lt;br&gt;
It's the rule AWS uses when multiple routes in a table could match a destination IP — the route with the most specific (longest) CIDR prefix always wins, regardless of when it was added. For example, a &lt;code&gt;/24&lt;/code&gt; route wins over a &lt;code&gt;/16&lt;/code&gt; route even if the &lt;code&gt;/16&lt;/code&gt; was configured first, as long as the destination IP falls within the &lt;code&gt;/24&lt;/code&gt; range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What is a Blackhole route and how do you detect it?&lt;/strong&gt;&lt;br&gt;
A Blackhole route is a route whose target (like a NAT Gateway or Peering connection) no longer exists or is unavailable. Traffic matching that route gets silently dropped. AWS visually flags it as "Blackhole" directly in the Route Table console, making it easy to spot during troubleshooting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: A private EC2 instance suddenly lost internet access. Walk through your troubleshooting steps.&lt;/strong&gt;&lt;br&gt;
First check if the NAT Gateway is still available. Then verify the route table still points to the correct NAT Gateway ID and isn't marked Blackhole. Then check Security Groups and NACLs are still allowing the traffic in both directions. This systematic order — route table, target status, then security — quickly isolates whether it's a routing problem or a security rule problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: What is the difference between a Gateway Endpoint and an Interface Endpoint?&lt;/strong&gt;&lt;br&gt;
Gateway Endpoints only support S3 and DynamoDB, work via a route table entry, and are free. Interface Endpoints support most other AWS services, work by creating an ENI with a private IP inside your subnet, have an hourly + data processing cost, and can be accessed from on-premises networks via VPN or Direct Connect — Gateway Endpoints cannot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: If both an S3 Gateway Endpoint and an S3 Interface Endpoint exist with Private DNS enabled, which is used?&lt;/strong&gt;&lt;br&gt;
The Interface Endpoint. Enabling Private DNS overrides the default S3 DNS resolution, so standard S3 endpoint calls resolve to the Interface Endpoint's private IP instead of routing through the Gateway Endpoint via the route table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: Why would a company use Transit Gateway instead of VPC Peering?&lt;/strong&gt;&lt;br&gt;
VPC Peering requires a direct connection between every pair of VPCs — this becomes unmanageable at scale (10 VPCs need 45 individual peering connections). Transit Gateway acts as a central hub — each VPC connects once to the Transit Gateway, which handles routing between all of them, and it also connects to on-premise networks via VPN or Direct Connect. Far more scalable for organizations with many VPCs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: What do VPC Flow Logs capture, and what don't they capture?&lt;/strong&gt;&lt;br&gt;
Flow Logs capture metadata about IP traffic — source/destination IP and port, protocol, packet/byte counts, timestamps, and whether traffic was accepted or rejected. They do NOT capture the actual content/payload of the traffic — you know that a connection happened and whether it was allowed, but not what data was inside it.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔬 Practice Scenarios
&lt;/h2&gt;

&lt;p&gt;Since this session is dense and interconnected, these scenarios are designed to be worked through slowly — the goal is confidence explaining "why," not just naming the term. Come back to these once the full VPC picture has settled after revisiting the lectures.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Longest Prefix Match drill:&lt;/strong&gt; Given a route table with &lt;code&gt;10.0.0.0/16 → Local&lt;/code&gt;, &lt;code&gt;10.0.5.0/24 → pcx-abc&lt;/code&gt; (peering), and &lt;code&gt;0.0.0.0/0 → igw-xyz&lt;/code&gt; — for each of these destination IPs, state which route wins and why: &lt;code&gt;10.0.5.10&lt;/code&gt;, &lt;code&gt;10.0.9.10&lt;/code&gt;, &lt;code&gt;52.1.1.1&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Blackhole diagnosis:&lt;/strong&gt; You delete a NAT Gateway to save cost. Two days later, private EC2 instances report they can't reach the internet. Walk through the full troubleshooting checklist and identify exactly where the problem would surface.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;VPC Endpoint decision:&lt;/strong&gt; Your app needs private access to S3, DynamoDB, and Secrets Manager without traffic touching the internet. Which endpoint type do you use for each service, and why?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transit Gateway vs Peering:&lt;/strong&gt; Your company has 3 VPCs today but plans to add 12 more over the next year, plus a VPN to the head office. Would you start with VPC Peering or Transit Gateway? Justify with the connection-count math.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flow Logs investigation:&lt;/strong&gt; You suspect someone is trying to SSH into your EC2 from an unauthorized IP. Describe exactly how you'd set up Flow Logs and what you'd filter for to confirm or rule this out.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Full lab (when ready):&lt;/strong&gt; Build two VPCs, peer them, then replace the peering with a Transit Gateway once you have a third VPC. Add an S3 Gateway Endpoint to one VPC and confirm S3 traffic never leaves the AWS network by checking Flow Logs.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;AWS VPC Extended | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AWS VPC — Your Private Network in the Cloud, Explained from the Ground Up</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Tue, 28 Jul 2026 02:51:52 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-networking-fundamentals-vpc-subnets-igw-nat-the-sg-vs-nacl-battle-eel</link>
      <guid>https://dev.to/tejas_shinkar/aws-networking-fundamentals-vpc-subnets-igw-nat-the-sg-vs-nacl-battle-eel</guid>
      <description>&lt;h1&gt;
  
  
  🌐 AWS Session 9 — Amazon VPC
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Private Networking · Subnets · Internet Gateway · Route Tables · Security Groups · NACLs · NAT Gateway
&lt;/h3&gt;




&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. VPC is the foundation everything else sits on — get this right and the rest of AWS networking clicks into place.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Prerequisites — IP, Subnetting, Ports, DHCP, DNS&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;What is a VPC — Big Picture&lt;/td&gt;
&lt;td&gt;Concept + Analogy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;VPC as a Regional Resource&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;RFC 1918 — Private IP Ranges&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;CIDR Notation&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Default VPC vs Custom VPC&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;VPC Tenancy — Shared vs Dedicated&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Subnets — Public vs Private&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Internet Gateway&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Route Tables&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Security Groups — Stateful Firewall&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;NACLs — Stateless Firewall&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Stateful vs Stateless — Deep Explanation&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;NAT Gateway — Private Subnet Internet Access&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;How Traffic Flows — End to End&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Lab — Build a VPC from Scratch&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Production VPC Architecture&lt;/td&gt;
&lt;td&gt;DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;Interview Questions&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;Practice Tasks&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Big Picture First
&lt;/h2&gt;

&lt;p&gt;Before any definition — here's what VPC actually does in plain terms.&lt;/p&gt;

&lt;p&gt;When you sign up for AWS, you're sharing the same physical infrastructure as thousands of other companies. Without isolation, your servers could technically be reached by anyone else on that same hardware. VPC creates a &lt;strong&gt;logically isolated, private section of the AWS cloud&lt;/strong&gt; that belongs only to you.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;AWS is a massive co-working space with thousands of companies working in it. A VPC is your company's private floor — locked doors, your own network, your own rules. Other companies are in the same building but cannot reach your floor. Inside your floor, you decide which rooms (subnets) are open to visitors (public) and which are staff-only (private).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The whiteboard diagram says it perfectly:&lt;br&gt;
&lt;em&gt;"VPC as a service provides you an isolated environment by default. But you can build connectivity using various components for your organisation."&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Prerequisites — Networking Foundations
&lt;/h2&gt;

&lt;p&gt;You can't truly understand VPC without these five concepts. Don't skip them.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. IP Address
&lt;/h3&gt;

&lt;p&gt;An IP address is a &lt;strong&gt;unique logical address&lt;/strong&gt; assigned to every device on a network so data knows where to go. Like a postal address — without it, packets have no destination.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IPv4 format:  192.168.1.100   (four numbers, 0-255, separated by dots)
IPv6 format:  2001:0db8:85a3::8a2e:0370:7334  (newer, longer, 128-bit)

In AWS VPC:
  Every EC2 instance gets a private IP  (e.g., 10.200.0.45)
  Internet-facing instances get a public IP too
  Elastic IP = static public IP that doesn't change on restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Subnetting
&lt;/h3&gt;

&lt;p&gt;A VPC gives you a large block of IP addresses (e.g., 10.200.0.0/16 = 65,536 IPs). You wouldn't hand all 65,536 addresses to random resources — you &lt;strong&gt;divide the VPC into subnets&lt;/strong&gt; (smaller logical networks), each with its own range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why subnet?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; — isolate databases from web servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organization&lt;/strong&gt; — public-facing resources vs internal resources in separate segments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP conservation&lt;/strong&gt; — allocate only what each tier needs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AZ distribution&lt;/strong&gt; — each subnet lives in one AZ
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VPC: 10.200.0.0/16  (65,536 IPs total)
  ├── Public Subnet:   10.200.0.0/24   (256 IPs) — web servers, ALB
  ├── Private Subnet:  10.200.1.0/24   (256 IPs) — app servers
  └── Database Subnet: 10.200.2.0/24   (256 IPs) — RDS, databases
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Ports &amp;amp; Protocols
&lt;/h3&gt;

&lt;p&gt;An IP address gets you to the right device. A &lt;strong&gt;port&lt;/strong&gt; gets you to the right service on that device.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request: 10.200.0.45 : 80  →  reaches web server (Apache/Nginx)
Request: 10.200.0.45 : 22  →  reaches SSH daemon
Request: 10.200.0.45 : 3306 → reaches MySQL database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;TCP vs UDP:&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;&lt;/th&gt;
&lt;th&gt;TCP&lt;/th&gt;
&lt;th&gt;UDP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Connection&lt;/td&gt;
&lt;td&gt;Connection-oriented (handshake first)&lt;/td&gt;
&lt;td&gt;Connectionless (just send)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliability&lt;/td&gt;
&lt;td&gt;Guarantees delivery, retries on failure&lt;/td&gt;
&lt;td&gt;No guarantee, no retry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;Slower (overhead for reliability)&lt;/td&gt;
&lt;td&gt;Faster (no overhead)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use for&lt;/td&gt;
&lt;td&gt;HTTP, SSH, databases, email&lt;/td&gt;
&lt;td&gt;DNS, video streaming, gaming, VoIP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4. DHCP — Automatic IP Assignment
&lt;/h3&gt;

&lt;p&gt;Without DHCP, every device joining a network would need manual IP configuration. DHCP automates this through the &lt;strong&gt;DORA&lt;/strong&gt; process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D — Discover   EC2 instance boots: "Is there a DHCP server?"
O — Offer      DHCP server: "Here, take IP 10.200.0.45"
R — Request    EC2: "Yes, I'll take that IP"
A — Acknowledge DHCP: "Confirmed. Here's your full config"

What DHCP delivers in AWS:
  1. IP Address        (e.g., 10.200.0.45)
  2. Subnet Mask       (e.g., 255.255.255.0)
  3. Default Gateway   (e.g., 10.200.0.1 — your VPC router)
  4. Primary DNS       (e.g., 169.254.169.253 — AWS DNS)
  5. Secondary DNS     (backup resolver)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In AWS, the VPC's built-in DHCP service handles all of this — EC2 instances get their config automatically at boot, no manual intervention.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. DNS — The Internet's Phonebook
&lt;/h3&gt;

&lt;p&gt;DNS translates domain names to IP addresses. Without it, you'd type &lt;code&gt;54.239.28.85&lt;/code&gt; instead of &lt;code&gt;amazon.com&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You type: www.amazon.com
      ↓
DNS resolver: "What's the IP for amazon.com?"
      ↓
DNS server returns: 54.239.28.85
      ↓
Your browser connects to 54.239.28.85
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In AWS VPC:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each VPC gets a built-in DNS resolver at &lt;code&gt;169.254.169.253&lt;/code&gt; (or VPC base IP + 2)&lt;/li&gt;
&lt;li&gt;EC2 hostnames like &lt;code&gt;ip-10-200-0-45.ap-south-1.compute.internal&lt;/code&gt; are resolved automatically&lt;/li&gt;
&lt;li&gt;Route 53 is AWS's managed DNS service — used for custom domain names&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What is a VPC — With the Whiteboard Diagram Explained
&lt;/h2&gt;

&lt;p&gt;From your class diagram, here's exactly what's happening when a packet travels between two EC2 instances across VPCs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VPC 1 (Source)                              VPC 2 (Destination)
┌─────────────────────────────┐             ┌─────────────────────────────┐
│  EC2 - OS (App/App)         │             │  EC2 - OS (App/Dest)        │
│       ↕                     │             │       ↕                     │
│  NITRO CARD - ENI           │             │  NITRO CARD - ENI           │
│  (IP Address)               │             │  (IP Address)               │
│       ↕                     │             │       ↕                     │
│  SG Eval ←── Flow Logs      │             │  SG (Inbound Rules) → Drop/ │
│  Connection Tracking        │             │  Accept                     │
└──────────────┬──────────────┘             └──────────────▲──────────────┘
               │                                           │
               │    Encapsulate → Overlay Tunnel → Decapsulate
               └───────────────────────────────────────────┘

VPC is MAPPED to:
  ├── Subnet A  (NACL = Enforcement Point)
  └── Subnet B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Walking through this step by step:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;EC2 OS&lt;/strong&gt; generates a packet destined for the other VPC's IP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NITRO Card + ENI&lt;/strong&gt; — AWS's custom hardware that handles networking. The ENI (Elastic Network Interface) is the virtual network card attached to the EC2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Group evaluation&lt;/strong&gt; — before the packet leaves, SG rules are checked (outbound). Flow logs record the traffic. Connection tracking remembers this connection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encapsulation&lt;/strong&gt; — the packet is wrapped in another packet (overlay tunnel) and sent across the AWS backbone network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decapsulation&lt;/strong&gt; — at destination, the outer wrapper is removed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NACL enforcement&lt;/strong&gt; — at the subnet boundary of the destination VPC, the NACL evaluates whether to allow or drop the inbound traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Group&lt;/strong&gt; — at the destination ENI, inbound SG rules evaluated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2 OS receives&lt;/strong&gt; the packet&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  VPC as a Regional Resource
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS Account
    │
    ├── Region: ap-south-1 (Mumbai)
    │     ├── VPC 1 (10.0.0.0/16)
    │     │     ├── AZ: ap-south-1a → Subnet A (10.0.1.0/24)
    │     │     ├── AZ: ap-south-1b → Subnet B (10.0.2.0/24)
    │     │     └── AZ: ap-south-1c → Subnet C (10.0.3.0/24)
    │     │
    │     └── VPC 2 (10.200.0.0/16)  ← separate VPC, same region
    │
    └── Region: us-east-1 (N. Virginia)
          └── VPC 3 (10.1.0.0/16)  ← must be separate VPC for cross-region
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;A VPC belongs to exactly one Region — it cannot span Regions&lt;/li&gt;
&lt;li&gt;A VPC &lt;strong&gt;can span multiple AZs&lt;/strong&gt; within that Region via subnets&lt;/li&gt;
&lt;li&gt;Cross-Region networking: use &lt;strong&gt;VPC Peering&lt;/strong&gt; (direct 1:1) or &lt;strong&gt;Transit Gateway&lt;/strong&gt; (hub-and-spoke for many VPCs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default limit: 5 VPCs per Region&lt;/strong&gt; (soft limit, can request increase via Service Quotas)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview scenario:&lt;/strong&gt; &lt;em&gt;"A customer says they cannot create a new VPC."&lt;/em&gt; → Check if they've hit the 5 VPC per Region quota. Go to Service Quotas → request increase, or delete unused VPCs. Always check the specific Region they're trying to create in — limits are per-Region.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  RFC 1918 — Private IP Ranges
&lt;/h2&gt;

&lt;p&gt;Not all IP addresses are created equal. RFC 1918 (defined by IETF) reserves three ranges specifically for &lt;strong&gt;private networks&lt;/strong&gt; — they are never routed on the public internet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Range               CIDR Block      Total IPs      Common Use
─────────────────────────────────────────────────────────────
10.0.0.0 - 10.255.255.255   10.0.0.0/8      16,777,216    Large enterprise VPCs
172.16.0.0 - 172.31.255.255 172.16.0.0/12   1,048,576     Medium networks
192.168.0.0 - 192.168.255.255 192.168.0.0/16 65,536       Home routers, small offices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;These are the only IP ranges you should use for VPC CIDR blocks in AWS.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why? Because private IPs are not routable on the internet — two companies can both use &lt;code&gt;10.0.0.0/16&lt;/code&gt; internally without conflict, since those IPs never appear on the public internet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Overlap trap:&lt;/strong&gt; If two VPCs you want to peer have overlapping CIDR blocks (e.g., both use &lt;code&gt;10.0.0.0/16&lt;/code&gt;), you &lt;strong&gt;cannot peer them&lt;/strong&gt;. Plan CIDR ranges carefully upfront in multi-VPC architectures.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  CIDR Notation — Reading IP Ranges
&lt;/h2&gt;

&lt;p&gt;CIDR (Classless Inter-Domain Routing) is how IP ranges are written. Understanding the &lt;code&gt;/&lt;/code&gt; notation is essential.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10.200.0.0/16

10.200.0.0 = the starting IP address
/16        = 16 bits are fixed (the "network" part)
             remaining 32-16 = 16 bits are flexible (the "host" part)
             2^16 = 65,536 total IP addresses in this range
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Quick reference:&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;CIDR&lt;/th&gt;
&lt;th&gt;IPs Available&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;/16&lt;/td&gt;
&lt;td&gt;65,536&lt;/td&gt;
&lt;td&gt;Entire VPC (large)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/24&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;One subnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/28&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Small subnet (minimum for AWS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/32&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Single specific IP (used in rules)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/0&lt;/td&gt;
&lt;td&gt;All IPs&lt;/td&gt;
&lt;td&gt;"Anywhere" (used in internet routes)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;AWS reserves 5 IPs in every subnet:&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;10.200.0.0   → Network address
10.200.0.1   → VPC router (your default gateway)
10.200.0.2   → DNS resolver
10.200.0.3   → Reserved for future use
10.200.0.255 → Broadcast address

So a /24 subnet = 256 - 5 = 251 usable IPs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert tip:&lt;/strong&gt; AWS always takes 5 IPs per subnet. Exam questions sometimes ask how many usable hosts are in a /28 subnet — answer is 16 - 5 = 11.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Default VPC vs Custom VPC
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Default VPC&lt;/th&gt;
&lt;th&gt;Custom VPC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Created by&lt;/td&gt;
&lt;td&gt;AWS automatically, one per Region&lt;/td&gt;
&lt;td&gt;You create manually&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CIDR&lt;/td&gt;
&lt;td&gt;172.31.0.0/16 (always)&lt;/td&gt;
&lt;td&gt;Your choice (RFC 1918)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subnets&lt;/td&gt;
&lt;td&gt;One public subnet per AZ (pre-created)&lt;/td&gt;
&lt;td&gt;You design and create&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internet Gateway&lt;/td&gt;
&lt;td&gt;Pre-attached&lt;/td&gt;
&lt;td&gt;You create and attach&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route tables&lt;/td&gt;
&lt;td&gt;Pre-configured with internet route&lt;/td&gt;
&lt;td&gt;You configure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public IPs&lt;/td&gt;
&lt;td&gt;Auto-assigned to all instances&lt;/td&gt;
&lt;td&gt;You choose per subnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use for&lt;/td&gt;
&lt;td&gt;Quick learning, quick testing&lt;/td&gt;
&lt;td&gt;Production, all real work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Should you delete the Default VPC?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many organisations delete it or leave it deliberately empty. Why? If developers can accidentally launch resources into the Default VPC (which has public internet access by default), they may expose things that should be private. Custom VPCs enforce intentional network design.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Production standard:&lt;/strong&gt; In real companies, the Default VPC is disabled or deleted. All production infrastructure runs in Custom VPCs with carefully designed subnet tiers, dedicated security groups per tier, and explicit routing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  VPC Tenancy — Shared vs Dedicated
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Default Tenancy (Shared):&lt;/strong&gt;&lt;br&gt;
Your EC2 instances run on physical hardware shared with other AWS customers. The hypervisor (Nitro) provides full isolation — other customers cannot see your data — but the underlying physical server is shared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dedicated Tenancy:&lt;/strong&gt;&lt;br&gt;
Your EC2 instances run on physical hardware that &lt;strong&gt;no other AWS customer uses&lt;/strong&gt;. The server is yours alone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Default Tenancy:    [Your VM] [Other Customer VM] [Another VM]  ← same physical server
                    │          │                  │
                    └──────────┴──────────────────┘
                              Nitro hypervisor isolates them

Dedicated Tenancy:  [Your VM] [Your VM] [Your VM]  ← entire server is yours
                    No other customers on this hardware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Regulatory compliance requiring physical isolation (HIPAA, PCI-DSS)&lt;/li&gt;
&lt;li&gt;Software licensing tied to physical cores/sockets&lt;/li&gt;
&lt;li&gt;Security policy mandating dedicated hardware&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; Significantly more expensive than Default. Most workloads don't need it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;One-way door:&lt;/strong&gt; A VPC can be changed from Default → Dedicated tenancy. But &lt;strong&gt;Dedicated → Default cannot be changed back&lt;/strong&gt;. If you want Default tenancy again, you must create a new VPC and migrate everything. Always think carefully before choosing Dedicated.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Subnets — Public vs Private
&lt;/h2&gt;

&lt;p&gt;A subnet is a subdivision of your VPC — a smaller network within the larger network. What makes a subnet "public" or "private" isn't a setting you click — it's determined by the &lt;strong&gt;route table attached to it&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;A subnet is PUBLIC if:
  → Its route table has a route: 0.0.0.0/0 → Internet Gateway
  → Instances can send/receive internet traffic
  → Instances should have a public IP (or Elastic IP)

A subnet is PRIVATE if:
  → Its route table has NO route to an Internet Gateway
  → No direct internet access
  → For private subnet internet access → needs NAT Gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Standard 3-tier 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;VPC: 10.200.0.0/16
│
├── Public Subnet: 10.200.0.0/24  (AZ-a)
│     Route: 0.0.0.0/0 → IGW
│     Resources: ALB, Bastion Host, NAT Gateway
│
├── Private Subnet: 10.200.1.0/24  (AZ-a)
│     Route: 0.0.0.0/0 → NAT Gateway
│     Resources: EC2 App Servers
│
└── Database Subnet: 10.200.2.0/24  (AZ-a)
      Route: local only
      Resources: RDS, ElastiCache
      No internet access at all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Why keep databases in a separate subnet with no internet route?&lt;/strong&gt; Even if someone compromises your app server, the database is in a completely separate network segment with no path to the internet. Lateral movement is constrained.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Internet Gateway (IGW)
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;Internet Gateway&lt;/strong&gt; is the door between your VPC and the public internet. Without it, nothing in your VPC can reach the internet and nothing from the internet can reach your VPC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet
    │
Internet Gateway (IGW)
    │   ← attached to VPC (1:1 relationship)
    │
VPC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;One VPC can have &lt;strong&gt;only one IGW&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Horizontally scaled, redundant, and highly available — managed entirely by AWS&lt;/li&gt;
&lt;li&gt;No bandwidth limits, no performance configuration needed&lt;/li&gt;
&lt;li&gt;Free — you pay for data transfer, not the gateway itself&lt;/li&gt;
&lt;li&gt;Performs &lt;strong&gt;NAT for public IPv4&lt;/strong&gt; — maps public IP to private IP for inbound traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Traffic flow for a public EC2 instance:&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 user (203.0.113.5) → requests http://54.239.28.85 (EC2 public IP)
      ↓
Internet Gateway receives request
      ↓
IGW translates: 54.239.28.85 → 10.200.0.45 (EC2 private IP)
      ↓
Routes to EC2 instance in public subnet
      ↓
EC2 processes request, sends response
      ↓
Response goes back through IGW (private IP → public IP translation)
      ↓
Internet user receives response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Route Tables
&lt;/h2&gt;

&lt;p&gt;A route table is a set of rules (routes) that determines &lt;strong&gt;where network traffic is directed&lt;/strong&gt;. Every subnet must be associated with a route table — this is what makes it public or private.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to read a route table:&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;Public Subnet Route Table:
Destination     Target
10.200.0.0/16   local         ← all VPC-internal traffic stays inside
0.0.0.0/0       igw-xxxxx     ← all other traffic goes to Internet Gateway

Private Subnet Route Table:
Destination     Target
10.200.0.0/16   local         ← VPC-internal traffic stays inside
0.0.0.0/0       nat-xxxxx     ← internet traffic goes through NAT Gateway
                              (NAT in public subnet bridges to IGW)

Database Subnet Route Table:
Destination     Target
10.200.0.0/16   local         ← only internal VPC traffic allowed
                              no 0.0.0.0/0 route = no internet at all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Most specific route wins:&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;Traffic to 10.200.1.50:
  Match: 10.200.0.0/16 → local  (/16 matches)
  No match for 0.0.0.0/0 needed
  → stays inside VPC ✅

Traffic to 8.8.8.8 (Google DNS):
  No match for 10.200.0.0/16  (8.8.8.8 is not in this range)
  Match: 0.0.0.0/0 → IGW  (catch-all)
  → goes to internet via IGW ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;One Main Route Table:&lt;/strong&gt; Every VPC has a main route table (implicitly attached to all subnets by default). You create custom route tables and explicitly associate them with specific subnets to override the main table. Always use custom route tables per subnet tier — never rely on the main table for production routing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Security Groups — Stateful Firewall
&lt;/h2&gt;

&lt;p&gt;A Security Group is a &lt;strong&gt;stateful virtual firewall&lt;/strong&gt; that controls traffic at the &lt;strong&gt;ENI level&lt;/strong&gt; — attached to EC2 instances, RDS databases, and Load Balancers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; The SG is the lock on an employee's cabin door. Only people on the approved list get in. And because it's stateful, if the employee walks out to get coffee (outbound), they're automatically let back in (return traffic) — no separate rule needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key behaviours:&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;Default rules:
  Inbound:  DENY all  (nothing gets in unless you explicitly allow it)
  Outbound: ALLOW all (everything can leave by default)

Stateful = tracks connections:
  Outbound rule allows EC2 to call Google (port 443)
  → Return traffic from Google is automatically allowed
  → No inbound rule needed for the response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important limitations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security Groups can only &lt;strong&gt;ALLOW&lt;/strong&gt; — they cannot explicitly deny specific IPs&lt;/li&gt;
&lt;li&gt;If you want to block a specific IP, you need a NACL (covered next)&lt;/li&gt;
&lt;li&gt;A Security Group cannot be applied to a VPC — only to resources (ENIs)&lt;/li&gt;
&lt;li&gt;Multiple SGs can be attached to one resource (rules are combined)&lt;/li&gt;
&lt;li&gt;SGs can reference other SGs as source (e.g., allow traffic from SG-ALB)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  NACLs — Stateless Firewall
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;NACL (Network Access Control List)&lt;/strong&gt; is a stateless firewall at the &lt;strong&gt;subnet boundary&lt;/strong&gt; — it evaluates traffic entering and leaving a subnet before it even reaches any EC2 instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; The NACL is the security guard at the department entrance (subnet boundary). Before anyone enters the department, the guard checks them against a list. The guard doesn't care if someone is "already inside" — every time they pass through the door, they're checked again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet traffic
      ↓
NACL Inbound Rules (evaluated at subnet entry)
      ↓  (if allowed)
EC2 Instance (Security Group evaluated at ENI)
      ↓  (response)
NACL Outbound Rules (evaluated at subnet exit)
      ↓  (if allowed)
Internet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;NACL rules have numbered priorities — lower number = evaluated first:&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;Rule #  Protocol  Port    Source          Allow/Deny
100     TCP       80      0.0.0.0/0       ALLOW   ← HTTP allowed
110     TCP       443     0.0.0.0/0       ALLOW   ← HTTPS allowed
120     TCP       22      10.200.0.0/16   ALLOW   ← SSH from VPC only
*       ALL       ALL     0.0.0.0/0       DENY    ← default: deny everything else
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;*&lt;/code&gt; rule at the bottom is the catch-all deny — always present, cannot be modified.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stateful vs Stateless — The Critical Difference
&lt;/h2&gt;

&lt;p&gt;This is one of the most tested concepts in AWS certs and a very common interview question.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Security Group (Stateful)&lt;/th&gt;
&lt;th&gt;NACL (Stateless)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tracks connections?&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Return traffic&lt;/td&gt;
&lt;td&gt;Auto-allowed&lt;/td&gt;
&lt;td&gt;Must explicitly allow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Applied at&lt;/td&gt;
&lt;td&gt;ENI (instance level)&lt;/td&gt;
&lt;td&gt;Subnet boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rules&lt;/td&gt;
&lt;td&gt;Allow only&lt;/td&gt;
&lt;td&gt;Allow + Deny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rule evaluation&lt;/td&gt;
&lt;td&gt;All rules evaluated together&lt;/td&gt;
&lt;td&gt;In order, first match wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;Per-resource&lt;/td&gt;
&lt;td&gt;Per-subnet (all resources in subnet)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;Deny all inbound, Allow all outbound&lt;/td&gt;
&lt;td&gt;Allow all (default NACL)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The stateful vs stateless scenario that always appears in exams:&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 in public subnet makes an outbound HTTPS call to api.example.com (port 443)

Security Group evaluation:
  Outbound rule: TCP 443 → ALLOW ✅
  Return traffic (inbound from api.example.com): AUTO-ALLOWED ✅
  (Stateful — tracks the connection, no explicit inbound rule needed)

NACL evaluation (outbound request):
  Outbound rule: TCP 443 → ALLOW ✅
  Return traffic comes back on EPHEMERAL PORT (1024-65535)
  NACL inbound rule needed: TCP 1024-65535 → ALLOW ✅
  (Stateless — doesn't remember the connection, must explicitly allow return traffic)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Classic exam question:&lt;/strong&gt; "An EC2 can send requests but cannot receive responses. Security Group allows outbound TCP 443. NACL allows outbound TCP 443. What's wrong?" → The NACL is missing an inbound rule for &lt;strong&gt;ephemeral ports (1024-65535)&lt;/strong&gt; for the return traffic. Security Groups don't need it (stateful). NACLs do (stateless).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Ephemeral ports:&lt;/strong&gt; When your EC2 makes an outbound connection to port 443, the response comes back on a randomly assigned ephemeral port (1024-65535). NACLs need an explicit rule to allow this return range. Security Groups handle it automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  NAT Gateway — Private Subnet Internet Access
&lt;/h2&gt;

&lt;p&gt;Private subnets have no direct path to the internet — that's intentional. But sometimes private instances &lt;strong&gt;need&lt;/strong&gt; internet access outbound: to download software updates (&lt;code&gt;yum update&lt;/code&gt;), reach external APIs, or pull container images.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NAT = Network Address Translation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A NAT Gateway sits in the &lt;strong&gt;public subnet&lt;/strong&gt; and acts as a proxy — private instances send traffic to it, it forwards to the internet using its own public IP, gets the response, and sends it back to the private instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; A NAT Gateway is like a receptionist. Internal staff (private EC2) can ask the receptionist to call a vendor (internet service) on their behalf. The vendor speaks only to the receptionist (public IP), never knowing the internal staff member's internal extension (private IP). The receptionist takes the response and passes it back inside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works — step by step:&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;Private EC2 (10.200.1.45) wants to run: yum update

Step 1: EC2 sends packet
  Source:      10.200.1.45 (private IP)
  Destination: 151.101.1.91 (yum repo server on internet)

Step 2: Route table sends to NAT Gateway
  Private subnet route: 0.0.0.0/0 → nat-gateway-id

Step 3: NAT Gateway translates
  Replaces source: 10.200.1.45 → 52.66.1.100 (NAT Gateway's Elastic IP)
  Forwards to internet via IGW

Step 4: Response comes back
  Source: 151.101.1.91 (yum server)
  Destination: 52.66.1.100 (NAT Gateway public IP)

Step 5: NAT Gateway translates back
  Looks up connection table: 52.66.1.100 → 10.200.1.45
  Sends response to private EC2

Step 6: Private EC2 receives yum update packages ✅
        Internet never knew the real private IP
        No unsolicited inbound from internet possible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Many-to-one NAT:&lt;/strong&gt;&lt;br&gt;
Multiple private instances can all share one NAT Gateway's public IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10.200.1.10 → outbound → 52.66.1.100 (NAT EIP)
10.200.1.11 → outbound → 52.66.1.100 (NAT EIP)
10.200.1.12 → outbound → 52.66.1.100 (NAT EIP)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The NAT Gateway tracks each connection in its translation table so return traffic goes to the correct private instance. The internet sees only one public IP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NAT Gateway vs NAT Instance:&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;&lt;/th&gt;
&lt;th&gt;NAT Gateway (AWS Managed)&lt;/th&gt;
&lt;th&gt;NAT Instance (DIY on EC2)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Management&lt;/td&gt;
&lt;td&gt;Fully managed by AWS&lt;/td&gt;
&lt;td&gt;You manage (OS, updates)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability&lt;/td&gt;
&lt;td&gt;Highly available within AZ&lt;/td&gt;
&lt;td&gt;Single point of failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bandwidth&lt;/td&gt;
&lt;td&gt;Up to 100 Gbps&lt;/td&gt;
&lt;td&gt;Limited by instance type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Higher per hour&lt;/td&gt;
&lt;td&gt;Lower per hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production use&lt;/td&gt;
&lt;td&gt;✅ Always&lt;/td&gt;
&lt;td&gt;❌ Avoid&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;Always use NAT Gateway in production&lt;/strong&gt; — never NAT Instance. The management overhead and single-point-of-failure risk of NAT Instance outweigh the cost savings.&lt;/p&gt;

&lt;p&gt;💰 &lt;strong&gt;Cost note:&lt;/strong&gt; NAT Gateway charges per hour + per GB of data processed. Put NAT Gateway in each AZ (not just one) for high availability — but this doubles the NAT cost. Trade-off between cost and availability.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  End-to-End Traffic Flow — How It All Works Together
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; A user in Mumbai opens your website hosted on a private EC2 behind an ALB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Browser (Mumbai)
      │
      │  HTTPS request to your domain
      ▼
Route 53 (DNS)
      │  resolves domain → ALB DNS name → ALB IP
      ▼
Internet Gateway (IGW)
      │  traffic enters VPC
      ▼
NACL (Public Subnet boundary)
      │  inbound rule: TCP 443 → ALLOW
      ▼
Application Load Balancer (in Public Subnet)
      │  SG-ALB: allows TCP 443 from 0.0.0.0/0
      │  ALB decrypts HTTPS, picks healthy EC2 target
      ▼
NACL (Private Subnet boundary)
      │  inbound rule: TCP 8080 from VPC CIDR → ALLOW
      ▼
EC2 App Server (in Private Subnet)
      │  SG-EC2: allows TCP 8080 from SG-ALB only
      │  App processes request, queries database
      ▼
RDS Database (in DB Subnet)
      │  SG-RDS: allows TCP 3306 from SG-EC2 only
      │  Returns query results
      ▼
Response travels back up the same path ↑
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the standard 3-tier architecture deployed in every serious production environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lab — Build a VPC from Scratch
&lt;/h2&gt;

&lt;p&gt;This is exactly what you did in class. Walk through it again to make the steps click.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You're Building
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VPC: 10.200.0.0/16
  ├── Public Subnet: 10.200.0.0/24  (connected to IGW)
  ├── Private Subnet: 10.200.1.0/24 (connected to NAT GW)
  ├── Internet Gateway (attached to VPC)
  ├── NAT Gateway (in public subnet)
  ├── Public Route Table (0.0.0.0/0 → IGW)
  └── Private Route Table (0.0.0.0/0 → NAT GW)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1 — Create VPC
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VPC Console → Your VPCs → Create VPC

Resource:     VPC only (not "VPC and more")
Name tag:     lab-vpc
IPv4 CIDR:    10.200.0.0/16  (manual input)
IPv6 CIDR:    No
Tenancy:      Default

→ Create VPC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Checkpoint: &lt;code&gt;lab-vpc&lt;/code&gt; appears in Your VPCs list with state &lt;strong&gt;Available&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Create Subnets
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Public Subnet:&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;Subnets → Create subnet

VPC:              lab-vpc
Subnet name:      public-subnet-1
Availability Zone: ap-south-1a
IPv4 CIDR block:  10.200.0.0/24

→ Create subnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Private Subnet:&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;Subnet name:      private-subnet-1
Availability Zone: ap-south-1b
IPv4 CIDR block:  10.200.1.0/24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Put public and private subnets in &lt;strong&gt;different AZs&lt;/strong&gt; — this improves availability. Even if one AZ has issues, the other tier keeps running.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;✅ Checkpoint: Both subnets appear, status &lt;strong&gt;Available&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Create Internet Gateway
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet Gateways → Create internet gateway

Name tag: lab-igw

→ Create
→ After creation: Actions → Attach to VPC → select lab-vpc → Attach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ After creating an IGW, it shows &lt;strong&gt;"Detached"&lt;/strong&gt; until you attach it to a VPC. This is the step most beginners forget — the IGW exists but does nothing until attached.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;✅ Checkpoint: &lt;code&gt;lab-igw&lt;/code&gt; shows state &lt;strong&gt;Attached&lt;/strong&gt; to &lt;code&gt;lab-vpc&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — Create Route Tables
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Public Route Table:&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;Route Tables → Create route table
Name: public-rt
VPC: lab-vpc → Create

Edit Routes:
  Add route:
    Destination: 0.0.0.0/0
    Target: lab-igw
  Save routes

Subnet Associations → Edit subnet associations:
  Select: public-subnet-1
  Save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Private Route Table (add after NAT Gateway is created):&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;Route Tables → Create route table
Name: private-rt
VPC: lab-vpc → Create

(Add 0.0.0.0/0 → NAT Gateway after Step 5)

Subnet Associations → Edit subnet associations:
  Select: private-subnet-1
  Save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5 — Create NAT Gateway
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAT Gateways → Create NAT gateway

Name:              lab-nat-gw
Subnet:            public-subnet-1  ← MUST be in public subnet
Connectivity type: Public
Elastic IP:        Allocate Elastic IP → Allocate

→ Create NAT gateway
Wait 2-3 minutes for state: Available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ NAT Gateway &lt;strong&gt;must be in the public subnet&lt;/strong&gt; — it needs internet access via the IGW to forward traffic on behalf of private instances.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Now update private route table:&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;private-rt → Routes → Edit routes
Add route:
  Destination: 0.0.0.0/0
  Target: lab-nat-gw
Save routes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6 — Launch EC2 Instances to Test
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Public EC2:&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 → Launch Instance
Name: public-ec2
AMI: Amazon Linux 2023
Type: t2.micro
VPC: lab-vpc
Subnet: public-subnet-1
Auto-assign public IP: Enable
Security Group: allow SSH (22) from My IP + HTTP (80) from anywhere
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Private EC2:&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;Name: private-ec2
VPC: lab-vpc
Subnet: private-subnet-1
Auto-assign public IP: Disable  ← no public IP
Security Group: allow SSH (22) from VPC CIDR (10.200.0.0/16)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Testing connectivity:&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;# SSH into public EC2 (has public IP)&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; key.pem ec2-user@&amp;lt;public-ec2-public-ip&amp;gt;

&lt;span class="c"&gt;# From public EC2, SSH into private EC2 (using private IP)&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; key.pem ec2-user@&amp;lt;private-ec2-private-ip&amp;gt;

&lt;span class="c"&gt;# From private EC2, test internet access via NAT Gateway&lt;/span&gt;
curl https://checkip.amazonaws.com
&lt;span class="c"&gt;# Returns: NAT Gateway's Elastic IP (not the private EC2's IP) ✅&lt;/span&gt;

&lt;span class="c"&gt;# Test outbound (yum update should work)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum update &lt;span class="nt"&gt;-y&lt;/span&gt;  &lt;span class="c"&gt;# downloads via NAT Gateway ✅&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🏢 Production VPC Architecture
&lt;/h2&gt;

&lt;p&gt;This is what a real company's VPC looks like in AWS. Understanding this at the architecture level is what separates someone who knows AWS features from someone who can design AWS systems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Region: ap-south-1 (Mumbai)
VPC: prod-vpc  10.0.0.0/16
│
├── AZ: ap-south-1a                    AZ: ap-south-1b
│   ├── Public Subnet  10.0.0.0/24    Public Subnet  10.0.10.0/24
│   │   ├── ALB node                  ├── ALB node
│   │   └── NAT Gateway               └── NAT Gateway
│   │
│   ├── Private App Subnet 10.0.1.0/24   Private App Subnet 10.0.11.0/24
│   │   └── EC2 (App Server)             └── EC2 (App Server)
│   │       ASG manages these             ASG manages these
│   │
│   └── Private DB Subnet 10.0.2.0/24    Private DB Subnet 10.0.12.0/24
│       └── RDS Primary                  └── RDS Standby (Multi-AZ)
│
├── Internet Gateway (single, attached to VPC)
├── Route 53 (DNS → ALB)
└── CloudFront (CDN → ALB or S3)

Security controls:
  SG-ALB:  HTTP/HTTPS from 0.0.0.0/0
  SG-EC2:  HTTP from SG-ALB only
  SG-RDS:  MySQL (3306) from SG-EC2 only
  NACL-Public:  Allow 80, 443, 1024-65535 inbound; Allow all outbound
  NACL-Private: Allow from VPC CIDR; No direct internet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this design?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users only ever reach the ALB — EC2 and RDS are never exposed to internet&lt;/li&gt;
&lt;li&gt;Even if app servers are compromised, no direct database access from internet&lt;/li&gt;
&lt;li&gt;Multi-AZ for everything — if one AZ goes down, the other keeps serving&lt;/li&gt;
&lt;li&gt;NAT Gateways in each AZ — private instances don't go cross-AZ for internet (reduces cost and latency)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VPC FUNDAMENTALS
  Regional resource — one Region, spans multiple AZs via subnets
  Default limit: 5 VPCs per Region (soft limit)
  CIDR must be from RFC 1918 private ranges
  Overlapping CIDRs = cannot peer VPCs

SUBNET
  One AZ per subnet (subnet lives in one AZ)
  Public = route table has 0.0.0.0/0 → IGW
  Private = route table has 0.0.0.0/0 → NAT GW (or no internet route)
  AWS reserves 5 IPs per subnet

INTERNET GATEWAY
  One IGW per VPC (1:1 relationship)
  Must attach to VPC after creating
  Provides internet access for public subnets
  Performs NAT for public IPs

ROUTE TABLE
  Controls where traffic goes
  Most specific route wins
  Each subnet must have one associated route table
  Local route (VPC CIDR) always present, cannot delete

SECURITY GROUP (Stateful)
  Operates at ENI / instance level
  Tracks connections — return traffic auto-allowed
  Allow rules only (cannot deny specific IPs)
  Default: deny all inbound, allow all outbound

NACL (Stateless)
  Operates at subnet boundary
  Numbered rules, evaluated in order (lowest first)
  Must allow BOTH directions (including return/ephemeral ports 1024-65535)
  Allow + Deny rules
  Default NACL: allow all

NAT GATEWAY
  Must be in public subnet + has Elastic IP
  Private instances → NAT GW → IGW → Internet
  Outbound only from private instances (no unsolicited inbound)
  Many private IPs share one public IP
  Always use NAT Gateway (not NAT Instance) in production

DHCP (DORA)
  Discover → Offer → Request → Acknowledge
  Gives: IP, Subnet Mask, Default Gateway, DNS servers

STATEFUL vs STATELESS
  Stateful (SG):  tracks connection, return traffic auto-allowed
  Stateless (NACL): no memory, both directions need explicit rules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: What is the difference between a Security Group and a NACL?&lt;/strong&gt;&lt;br&gt;
Security Groups are stateful firewalls at the ENI/instance level — they track connections so return traffic is automatically allowed, and they support Allow rules only. NACLs are stateless firewalls at the subnet boundary — they don't track connections so you need explicit rules for both directions (including ephemeral ports for return traffic), and they support both Allow and Deny rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: A private EC2 instance can send requests to the internet but cannot receive responses. What's likely wrong?&lt;/strong&gt;&lt;br&gt;
The NACL for the private subnet is missing an inbound rule for ephemeral ports (1024-65535). The request goes out fine, but the response comes back on a dynamically assigned port in this range. The Security Group doesn't need a rule (stateful), but the NACL does (stateless). Add NACL inbound rule: TCP 1024-65535 from 0.0.0.0/0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: A customer says they cannot create a new VPC. What do you check first?&lt;/strong&gt;&lt;br&gt;
Check if they've hit the VPC quota for that Region — the default is 5 VPCs per Region. Go to Service Quotas and request an increase, or identify and delete unused VPCs. Also confirm they're checking the correct Region.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: What makes a subnet "public" vs "private" in AWS?&lt;/strong&gt;&lt;br&gt;
Not a setting — it's determined by the route table. A subnet is public if its route table contains a route sending 0.0.0.0/0 traffic to an Internet Gateway. A subnet is private if it has no such route (or routes internet traffic through a NAT Gateway instead).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: Why must a NAT Gateway be placed in a public subnet?&lt;/strong&gt;&lt;br&gt;
The NAT Gateway needs to be reachable from the internet to forward traffic on behalf of private instances. It must itself have a path to the Internet Gateway, which only exists in public subnets (via the route table). If it were in a private subnet, it would have no internet path and couldn't perform its function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: Can two VPCs with CIDR 10.0.0.0/16 be peered?&lt;/strong&gt;&lt;br&gt;
No. VPC Peering requires non-overlapping CIDR blocks. Both VPCs using 10.0.0.0/16 means their IP ranges are identical — routing would be ambiguous. You'd need to change one VPC's CIDR (which requires recreation) or use a different range from the start. This is why planning CIDR blocks upfront in multi-VPC architectures is critical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: What is the difference between an Internet Gateway and a NAT Gateway?&lt;/strong&gt;&lt;br&gt;
An Internet Gateway enables two-way communication between public subnet resources and the internet — inbound and outbound. A NAT Gateway enables outbound-only internet access for private subnet resources — it translates private IPs to its public IP for outbound traffic but blocks all unsolicited inbound connections from the internet.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔬 Practice Tasks
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build from scratch (mandatory):&lt;/strong&gt; Without referring to the lab steps, create a complete VPC: custom CIDR, one public and one private subnet in different AZs, IGW attached, NAT Gateway in public subnet, separate route tables per subnet with correct routes. Launch one EC2 in each subnet and verify: public EC2 reachable from internet, private EC2 reachable only from public EC2 via SSH, private EC2 can reach internet via NAT.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;NACL test:&lt;/strong&gt; Create a NACL that blocks HTTP (port 80) inbound to a public subnet but allows HTTPS (443). Launch an EC2 with Apache. Confirm HTTP is blocked but HTTPS works.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;VPC Flow Logs:&lt;/strong&gt; Enable VPC Flow Logs on your VPC. Generate some traffic (SSH, HTTP). Find the logs in CloudWatch and identify: source IP, destination IP, port, and whether traffic was accepted or rejected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stateless gotcha:&lt;/strong&gt; Launch a private EC2 with a custom NACL that allows TCP 443 outbound. Try to reach an HTTPS endpoint. It will fail. Figure out why (missing ephemeral port inbound rule). Fix it and confirm it works.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-AZ setup:&lt;/strong&gt; Create subnets in 2 AZs, one public + one private in each AZ. Place a NAT Gateway in each public subnet. Configure private subnets to use the NAT Gateway in their own AZ. Test that private instances in each AZ reach the internet through their local NAT (check source IP).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;AWS Session 9 — VPC Essentials | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>networking</category>
    </item>
    <item>
      <title>AWS S3 — Versioning, Static Hosting, CORS, Object Lock &amp; More</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Sun, 26 Jul 2026 10:24:48 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-s3-versioning-static-hosting-cors-object-lock-more-4m5</link>
      <guid>https://dev.to/tejas_shinkar/aws-s3-versioning-static-hosting-cors-object-lock-more-4m5</guid>
      <description>&lt;h1&gt;
  
  
  🗃️ AWS S3 — Extended Concepts
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Versioning Deep Dive · Bucket Types · Static Hosting · CORS · Object Lock · Data Concepts
&lt;/h3&gt;




&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This session goes deeper into S3 — versioning behaviour, static website hosting, CORS, Object Lock, and important data concepts every cloud engineer should know.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Bucket Versioning — Deep Dive&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Does S3 Store Only Changed Bytes?&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Suspend Versioning — What Actually Happens&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;General Purpose vs Directory Bucket&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Storage Classes — Context Recap&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;OLTP vs OLAP — Data Concepts for Cloud Engineers&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Static Website Hosting on S3&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;CORS — What It Is and Why S3 Needs It&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;S3 Object Lock — WORM Model&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Interview Questions&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Practice Tasks&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Bucket Versioning — Deep Dive
&lt;/h2&gt;

&lt;p&gt;You know what versioning does at a surface level — it keeps multiple versions of an object. But the behaviour in two specific scenarios is what most people get wrong, and both come up in exams and interviews.&lt;/p&gt;

&lt;h3&gt;
  
  
  Normal Upload Flow (Versioning Enabled)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upload report.pdf           → Version ID: v1 (current)
Upload report.pdf again     → Version ID: v2 (current), v1 preserved
Upload report.pdf again     → Version ID: v3 (current), v1 + v2 preserved

GET report.pdf              → returns v3 (always returns current)
GET report.pdf?versionId=v1 → returns v1 specifically
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every upload with the same key creates a new version. The previous ones are never touched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete Flow (The Tricky Part)
&lt;/h3&gt;

&lt;p&gt;This is where most people get confused — and where interviewers test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State: report.pdf has v1, v2, v3

DELETE report.pdf (standard delete, no versionId specified)
→ S3 does NOT delete anything
→ S3 adds a Delete Marker (a special object with its own Version ID)
→ GET report.pdf now returns 404 (marker hides the object)
→ But v1, v2, v3 still exist underneath
→ You're still paying storage for all three versions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Restoring a "deleted" object:&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;Delete the Delete Marker
→ Object reappears, GET report.pdf returns v3 again ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Permanently deleting an object and all versions:&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;Must explicitly delete each version by its Version ID:
DELETE report.pdf?versionId=v1
DELETE report.pdf?versionId=v2
DELETE report.pdf?versionId=v3
DELETE &amp;lt;delete-marker&amp;gt;?versionId=dm-id
→ Now the object is truly gone, zero storage consumed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Cost trap:&lt;/strong&gt; People enable versioning, never clean up old versions, and then wonder why their S3 bill is growing. Every version consumes storage. Fix: add a Lifecycle rule to expire non-current versions after X days.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lifecycle rule → Expire non-current versions after 30 days
→ Old versions auto-deleted after 30 days
→ Current version always kept
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Does S3 Store Only Changed Bytes?
&lt;/h2&gt;

&lt;p&gt;A natural question — modern tools like git and incremental backups only store the &lt;em&gt;difference&lt;/em&gt; (delta) between versions. Does S3 do the same?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No. S3 stores each version as a complete, separate object.&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;report.pdf v1 = 10 MB → stored in full (10 MB)
report.pdf v2 = 10.2 MB → stored in full (10.2 MB)  ← NOT just the 0.2 MB diff
report.pdf v3 = 10.1 MB → stored in full (10.1 MB)

Total storage consumed: 30.3 MB (not 10.2 MB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each version gets its own Version ID and is managed independently — you can access, copy, restore, or delete any version directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implication for cost planning:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frequently updated large files + versioning = storage costs multiply fast&lt;/li&gt;
&lt;li&gt;For 100 uploads/day of a 50 MB file: 100 × 50 MB = 5 GB/day extra storage&lt;/li&gt;
&lt;li&gt;Always pair versioning with Lifecycle rules that expire old versions&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview tip:&lt;/strong&gt; "Does S3 versioning use delta storage like git?" — No. Every version is a full copy. This is why Lifecycle policies to expire non-current versions are essential in production.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Suspend Versioning — What Actually Happens
&lt;/h2&gt;

&lt;p&gt;Once you enable versioning on a bucket, you &lt;strong&gt;cannot fully turn it off&lt;/strong&gt; — you can only &lt;em&gt;suspend&lt;/em&gt; it. This is by design: AWS won't let you accidentally lose version history.&lt;/p&gt;

&lt;h3&gt;
  
  
  Behaviour After Suspension
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before suspension: v1, v2, v3 exist for report.pdf

Versioning suspended.

New upload: report.pdf v4
→ Gets Version ID: null (not a real UUID)
→ If another null version existed, it gets overwritten
→ v1, v2, v3 still safely preserved

State now:
  report.pdf  →  null version (v4 upload)
                 v3 (preserved)
                 v2 (preserved)
                 v1 (preserved)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Three states a bucket can be in:&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;State&lt;/th&gt;
&lt;th&gt;New uploads&lt;/th&gt;
&lt;th&gt;Existing versions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Unversioned&lt;/strong&gt; (never enabled)&lt;/td&gt;
&lt;td&gt;No Version ID&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enabled&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full Version ID (UUID)&lt;/td&gt;
&lt;td&gt;All preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Suspended&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Version ID: &lt;code&gt;null&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;All preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key rules to remember:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Versioning: once enabled, can never go back to "Unversioned"&lt;/li&gt;
&lt;li&gt;Suspended → Enabled: you can re-enable. All existing versions preserved, new uploads get UUIDs again&lt;/li&gt;
&lt;li&gt;Null version uploads: if you upload again while suspended, the previous null version is overwritten (but all UUID versions stay untouched)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert scenario:&lt;/strong&gt; "A bucket has versioning enabled. The team wants to stop creating new versions to save costs but keep existing ones. What should they do?" → Suspend versioning. New uploads will use null version ID and won't accumulate new versions, but all existing versions are retained.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  General Purpose Bucket vs Directory Bucket
&lt;/h2&gt;

&lt;p&gt;Historically S3 had one type of bucket. AWS introduced &lt;strong&gt;Directory Buckets&lt;/strong&gt; for a specific high-performance use case. Knowing the difference matters for both certs and architecture decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  General Purpose Bucket
&lt;/h3&gt;

&lt;p&gt;The standard S3 bucket you've been using in all labs. Think of it as a &lt;strong&gt;general warehouse&lt;/strong&gt; — stores anything, supports all features, multi-AZ redundancy by default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Features:          ALL S3 features supported
AZs:               Multi-AZ (data replicated across 3+ AZs in a Region)
Storage classes:   All classes (Standard, IA, Glacier, etc.)
Versioning:        ✅ Supported
Lifecycle rules:   ✅ Supported
Object Lock:       ✅ Supported
Replication:       ✅ Supported
Use for:           Everything — backups, web assets, data lakes, logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Directory Bucket
&lt;/h3&gt;

&lt;p&gt;Purpose-built for &lt;strong&gt;high-performance, latency-sensitive workloads&lt;/strong&gt; — think machine learning training data, real-time analytics, high-frequency log ingestion.&lt;/p&gt;

&lt;p&gt;Think of it as an &lt;strong&gt;express warehouse inside one building&lt;/strong&gt; — extremely fast, but only within a single AZ and stripped of most management features.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Features:          Limited to core operations
AZs:               Single AZ only (Express One Zone storage class)
Storage classes:   Only Express One Zone
Versioning:        ❌ Not supported
Lifecycle rules:   ❌ Not supported
Object Lock:       ❌ Not supported
Performance:       10x faster than General Purpose for small objects
Use for:           ML training, real-time data processing, low-latency apps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Decision guide:&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;Need all S3 features (versioning, lifecycle, replication)?  → General Purpose
Need maximum speed for ML/analytics inside one AZ?         → Directory Bucket
Need durability across multiple AZs?                       → General Purpose
Need sub-10ms consistent latency at massive scale?         → Directory Bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ Directory Buckets are in a single AZ — if that AZ has an outage, your data is temporarily unavailable. Only use them for workloads where performance beats durability requirements.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Storage Classes — Context Recap
&lt;/h2&gt;

&lt;p&gt;Covered in depth last session. Quick mental model for reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How often will this data be accessed?

Daily/Active         → Standard
Unknown pattern      → Intelligent-Tiering (auto-manages itself)
Once a month         → Standard-IA (retrieval fee applies)
Once a quarter       → Glacier Instant Retrieval
Once a year          → Glacier Flexible (minutes-to-hours retrieval)
Almost never         → Glacier Deep Archive (cheapest, up to 12hr retrieval)
One AZ is fine + fast? → Express One Zone (Directory Bucket only)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lifecycle rules automate the transitions&lt;/strong&gt; — set it once, S3 moves objects down the chain automatically as they age. Always pair versioning with lifecycle rules that expire old versions.&lt;/p&gt;




&lt;h2&gt;
  
  
  OLTP vs OLAP — Data Concepts Every Cloud Engineer Should Know
&lt;/h2&gt;

&lt;p&gt;This pair of concepts comes up when you're designing cloud architectures involving databases and data pipelines — which is constantly in DevOps and cloud roles.&lt;/p&gt;

&lt;h3&gt;
  
  
  OLTP — Online Transaction Processing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Handles day-to-day, real-time business operations — individual transactions, fast reads and writes, high concurrency.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Short, fast queries (milliseconds)&lt;/li&gt;
&lt;li&gt;Many concurrent users&lt;/li&gt;
&lt;li&gt;Writes and reads constantly&lt;/li&gt;
&lt;li&gt;Data is current and operational&lt;/li&gt;
&lt;li&gt;Optimized for INSERT, UPDATE, DELETE&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples in real life:&lt;/strong&gt; A customer places an order, a payment is processed, a hotel room is booked — each of these is one OLTP transaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS services:&lt;/strong&gt; RDS (MySQL, PostgreSQL), Aurora, DynamoDB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;User&lt;/span&gt; &lt;span class="n"&gt;clicks&lt;/span&gt; &lt;span class="nv"&gt;"Buy Now"&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;
      &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="n"&gt;OLTP&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RDS&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Aurora&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="err"&gt;↓&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;orders&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;
      &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="n"&gt;Completed&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;milliseconds&lt;/span&gt; &lt;span class="err"&gt;✅&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OLAP — Online Analytical Processing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Analyses large historical datasets to generate reports, trends, and business insights. Not for real-time transactions — for answering questions like "what were our top-selling products last quarter across all regions?"&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Complex queries across massive datasets (millions/billions of rows)&lt;/li&gt;
&lt;li&gt;Read-heavy (rarely writes)&lt;/li&gt;
&lt;li&gt;Aggregations, joins, GROUP BY across large tables&lt;/li&gt;
&lt;li&gt;Historical data, not real-time&lt;/li&gt;
&lt;li&gt;Optimized for fast analytical reads (columnar storage)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples in real life:&lt;/strong&gt; Monthly sales report, customer churn analysis, inventory trend forecast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS services:&lt;/strong&gt; Amazon Redshift (data warehouse), Amazon Athena (query S3 directly with SQL)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;Business&lt;/span&gt; &lt;span class="n"&gt;analyst&lt;/span&gt; &lt;span class="n"&gt;asks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"Show me total revenue by product category for 2025"&lt;/span&gt;
      &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="n"&gt;OLAP&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Redshift&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Athena&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales_fact&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2025&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;
      &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="n"&gt;Scans&lt;/span&gt; &lt;span class="n"&gt;billions&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="err"&gt;✅&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;would&lt;/span&gt; &lt;span class="n"&gt;kill&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;OLTP&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Both Matter Together
&lt;/h3&gt;

&lt;p&gt;In real architectures, you use both — OLTP for operations, OLAP for analysis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer transactions → RDS (OLTP) → ETL pipeline → Redshift (OLAP)
                                           ↑
                               Runs nightly, loads transformed data
                               Business reports run against Redshift
                               RDS never touched by analytical queries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why separate them?&lt;/strong&gt; Running a heavy analytical query against your OLTP database would lock tables, slow down real user transactions, and potentially take the site down. OLAP databases are architecturally different — columnar storage, massively parallel processing — built specifically for large scans.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;OLTP&lt;/th&gt;
&lt;th&gt;OLAP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real-time transactions&lt;/td&gt;
&lt;td&gt;Historical analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Query type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Short, simple&lt;/td&gt;
&lt;td&gt;Long, complex aggregations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data volume&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Thousands of rows&lt;/td&gt;
&lt;td&gt;Billions of rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Users&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Many concurrent end users&lt;/td&gt;
&lt;td&gt;Analysts, BI tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Writes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frequent&lt;/td&gt;
&lt;td&gt;Rare (batch loads)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS services&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RDS, Aurora, DynamoDB&lt;/td&gt;
&lt;td&gt;Redshift, Athena&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Row-oriented&lt;/td&gt;
&lt;td&gt;Columnar (faster scans)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview context:&lt;/strong&gt; If asked "how would you design a reporting system that doesn't slow down production?" — separate OLTP (RDS for transactions) from OLAP (Redshift/Athena for analytics), connected by an ETL pipeline. This pattern shows architectural thinking, not just tool knowledge.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Static Website Hosting on S3
&lt;/h2&gt;

&lt;p&gt;S3 can serve a complete website — HTML, CSS, JavaScript, images — directly from a bucket, without any server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Normally a website needs a server running 24/7 waiting for visitors. Static hosting on S3 is like pinning your brochure to a public notice board — it's always there, no one needs to "run" it, and millions of people can read it simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  What "Static" Means
&lt;/h3&gt;

&lt;p&gt;Static = files that are the same for every visitor. The server doesn't generate content dynamically per user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Static (S3 can serve):        ❌ Not static (S3 cannot serve):
   HTML files                       Node.js / Express apps
   CSS stylesheets                  PHP backend
   JavaScript (React, Vue)          Python Flask/Django
   Images, fonts, PDFs              Database queries
   Pre-built React apps             Server-side rendering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A React or Vue app that calls an API is fine — the &lt;em&gt;frontend&lt;/em&gt; is static files, the &lt;em&gt;API&lt;/em&gt; runs on EC2/Lambda elsewhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step-by-Step Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Step&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Create&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;Name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your-site-name.com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(can&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;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;domain)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;Region:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;us-east&lt;/span&gt;&lt;span class="mi"&gt;-1&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;closest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;users)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;Block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Access:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;DISABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;site&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Step&lt;/span&gt;&lt;span class="w"&gt; &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="err"&gt;Enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Website&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Hosting&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;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Properties&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;Static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;website&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hosting&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;Enable&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;document:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;index.html&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;Error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;document:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;error.html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(optional)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;Save&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;note&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Website&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Endpoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;URL&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Step&lt;/span&gt;&lt;span class="w"&gt; &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="err"&gt;Upload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;files&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;Upload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;index.html,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;styles.css,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;app.js,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;images/&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;Make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;present&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Step&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Add&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;Policy&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;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;read)&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;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Permissions&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;Bucket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Policy&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;Edit&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;paste:&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PublicReadGetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&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="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;"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="s2"&gt;"arn:aws:s3:::your-bucket-name/*"&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="err"&gt;Step&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Test&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;Open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&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="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Website&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Endpoint&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;browser&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;http://your-bucket-name.s&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;-website-ap-south&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="err"&gt;.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;Your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;site&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;loads&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  S3 Website URL vs S3 Object URL
&lt;/h3&gt;

&lt;p&gt;These look similar but behave differently:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;S3 Object URL&lt;/th&gt;
&lt;th&gt;S3 Website Endpoint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Format&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bucket.s3.region.amazonaws.com/key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bucket.s3-website-region.amazonaws.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Returns&lt;/td&gt;
&lt;td&gt;The raw file&lt;/td&gt;
&lt;td&gt;Serves index.html for &lt;code&gt;/&lt;/code&gt;, error.html for 404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ HTTP only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudFront compatible&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;Production setup:&lt;/strong&gt; Use CloudFront in front of S3 static hosting to get HTTPS, custom domain, and CDN caching. S3 alone gives you HTTP only with an ugly URL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Personal portfolio, documentation site, React/Vue/Angular SPA, company landing page — anything without server-side logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  CORS — Cross-Origin Resource Sharing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem CORS Solves
&lt;/h3&gt;

&lt;p&gt;Browsers enforce a security rule called the &lt;strong&gt;Same-Origin Policy&lt;/strong&gt; — a page loaded from &lt;code&gt;domain-a.com&lt;/code&gt; cannot make JavaScript requests to &lt;code&gt;domain-b.com&lt;/code&gt; unless explicitly permitted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; You work at Company A and want to access files in Company B's building. Company B's security guard (the browser's CORS check) checks whether Company A employees are on the approved visitor list before letting you in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where This Appears with S3
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your frontend: https://myapp.com  (served from EC2 or elsewhere)
Your S3 bucket: https://my-assets.s3.amazonaws.com

User visits myapp.com, JavaScript runs:
  fetch('https://my-assets.s3.amazonaws.com/data.json')
  → Browser BLOCKS this request ❌
  → "Cross-origin request blocked"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser blocked it because &lt;code&gt;myapp.com&lt;/code&gt; ≠ &lt;code&gt;my-assets.s3.amazonaws.com&lt;/code&gt; — different origins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing It — CORS Configuration on S3
&lt;/h3&gt;

&lt;p&gt;You add a CORS rule to the S3 bucket that tells browsers: "Requests from &lt;code&gt;myapp.com&lt;/code&gt; are approved."&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AllowedHeaders"&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;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AllowedMethods"&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;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AllowedOrigins"&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;"https://myapp.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;"ExposeHeaders"&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;"ETag"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"MaxAgeSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&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;Breaking it down:&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;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AllowedOrigins&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which domains can make requests — &lt;code&gt;["https://myapp.com"]&lt;/code&gt; or &lt;code&gt;["*"]&lt;/code&gt; for all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AllowedMethods&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which HTTP methods are allowed — GET, PUT, POST, DELETE, HEAD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AllowedHeaders&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which request headers are allowed — &lt;code&gt;["*"]&lt;/code&gt; means all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ExposeHeaders&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which response headers the browser can read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MaxAgeSeconds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How long the browser can cache the CORS preflight response&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Bucket Policy vs CORS — Key Distinction
&lt;/h3&gt;

&lt;p&gt;This trips people up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bucket Policy: answers "WHO can access this bucket?"
  → Applies to: all clients (CLI, SDK, browser, curl)
  → Example: "Only my EC2 role can GetObject"

CORS: answers "Which BROWSER ORIGINS can use that access?"
  → Applies to: only browser-based JavaScript requests
  → Example: "myapp.com's JavaScript is allowed to fetch from this bucket"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both must allow the request for a browser to successfully access S3:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Bucket Policy says the user/origin is allowed&lt;/li&gt;
&lt;li&gt;CORS says the browser origin is allowed&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview tip:&lt;/strong&gt; "What's the difference between a bucket policy and CORS in S3?" — Bucket policy controls who can access (any client). CORS controls which browser origins can access (browser JS only). They're complementary, not interchangeable.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  S3 Object Lock — WORM Model
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is WORM?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;WORM = Write Once, Read Many&lt;/strong&gt; — once data is written, it cannot be modified or deleted for a defined period. This is a compliance requirement in industries like finance, healthcare, and legal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Object Lock is like writing in permanent marker on a whiteboard that's been laminated — it can be read by anyone, but nobody can erase or change what's written during the protection period.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two Retention Modes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Governance Mode&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Objects cannot be modified or deleted by regular users&lt;/li&gt;
&lt;li&gt;Authorized IAM users with &lt;code&gt;s3:BypassGovernanceRetention&lt;/code&gt; permission &lt;strong&gt;can&lt;/strong&gt; bypass the lock&lt;/li&gt;
&lt;li&gt;Use for: internal policies where administrators may occasionally need override ability
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Regular user: DELETE object → ❌ Blocked
Admin with bypass permission: DELETE object → ✅ Allowed (with override header)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Compliance Mode&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Objects cannot be modified or deleted by &lt;strong&gt;anyone&lt;/strong&gt; — including root user and AWS Support&lt;/li&gt;
&lt;li&gt;Once set, the retention period cannot be shortened&lt;/li&gt;
&lt;li&gt;Use for: regulated industries (SEC Rule 17a-4, HIPAA, FINRA) where even admins cannot bypass
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Regular user: DELETE object → ❌ Blocked
Admin:        DELETE object → ❌ Blocked
Root user:    DELETE object → ❌ Blocked
AWS Support:  Cannot help delete → ❌ Blocked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Compliance Mode is irreversible for the retention period.&lt;/strong&gt; Think carefully before setting it — if you set a 10-year retention in Compliance Mode, that data cannot be deleted for 10 years by anyone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Two Lock Mechanisms
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Retention Period&lt;/strong&gt; — protects the object version until a specific date&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object uploaded with retention: 2026-01-01 to 2036-01-01 (10 years)
→ Cannot be deleted or modified until 2036-01-01
→ After that date, normal rules apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Legal Hold&lt;/strong&gt; — protects indefinitely until explicitly removed (no date)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object placed under Legal Hold
→ Cannot be deleted until Legal Hold is removed
→ No expiry date — remains until someone with s3:PutObjectLegalHold removes it
→ Used during litigation: "preserve everything related to this case"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Requirements &amp;amp; Use Cases
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Requirements to enable Object Lock:&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;✅ Must enable at bucket creation (cannot enable on existing bucket)
✅ Versioning is automatically enabled and required
✅ Cannot disable versioning once Object Lock is active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common use cases:&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;Industry&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Finance&lt;/td&gt;
&lt;td&gt;SEC 17a-4: emails and records for 7 years&lt;/td&gt;
&lt;td&gt;Compliance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Healthcare&lt;/td&gt;
&lt;td&gt;HIPAA: patient records retention&lt;/td&gt;
&lt;td&gt;Compliance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legal&lt;/td&gt;
&lt;td&gt;Litigation hold on documents&lt;/td&gt;
&lt;td&gt;Legal Hold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup&lt;/td&gt;
&lt;td&gt;Ransomware-proof backups&lt;/td&gt;
&lt;td&gt;Governance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Government&lt;/td&gt;
&lt;td&gt;Regulatory audit records&lt;/td&gt;
&lt;td&gt;Compliance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert scenario:&lt;/strong&gt; "A financial company needs to store trade records for 7 years and ensure not even admins can delete them." → S3 Object Lock in Compliance Mode with a 7-year retention period. Governance Mode would be wrong here because admins could bypass it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Object Lock vs Versioning vs Delete Marker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Versioning alone:
  DELETE → adds delete marker, data still recoverable

Versioning + Object Lock (Governance):
  DELETE → blocked for regular users, admins can bypass
  Object still exists until retention period ends

Versioning + Object Lock (Compliance):
  DELETE → blocked for everyone, no bypass possible
  Object guaranteed to exist until retention date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VERSIONING
  Upload same key → new version created (old preserved)
  Standard delete → adds Delete Marker (data not gone)
  Permanent delete → must delete each version by Version ID
  Each version = full copy, not delta → watch storage costs
  Fix: Lifecycle rule to expire non-current versions

SUSPEND VERSIONING
  Cannot fully disable once enabled — only suspend
  New uploads after suspension → null Version ID
  Existing versions → all preserved, untouched
  Re-enable → new uploads get UUIDs again

BUCKET TYPES
  General Purpose = all features, multi-AZ, everyday use
  Directory Bucket = single AZ, 10x faster, no versioning/lifecycle/lock

OLTP vs OLAP
  OLTP → transactions, fast R/W, RDS/Aurora/DynamoDB
  OLAP → analytics, large scans, Redshift/Athena
  Separate them in architecture — never run analytics on OLTP DB

STATIC HOSTING
  S3 serves HTML/CSS/JS/images directly
  Cannot run server-side code (Node, PHP, Python)
  Must disable Block Public Access + add public read policy
  Add CloudFront for HTTPS + custom domain in production

CORS
  Bucket Policy = WHO can access (all clients)
  CORS = WHICH BROWSER ORIGINS can access (browsers only)
  Both must allow request for browser JS to work
  Configure in bucket's Permissions → Cross-origin resource sharing

OBJECT LOCK (WORM)
  Must enable at bucket creation
  Requires versioning (auto-enabled)
  Governance = lock with admin bypass
  Compliance = lock with NO bypass (not even root)
  Retention Period = until specific date
  Legal Hold = indefinite until removed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: What happens when you delete an object in a versioned S3 bucket?&lt;/strong&gt;&lt;br&gt;
S3 does not delete the actual data. Instead, it adds a &lt;strong&gt;Delete Marker&lt;/strong&gt; — a special placeholder with its own Version ID. The object appears gone (GET returns 404), but all versions still exist and are still billed. To permanently delete, you must explicitly delete each version by its Version ID and remove the delete marker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: Does S3 versioning use delta/incremental storage like git?&lt;/strong&gt;&lt;br&gt;
No. Each version is stored as a complete, independent object with its own Version ID. Uploading a 10 MB file 10 times creates 100 MB of stored data, not 10 MB plus incremental diffs. This makes Lifecycle rules to expire non-current versions essential for cost control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: What is the difference between Governance Mode and Compliance Mode in S3 Object Lock?&lt;/strong&gt;&lt;br&gt;
Both prevent object modification and deletion. Governance Mode allows authorized IAM users with the bypass permission to override the lock — useful for internal policies where administrators need flexibility. Compliance Mode cannot be bypassed by anyone, including root user and AWS — used for strict regulatory requirements where data immutability must be guaranteed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: When does S3 CORS apply and when does the bucket policy apply?&lt;/strong&gt;&lt;br&gt;
The bucket policy applies to all access — CLI, SDK, curl, and browser — and controls who is allowed to make requests. CORS applies only to browser-based JavaScript requests and controls which origins (domains) browsers are allowed to make requests from. Both must permit the request for a browser to successfully access S3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: What is the difference between a General Purpose and Directory Bucket?&lt;/strong&gt;&lt;br&gt;
General Purpose is the standard S3 bucket — supports all features (versioning, lifecycle, object lock, all storage classes), data is replicated across multiple AZs, suitable for all workloads. Directory Bucket is purpose-built for high-performance single-AZ workloads — up to 10x faster for small objects, but supports only Express One Zone storage class, no versioning, no lifecycle rules, no object lock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: Can you disable versioning after enabling it on a bucket?&lt;/strong&gt;&lt;br&gt;
No. Once versioning is enabled, it can only be suspended — never fully disabled. Suspension stops new versions from being created (new uploads get null version ID) but all existing versions are permanently preserved. You can re-enable versioning at any time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: What is the difference between OLTP and OLAP? Give AWS examples.&lt;/strong&gt;&lt;br&gt;
OLTP handles real-time operational transactions — short, fast read/write queries, high concurrency, current data. AWS: RDS, Aurora, DynamoDB. OLAP handles large-scale analytical queries across historical data — complex aggregations, columnar storage, read-heavy. AWS: Redshift, Athena. They're always kept separate in architecture because analytical queries would overwhelm and destabilize a transactional database.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔬 Practice Tasks
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Versioning lab:&lt;/strong&gt; Enable versioning on a bucket. Upload &lt;code&gt;test.txt&lt;/code&gt;. Upload a modified version with the same name. List all versions via CLI (&lt;code&gt;aws s3api list-object-versions&lt;/code&gt;). Delete the object via Console. Confirm the delete marker was created. Restore the object by deleting the delete marker. Then permanently delete all versions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lifecycle + Versioning:&lt;/strong&gt; Create a bucket with versioning enabled. Add a Lifecycle rule to expire non-current versions after 7 days. Upload the same file 5 times. Verify all 5 versions appear. Wait (or check back after 7 days) to confirm old versions are auto-expired.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Static website:&lt;/strong&gt; Create a bucket, disable Block Public Access, enable static website hosting, upload a simple &lt;code&gt;index.html&lt;/code&gt; and &lt;code&gt;error.html&lt;/code&gt;, add the public read bucket policy, and access the S3 website endpoint in your browser.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CORS lab:&lt;/strong&gt; Create a bucket with an image. Try to fetch it via JavaScript in your browser's console from a different origin — observe the CORS error. Add a CORS rule allowing &lt;code&gt;*&lt;/code&gt; as origin. Retry and confirm it succeeds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Object Lock:&lt;/strong&gt; Create a new bucket with Object Lock enabled (Governance Mode). Upload a file with a 1-day retention period. Try to delete it — confirm it's blocked. Check what happens when you try to overwrite it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write the policy:&lt;/strong&gt; Without notes, write a bucket policy that: (a) allows public GET on all objects, (b) denies all HTTP (non-HTTPS) requests, and (c) allows a specific IAM role to PUT objects.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;AWS S3 Extended Concepts | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AWS S3 Deep Dive — Objects, Encryption, Bucket Policies &amp; Everything In Between</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Mon, 20 Jul 2026 13:27:36 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-s3-deep-dive-objects-encryption-bucket-policies-everything-in-between-3k8k</link>
      <guid>https://dev.to/tejas_shinkar/aws-s3-deep-dive-objects-encryption-bucket-policies-everything-in-between-3k8k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This session goes deep into S3 — the most widely used AWS service and a guaranteed topic in every AWS certification and DevOps interview.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Why S3 Matters + Quick Recap&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;JSON &amp;amp; YAML — Why They Matter in AWS&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;API Calls in AWS (GET, PUT, DELETE)&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Region Selection — Why It Matters for S3&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;S3 Buckets — Rules &amp;amp; Constraints&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;S3 Objects — What They Actually Are&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;S3 Object URL Structure&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;S3 Storage Classes&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;S3 Versioning&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;S3 Encryption — SSE-S3, SSE-KMS, SSE-C, CSE&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;S3 Bucket Keys — KMS Cost Optimization&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Bucket Policies — Ownership &amp;amp; ACL Settings&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;S3 Access Control — Policies, ACLs, Public Access&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Explicit Deny — The Override Rule&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;AWS CloudTrail + S3 Audit Logging&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;VPC — Brief Introduction&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;S3 CLI Commands&lt;/td&gt;
&lt;td&gt;Practical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;boto3 — S3 with Python&lt;/td&gt;
&lt;td&gt;Practical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;Interview Questions&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Practice Tasks&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why S3 Matters
&lt;/h2&gt;

&lt;p&gt;S3 (Simple Storage Service) is arguably the most important AWS service to understand — it appears in almost every architecture, every certification, and every DevOps workflow. Backups, static websites, data lakes, CI/CD artifacts, ML training data, log archives — S3 underpins all of it.&lt;/p&gt;

&lt;p&gt;Before diving into encryption and policies, two foundational things from this session need to be covered first: JSON/YAML code and API calls — because everything in S3 (and AWS) is built on top of these.&lt;/p&gt;




&lt;h2&gt;
  
  
  JSON &amp;amp; YAML — Why Interviewers Ask You to Write Them
&lt;/h2&gt;

&lt;p&gt;In real organizations, nobody clicks through the AWS Console to create resources. Everything is written as &lt;strong&gt;code&lt;/strong&gt; — JSON or YAML — and executed via CLI, CloudFormation, or Terraform. Interviewers increasingly ask candidates to &lt;strong&gt;write infrastructure code on the spot&lt;/strong&gt; (sometimes called "vibe coding").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON (JavaScript Object Notation):&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;"AWSTemplateFormatVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2010-09-09"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resources"&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;"MyEC2Instance"&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;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AWS::EC2::Instance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Properties"&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;"ImageId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ami-0abcdef1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"InstanceType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"KeyName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-key-pair"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MyServer"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;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;YAML (Yet Another Markup Language) — same thing, cleaner to read:&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="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2010-09-09"&lt;/span&gt;
&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyEC2Instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EC2::Instance&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ImageId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ami-0abcdef1234567890&lt;/span&gt;
      &lt;span class="na"&gt;InstanceType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;t2.micro&lt;/span&gt;
      &lt;span class="na"&gt;KeyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-key-pair&lt;/span&gt;
      &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Name&lt;/span&gt;
          &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyServer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key differences:&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;&lt;/th&gt;
&lt;th&gt;JSON&lt;/th&gt;
&lt;th&gt;YAML&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Syntax&lt;/td&gt;
&lt;td&gt;Curly braces &lt;code&gt;{}&lt;/code&gt;, quotes &lt;code&gt;""&lt;/code&gt;, commas&lt;/td&gt;
&lt;td&gt;Indentation, colons &lt;code&gt;:&lt;/code&gt;, cleaner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readability&lt;/td&gt;
&lt;td&gt;Verbose, harder to read&lt;/td&gt;
&lt;td&gt;Clean, easier to write by hand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comments&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;td&gt;✅ Supported with &lt;code&gt;#&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Used in AWS for&lt;/td&gt;
&lt;td&gt;API responses, Lambda, IAM policies&lt;/td&gt;
&lt;td&gt;CloudFormation, SAM, CodePipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error-prone&lt;/td&gt;
&lt;td&gt;Missing comma or brace&lt;/td&gt;
&lt;td&gt;Wrong indentation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;S3 Bucket Policy in JSON (what you'll write in real interviews):&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowPublicRead"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&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="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;"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="s2"&gt;"arn:aws:s3:::my-bucket-name/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;IAM Policy in YAML (CloudFormation):&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="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::IAM::Policy&lt;/span&gt;
&lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PolicyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S3ReadPolicy&lt;/span&gt;
  &lt;span class="na"&gt;PolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2012-10-17"&lt;/span&gt;
    &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
        &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:GetObject&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;s3:ListBucket&lt;/span&gt;
        &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;arn:aws:s3:::my-bucket&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;arn:aws:s3:::my-bucket/*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview tip:&lt;/strong&gt; Practice writing a basic S3 bucket policy and EC2 IAM role in both JSON and YAML without looking things up. Interviewers genuinely ask for this now.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  API Calls in AWS — How Everything Actually Works
&lt;/h2&gt;

&lt;p&gt;Every click in the AWS Console, every CLI command, every boto3 call — all of it is ultimately an &lt;strong&gt;HTTP API call&lt;/strong&gt; under the hood. Understanding this is essential for DevOps and automation work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS uses standard HTTP methods:&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;Method&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;S3 Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read / retrieve data&lt;/td&gt;
&lt;td&gt;Download an object, list buckets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create or replace&lt;/td&gt;
&lt;td&gt;Upload an object, create a bucket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create or trigger&lt;/td&gt;
&lt;td&gt;Initiate multipart upload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DELETE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove&lt;/td&gt;
&lt;td&gt;Delete an object or bucket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HEAD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get metadata only&lt;/td&gt;
&lt;td&gt;Check if object exists without downloading&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;EC2 → S3 interaction via API:&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
    │
    │  PUT /my-bucket/backup.zip  (upload)
    │  GET /my-bucket/config.json (download)
    │  DELETE /my-bucket/old.log  (delete)
    ▼
S3 API Endpoint: https://s3.ap-south-1.amazonaws.com
    │
    ▼
S3 Bucket (my-bucket)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice, your application on EC2 uses the AWS SDK (boto3 in Python, or the Java/Node SDK) which converts your code calls into these signed HTTP API requests automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Signature Version 4&lt;/strong&gt; — all API calls must be signed with your credentials so AWS can verify identity. The SDK handles this for you when using IAM roles (recommended) or access keys (not recommended).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;DevOps relevance:&lt;/strong&gt; When you write a Python script with boto3 to copy files to S3, you're making PUT API calls. When your Lambda function reads from DynamoDB, it's making GET API calls. The console is just a visual wrapper over the same APIs.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Region Selection — Why It Matters for S3
&lt;/h2&gt;

&lt;p&gt;S3 is a global service in name, but buckets are &lt;strong&gt;created in a specific Region&lt;/strong&gt;. Choosing the right region affects:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Impact on S3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Store data in Region closest to your users/EC2 → lower latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;S3 pricing varies by Region (US East is typically cheapest)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Transfer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transferring data between Regions costs money — keep S3 and EC2 in same Region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compliance / Regulatory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GDPR (EU), RBI (India) may legally require data in specific regions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Disaster Recovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cross-Region Replication (CRR) copies to another Region for DR&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Practical rule:&lt;/strong&gt; Always create your S3 bucket in the same Region as the EC2 instances or services that will use it. Cross-region data transfer is charged per GB and adds latency.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  S3 Buckets — Rules &amp;amp; Constraints
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;bucket&lt;/strong&gt; is the container for your objects in S3. Think of it like a top-level folder — except with globally unique naming rules and no nesting of buckets inside buckets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bucket Naming Rules
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Must be globally unique — no two buckets in the world can share a name
✅ 3 to 63 characters long
✅ Lowercase letters, numbers, and hyphens only
✅ Must start with a letter or number
✅ Must not start with "xn--" (reserved)
✅ Must not end with "-s3alias"
❌ No uppercase letters
❌ No underscores
❌ No IP address format (e.g., 192.168.1.1)
❌ No spaces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good names:&lt;/strong&gt; &lt;code&gt;tejas-dev-backups&lt;/code&gt;, &lt;code&gt;company-prod-logs-2026&lt;/code&gt;, &lt;code&gt;my-static-site-assets&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad names:&lt;/strong&gt; &lt;code&gt;My_Bucket&lt;/code&gt;, &lt;code&gt;123.456.789.0&lt;/code&gt;, &lt;code&gt;TejasBucket&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Bucket Facts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One Region per bucket&lt;/strong&gt; — created in a specific Region, stays there&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No size limit&lt;/strong&gt; on total bucket storage — scales infinitely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5 TB max per individual object&lt;/strong&gt; — single file can't exceed 5TB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft limit of 100 buckets per account&lt;/strong&gt; (can request increase to 1000)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No nested buckets&lt;/strong&gt; — you cannot put a bucket inside a bucket&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bucket names in URLs&lt;/strong&gt; — because names are globally unique, they're used directly in URLs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  S3 Objects — What They Actually Are
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;object&lt;/strong&gt; is any file stored in S3. Unlike a traditional file system, S3 is &lt;strong&gt;flat&lt;/strong&gt; — there are no real folders (just prefixes that look like folders).&lt;/p&gt;

&lt;h3&gt;
  
  
  Object Components
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Key&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The full "path" of the object. e.g., &lt;code&gt;reports/2026/january/sales.csv&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Value&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The actual file content (bytes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Version ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unique ID if versioning is enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Key-value pairs about the object (content-type, custom tags)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ETag&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MD5 hash of the object (used to verify integrity)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage Class&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Standard, IA, Glacier, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The "Folder" Illusion
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bucket: my-data
Objects stored as:
  reports/2026/sales.csv    ← key includes the "path"
  reports/2026/revenue.csv
  images/logo.png
  images/banner.jpg
  config.json

There are NO real folders. "reports/2026/" is just a prefix.
The console shows them as folders for readability — but it's flat storage underneath.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Object Size Limits
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Upload type&lt;/th&gt;
&lt;th&gt;Size limit&lt;/th&gt;
&lt;th&gt;When to use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single PUT upload&lt;/td&gt;
&lt;td&gt;Up to 5 GB&lt;/td&gt;
&lt;td&gt;Small to medium files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multipart upload&lt;/td&gt;
&lt;td&gt;Up to 5 TB&lt;/td&gt;
&lt;td&gt;Files larger than 100 MB (recommended)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum part size&lt;/td&gt;
&lt;td&gt;5 MB&lt;/td&gt;
&lt;td&gt;For multipart upload chunks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;Best practice:&lt;/strong&gt; Use multipart upload for any file over 100 MB. It's faster (parallel chunks), more reliable (resume on failure), and required above 5 GB.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  S3 Object URL Structure
&lt;/h2&gt;

&lt;p&gt;Every object in S3 has a predictable, deterministic URL. Understanding the format is important for linking to files, debugging access issues, and building applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL Formats
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Path-style URL (older, being deprecated):&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;https://s3.&amp;lt;region&amp;gt;.amazonaws.com/&amp;lt;bucket-name&amp;gt;/&amp;lt;object-key&amp;gt;
https://s3.ap-south-1.amazonaws.com/tejas-dev/reports/sales.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Virtual-hosted-style URL (current standard):&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;https://&amp;lt;bucket-name&amp;gt;.s3.&amp;lt;region&amp;gt;.amazonaws.com/&amp;lt;object-key&amp;gt;
https://tejas-dev.s3.ap-south-1.amazonaws.com/reports/sales.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Breaking it down:&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;https://tejas-dev.s3.ap-south-1.amazonaws.com/reports/2026/sales.csv
        │          │   │                        │
        bucket     │   region                   object key (full path)
                   S3 service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  URL Validity
&lt;/h3&gt;

&lt;p&gt;An S3 object URL is only valid while:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The object actually exists in the bucket&lt;/li&gt;
&lt;li&gt;The object is publicly accessible (or you're using a pre-signed URL with a valid expiry)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you delete the object, the URL returns a 404 — the URL structure stays the same but there's nothing at that address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-Signed URLs
&lt;/h3&gt;

&lt;p&gt;For private objects, you can generate a &lt;strong&gt;pre-signed URL&lt;/strong&gt; — a temporary link with an expiry time that grants access without making the object public.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Generate a URL valid for 1 hour
&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;get_object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Params&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;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;my-private-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;Key&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;secret-file.pdf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;  &lt;span class="c1"&gt;# seconds
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# https://my-private-bucket.s3.ap-south-1.amazonaws.com/secret-file.pdf?X-Amz-Signature=...&amp;amp;X-Amz-Expires=3600
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Common use case:&lt;/strong&gt; Share a download link for a private file with a customer for 24 hours — pre-signed URL with &lt;code&gt;ExpiresIn=86400&lt;/code&gt;. Link expires automatically.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  S3 Storage Classes
&lt;/h2&gt;

&lt;p&gt;S3 isn't one-size-fits-all for storage. Different classes trade cost vs access speed vs retrieval time.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Storage Class&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Retrieval&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;&lt;strong&gt;S3 Standard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frequently accessed data&lt;/td&gt;
&lt;td&gt;Milliseconds&lt;/td&gt;
&lt;td&gt;Highest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3 Intelligent-Tiering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unknown or changing access patterns&lt;/td&gt;
&lt;td&gt;Milliseconds&lt;/td&gt;
&lt;td&gt;Moderate (auto-tiered)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3 Standard-IA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Infrequent access, needs fast retrieval&lt;/td&gt;
&lt;td&gt;Milliseconds&lt;/td&gt;
&lt;td&gt;Lower storage, retrieval fee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3 One Zone-IA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Infrequent, reproducible data, one AZ only&lt;/td&gt;
&lt;td&gt;Milliseconds&lt;/td&gt;
&lt;td&gt;Lower than Standard-IA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3 Glacier Instant&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Archive, accessed once a quarter&lt;/td&gt;
&lt;td&gt;Milliseconds&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3 Glacier Flexible&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Archive, minutes-to-hours retrieval ok&lt;/td&gt;
&lt;td&gt;Minutes to hours&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3 Glacier Deep Archive&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Long-term archive, accessed once a year&lt;/td&gt;
&lt;td&gt;Up to 12 hours&lt;/td&gt;
&lt;td&gt;Cheapest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Simple decision guide:&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;Serving to users daily?              → Standard
Don't know access pattern?           → Intelligent-Tiering (auto-manages)
Accessed once a month, need fast?    → Standard-IA
Old logs, rarely accessed?           → Glacier Instant Retrieval
Legal archive, won't need for years? → Glacier Deep Archive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lifecycle Policies (Auto-Tiering)
&lt;/h3&gt;

&lt;p&gt;Just like EFS, you can set rules to automatically move objects between storage classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Day 0    → Upload to Standard
Day 30   → Auto-move to Standard-IA
Day 90   → Auto-move to Glacier Instant
Day 365  → Auto-move to Glacier Deep Archive
Day 2555 → Auto-delete (7 years, for compliance)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert tip:&lt;/strong&gt; "How do you minimize S3 storage costs for logs that need to be kept for 7 years but rarely accessed after 30 days?" → Lifecycle policy transitioning to Glacier.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  S3 Versioning
&lt;/h2&gt;

&lt;p&gt;When versioning is enabled, S3 keeps every version of every object — overwrites create a new version instead of replacing, and deletes add a "delete marker" instead of actually deleting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upload file.txt (v1)      → Version ID: aaa111
Upload file.txt (v2)      → Version ID: bbb222  ← current version
Upload file.txt (v3)      → Version ID: ccc333  ← current version

GET file.txt              → returns v3 (current)
GET file.txt?versionId=aaa111 → returns v1
DELETE file.txt           → adds delete marker, v3 still exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why versioning matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accidental overwrite? Restore previous version instantly&lt;/li&gt;
&lt;li&gt;Accidental delete? Remove the delete marker to recover&lt;/li&gt;
&lt;li&gt;Required for Cross-Region Replication&lt;/li&gt;
&lt;li&gt;Required for S3 Object Lock (compliance)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Cost warning:&lt;/strong&gt; Versioning stores every version of every file. An object updated 100 times = 100 stored copies. Enable Lifecycle Policies to expire old versions, otherwise storage costs grow silently.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  S3 Encryption — Four Methods
&lt;/h2&gt;

&lt;p&gt;Encryption in S3 has two dimensions: &lt;strong&gt;at rest&lt;/strong&gt; (stored on disk) and &lt;strong&gt;in transit&lt;/strong&gt; (moving over the network). Transit is always HTTPS — the four methods below are all about &lt;strong&gt;at rest&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSE-S3 (Server-Side Encryption with S3-Managed Keys)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AWS manages everything — keys are created, rotated, and used by S3 automatically&lt;/li&gt;
&lt;li&gt;Enabled by default on all new buckets (since Jan 2023)&lt;/li&gt;
&lt;li&gt;Zero configuration needed&lt;/li&gt;
&lt;li&gt;AES-256 encryption&lt;/li&gt;
&lt;li&gt;You have no control over the keys
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upload file → S3 auto-encrypts with its own key → stored encrypted
Download → S3 auto-decrypts → you get the file
You never see or touch the key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; You need encryption but don't have compliance requirements about key control.&lt;/p&gt;




&lt;h3&gt;
  
  
  SSE-KMS (Server-Side Encryption with AWS KMS Keys)
&lt;/h3&gt;

&lt;p&gt;Used mostly&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upload flow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You upload file.pdf
      ↓
S3 sends request to AWS KMS: "Give me a data encryption key"
      ↓
KMS generates a data key (encrypted + plaintext versions)
      ↓
S3 uses plaintext key to encrypt your file
      ↓
S3 stores: encrypted file + encrypted key (plaintext key discarded)
      ↓
Your file sits on disk: fully encrypted ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Download flow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You request file.pdf
      ↓
S3 checks your IAM permissions (must have s3:GetObject)
      ↓
S3 checks your KMS permissions (must have kms:Decrypt)
      ↓
S3 sends encrypted key to KMS: "Decrypt this"
      ↓
KMS decrypts the key → returns plaintext key
      ↓
S3 uses plaintext key to decrypt your file
      ↓
You receive the original file ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why SSE-KMS over SSE-S3?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You control the KMS key (create, rotate, disable, delete)&lt;/li&gt;
&lt;li&gt;Full audit trail — every KMS key usage logged in CloudTrail&lt;/li&gt;
&lt;li&gt;Granular access control — you can restrict who can use the key via KMS key policy&lt;/li&gt;
&lt;li&gt;Required by many compliance standards (HIPAA, PCI-DSS, FedRAMP)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Downside:&lt;/strong&gt; Every object upload/download = one KMS API call. KMS has a cost per API call and rate limits.&lt;/p&gt;




&lt;h3&gt;
  
  
  SSE-C (Server-Side Encryption with Customer-Provided Keys)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You provide your own encryption key with each request&lt;/li&gt;
&lt;li&gt;AWS uses it to encrypt/decrypt, but &lt;strong&gt;never stores your key&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You are 100% responsible for key management
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upload: You send (file + your-key) → S3 encrypts → discards your-key
Download: You send (object-key + your-key) → S3 decrypts → returns file
Lose your key = lose your data permanently (no recovery)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; You have strict regulatory requirements that AWS must never hold your encryption key even temporarily.&lt;/p&gt;




&lt;h3&gt;
  
  
  CSE (Client-Side Encryption)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You encrypt the file &lt;strong&gt;before&lt;/strong&gt; uploading it to S3&lt;/li&gt;
&lt;li&gt;S3 just stores whatever bytes you send — it has no idea the content is encrypted&lt;/li&gt;
&lt;li&gt;You manage all encryption/decryption on your side
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your app: file → encrypt locally → send encrypted bytes → S3 stores them
Your app: download encrypted bytes → decrypt locally → original file
AWS sees only ciphertext — never the original data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; Maximum data confidentiality — even AWS employees cannot read your data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encryption Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Who manages key&lt;/th&gt;
&lt;th&gt;AWS sees plaintext?&lt;/th&gt;
&lt;th&gt;Audit trail&lt;/th&gt;
&lt;th&gt;Use when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSE-S3&lt;/td&gt;
&lt;td&gt;AWS (automatic)&lt;/td&gt;
&lt;td&gt;Internally yes&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Default, no compliance needs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE-KMS&lt;/td&gt;
&lt;td&gt;You (via KMS)&lt;/td&gt;
&lt;td&gt;Internally yes&lt;/td&gt;
&lt;td&gt;Full (CloudTrail)&lt;/td&gt;
&lt;td&gt;Compliance, audit requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE-C&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Strict key control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSE&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Maximum confidentiality&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  S3 Bucket Keys — Reducing KMS Costs by 99%
&lt;/h2&gt;

&lt;p&gt;With SSE-KMS, every single object upload/download triggers a KMS API call. For buckets with millions of objects, this generates millions of KMS calls — which have both a per-call cost and API rate limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S3 Bucket Keys&lt;/strong&gt; solve this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without Bucket Key:&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;100 object uploads → 100 KMS API calls → $$$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With Bucket Key enabled:&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;S3 generates one bucket-level data key from KMS
That key is cached in S3 for a time period
100 object uploads → 1 KMS API call + 99 local operations → $ (99% fewer calls)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Instead of going to the bank (KMS) to get cash for every purchase, you withdraw a stack of cash once and use it locally for many purchases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces KMS API costs by up to &lt;strong&gt;99%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Reduces KMS request rate (avoids throttling)&lt;/li&gt;
&lt;li&gt;Transparent — encryption strength is unchanged&lt;/li&gt;
&lt;li&gt;Enable it when creating the bucket or in bucket properties&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert tip:&lt;/strong&gt; "A company uses SSE-KMS on an S3 bucket with millions of daily uploads and is hitting KMS rate limits. What's the fix?" → Enable S3 Bucket Keys.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  AWS CloudTrail + S3 Audit Logging
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CloudTrail&lt;/strong&gt; records every API call made to AWS services — who did what, when, from where. For S3 with KMS encryption, it becomes your full audit trail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What CloudTrail logs for S3 + KMS:&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;Who:    IAM user/role that accessed the object
What:   s3:GetObject, s3:PutObject, kms:Decrypt, kms:GenerateDataKey
When:   Timestamp of the request
Where:  Source IP address
Result: Success or failure (and why if failed)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Two types of S3 events in CloudTrail:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Management Events&lt;/strong&gt; — bucket-level operations (CreateBucket, DeleteBucket, PutBucketPolicy) — logged by default&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Events&lt;/strong&gt; — object-level operations (GetObject, PutObject, DeleteObject) — must be explicitly enabled (generates high volume)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;S3 Server Access Logging (separate from CloudTrail):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detailed request logs written directly to another S3 bucket&lt;/li&gt;
&lt;li&gt;Every HTTP request to the bucket: requester IP, request type, response code, bytes transferred&lt;/li&gt;
&lt;li&gt;Good for access pattern analysis and billing investigation&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;DevOps/Security use case:&lt;/strong&gt; Enable CloudTrail data events for your S3 bucket to detect unauthorized access. If someone outside your organization downloads a sensitive file, CloudTrail has the full record — user, IP, timestamp, object key.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Bucket Policies — Object Ownership &amp;amp; ACL Settings
&lt;/h2&gt;

&lt;p&gt;When multiple AWS accounts are involved (or when files are uploaded by external parties), there are three ownership models that control who owns uploaded objects and how ACLs work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three Object Ownership Settings
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Bucket Owner Enforced (Recommended)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All objects in the bucket are always owned by the bucket owner&lt;/li&gt;
&lt;li&gt;ACLs are completely disabled&lt;/li&gt;
&lt;li&gt;All access must be controlled via bucket policies or IAM policies only&lt;/li&gt;
&lt;li&gt;Simplest and most secure model
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;External account uploads file → bucket owner automatically owns it
No ACL confusion, no cross-account ownership issues
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Bucket Owner Preferred&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bucket owner takes ownership IF the uploader includes &lt;code&gt;bucket-owner-full-control&lt;/code&gt; ACL&lt;/li&gt;
&lt;li&gt;ACLs are still enabled&lt;/li&gt;
&lt;li&gt;Gives flexibility while nudging toward central ownership
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;External account uploads with bucket-owner-full-control ACL
→ bucket owner gets ownership ✅
External account uploads without that ACL
→ external account retains ownership ⚠️
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Object Writer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The account that uploads an object owns it (legacy default behavior)&lt;/li&gt;
&lt;li&gt;Can cause situations where bucket owner cannot read/delete objects in their own bucket&lt;/li&gt;
&lt;li&gt;ACLs enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;Best practice:&lt;/strong&gt; Always use &lt;strong&gt;Bucket Owner Enforced&lt;/strong&gt; for new buckets. It's simpler, removes ACL complexity, and avoids cross-account ownership confusion that has caused real security incidents.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  S3 Access Control — Full Picture
&lt;/h2&gt;

&lt;p&gt;S3 has multiple layers of access control that work together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request to access S3 object
        │
        ▼
1. IAM Policy — does the requester's IAM user/role allow this?
        │
        ▼
2. Bucket Policy — does the bucket policy allow this request?
        │
        ▼
3. S3 Block Public Access — is public access blocked at account/bucket level?
        │
        ▼
4. ACL (if enabled) — does the object/bucket ACL permit access?
        │
        ▼
Access Granted or Denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;All applicable policies are evaluated together. An explicit Deny at ANY layer = denied, regardless of allows elsewhere.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bucket Policy
&lt;/h3&gt;

&lt;p&gt;A JSON document attached to the bucket that defines who can access it and what they can do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowEC2Access"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"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::123456789:role/EC2-S3-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="s2"&gt;"arn:aws:s3:::my-bucket/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DenyDeleteForEveryone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"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;"Principal"&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="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;"s3:DeleteObject"&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:s3:::my-bucket/*"&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;h3&gt;
  
  
  S3 Block Public Access (4 Settings)
&lt;/h3&gt;

&lt;p&gt;AWS added this specifically to prevent accidental data exposure — one of the most common cloud security incidents.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;What it blocks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Block public ACLs&lt;/td&gt;
&lt;td&gt;Rejects PUT requests with public ACLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ignore public ACLs&lt;/td&gt;
&lt;td&gt;Ignores existing public ACLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block public bucket policies&lt;/td&gt;
&lt;td&gt;Rejects bucket policies that grant public access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restrict public bucket policies&lt;/td&gt;
&lt;td&gt;Restricts public access even if bucket policy allows it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;Best practice:&lt;/strong&gt; Enable all 4 Block Public Access settings at the account level. Explicitly disable only for specific buckets that genuinely need public access (e.g., static website hosting).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Explicit Deny — The Override Rule
&lt;/h2&gt;

&lt;p&gt;This is one of the most important concepts in all of AWS security — and it applies everywhere, not just S3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&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;Explicit Deny ALWAYS wins over any Allow, from any source
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Policy evaluation order:&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;1. Is there an Explicit Deny anywhere? → DENIED (done, nothing else checked)
2. Is there an Explicit Allow? → ALLOWED
3. Neither? → DENIED (implicit deny is the default)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters in practice:&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;Scenario: User has AdministratorAccess (allows everything)
          SCP says: Deny s3:DeleteBucket

Result: User CANNOT delete S3 buckets — despite having AdministratorAccess
The Deny wins. Always.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real example in a bucket 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;"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;"Principal"&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="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;"s3:*"&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:s3:::critical-data/*"&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;"Bool"&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:SecureTransport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This denies all S3 actions if the request isn't using HTTPS — even if the user has full S3 permissions elsewhere. The Deny wins.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview tip:&lt;/strong&gt; "A user has S3FullAccess but can't delete objects from a specific bucket. Why?" → There's likely an explicit Deny somewhere — in a bucket policy, SCP, or permission boundary. Explicit Deny always overrides explicit Allow.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  VPC — Brief Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;VPC = Virtual Private Cloud&lt;/strong&gt; — your own logically isolated private network inside AWS.&lt;/p&gt;

&lt;p&gt;Think of it as buying a plot of land inside a large city (AWS). Within your plot, you decide the layout — which areas are public-facing, which are private, what gets connected to the internet.&lt;/p&gt;

&lt;p&gt;Why it's relevant to S3 here: By default, S3 is accessed over the public internet (via HTTPS). For security, you can set up a &lt;strong&gt;VPC Endpoint for S3&lt;/strong&gt; — a private connection from your VPC directly to S3 without traffic leaving the AWS network.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Without VPC Endpoint:
EC2 → public internet → S3 (traffic exits AWS network)

With VPC Endpoint (Gateway type):
EC2 → VPC Endpoint → S3 (traffic stays inside AWS backbone)
     No internet gateway needed
     More secure, no data transfer costs for S3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;VPC will be covered in depth in a dedicated session. For now: VPC = your private network, and S3 can be accessed privately through it via Gateway Endpoints.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  S3 CLI Commands
&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;# List all buckets&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;ls&lt;/span&gt;

&lt;span class="c"&gt;# List objects in a bucket&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://my-bucket/
aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://my-bucket/reports/ &lt;span class="nt"&gt;--recursive&lt;/span&gt;

&lt;span class="c"&gt;# Create a bucket&lt;/span&gt;
aws s3 mb s3://my-new-bucket-name &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1

&lt;span class="c"&gt;# Upload a file&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;local-file.txt s3://my-bucket/uploads/file.txt

&lt;span class="c"&gt;# Upload entire folder&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp&lt;/span&gt; ./my-folder s3://my-bucket/my-folder/ &lt;span class="nt"&gt;--recursive&lt;/span&gt;

&lt;span class="c"&gt;# Download a file&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;s3://my-bucket/file.txt ./local-file.txt

&lt;span class="c"&gt;# Sync local folder to S3 (only uploads new/changed files)&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;sync&lt;/span&gt; ./local-folder s3://my-bucket/folder/

&lt;span class="c"&gt;# Delete an object&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;rm &lt;/span&gt;s3://my-bucket/old-file.txt

&lt;span class="c"&gt;# Delete all objects in a folder&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;rm &lt;/span&gt;s3://my-bucket/old-folder/ &lt;span class="nt"&gt;--recursive&lt;/span&gt;

&lt;span class="c"&gt;# Delete a bucket (must be empty first)&lt;/span&gt;
aws s3 rb s3://my-bucket

&lt;span class="c"&gt;# Delete bucket and all contents&lt;/span&gt;
aws s3 rb s3://my-bucket &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Copy between buckets (cross-region)&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;s3://source-bucket/file.txt s3://dest-bucket/file.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source-region&lt;/span&gt; ap-south-1 &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1

&lt;span class="c"&gt;# Set storage class on upload&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;file.txt s3://my-bucket/ &lt;span class="nt"&gt;--storage-class&lt;/span&gt; STANDARD_IA

&lt;span class="c"&gt;# Generate pre-signed URL (valid 1 hour)&lt;/span&gt;
aws s3 presign s3://my-bucket/private-file.pdf &lt;span class="nt"&gt;--expires-in&lt;/span&gt; 3600

&lt;span class="c"&gt;# Put bucket policy&lt;/span&gt;
aws s3api put-bucket-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; my-bucket &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy&lt;/span&gt; file://policy.json

&lt;span class="c"&gt;# Enable versioning&lt;/span&gt;
aws s3api put-bucket-versioning &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; my-bucket &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--versioning-configuration&lt;/span&gt; &lt;span class="nv"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  boto3 — S3 with Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ap-south-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;s3_resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&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="c1"&gt;# ── List buckets ────────────────────────────────────────────
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_buckets&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;bucket&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Buckets&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="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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | Created: &lt;/span&gt;&lt;span class="si"&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;CreationDate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ── Create bucket ───────────────────────────────────────────
&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-new-bucket-2026&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CreateBucketConfiguration&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;LocationConstraint&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;ap-south-1&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;# ── Upload file ─────────────────────────────────────────────
&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;local-file.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-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;uploads/file.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ── Upload with SSE-KMS encryption ─────────────────────────
&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sensitive.pdf&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;my-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;secure/sensitive.pdf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ExtraArgs&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;ServerSideEncryption&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;aws:kms&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;SSEKMSKeyId&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;arn:aws:kms:ap-south-1:123456789:key/abc-123&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;# ── Download file ───────────────────────────────────────────
&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-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;uploads/file.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;downloaded-file.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ── List objects in bucket ──────────────────────────────────
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_objects_v2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-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;Prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reports/&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;obj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;Contents&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="nf"&gt;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;Key: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;obj&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="si"&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;obj&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="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="c1"&gt;# ── Delete object ───────────────────────────────────────────
&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;old-file.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ── Generate pre-signed URL ─────────────────────────────────
&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;get_object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Params&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;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;my-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;Key&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;private-doc.pdf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&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;Share this URL (valid 1 hour): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&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;# ── Apply bucket policy ─────────────────────────────────────
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="n"&gt;policy&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;Version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2012-10-17&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;Statement&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;Effect&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;Deny&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;Principal&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;Action&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;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;Resource&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;arn:aws:s3:::my-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;arn:aws:s3:::my-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;Condition&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;Bool&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;aws:SecureTransport&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;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_bucket_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-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;Policy&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;policy&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;JSON vs YAML&lt;/span&gt;
  &lt;span class="s"&gt;JSON = braces/quotes/commas, API responses, IAM policies&lt;/span&gt;
  &lt;span class="s"&gt;YAML = indentation/colons, CloudFormation, SAM&lt;/span&gt;
  &lt;span class="s"&gt;Both used for infrastructure-as-code — know both&lt;/span&gt;

&lt;span class="s"&gt;API CALLS&lt;/span&gt;
  &lt;span class="s"&gt;GET    = read/retrieve&lt;/span&gt;
  &lt;span class="s"&gt;PUT    = create/replace&lt;/span&gt;
  &lt;span class="s"&gt;POST   = create/trigger&lt;/span&gt;
  &lt;span class="s"&gt;DELETE = remove&lt;/span&gt;
  &lt;span class="s"&gt;HEAD   = metadata only&lt;/span&gt;

&lt;span class="s"&gt;S3 OBJECT URL&lt;/span&gt;
  &lt;span class="s"&gt;https://&amp;lt;bucket&amp;gt;.s3.&amp;lt;region&amp;gt;.amazonaws.com/&amp;lt;key&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;Valid only while object exists and access is permitted&lt;/span&gt;
  &lt;span class="s"&gt;Pre-signed URL = temporary access for private objects&lt;/span&gt;

&lt;span class="s"&gt;STORAGE CLASSES (cost vs speed)&lt;/span&gt;
  &lt;span class="s"&gt;Standard          → frequent access, fastest, most expensive&lt;/span&gt;
  &lt;span class="s"&gt;Intelligent-Tiering → unknown patterns, auto-optimizes&lt;/span&gt;
  &lt;span class="s"&gt;Standard-IA       → monthly access, fast retrieval fee&lt;/span&gt;
  &lt;span class="s"&gt;Glacier Instant   → quarterly access, instant retrieval&lt;/span&gt;
  &lt;span class="s"&gt;Glacier Deep      → annual access, 12hr retrieval, cheapest&lt;/span&gt;

&lt;span class="s"&gt;ENCRYPTION&lt;/span&gt;
  &lt;span class="s"&gt;SSE-S3  → AWS manages key, default, zero config&lt;/span&gt;
  &lt;span class="s"&gt;SSE-KMS → you control key via KMS, audit trail, compliance&lt;/span&gt;
  &lt;span class="s"&gt;SSE-C   → you provide key per request, AWS never stores it&lt;/span&gt;
  &lt;span class="s"&gt;CSE     → you encrypt before upload, AWS sees ciphertext only&lt;/span&gt;

&lt;span class="s"&gt;S3 BUCKET KEYS&lt;/span&gt;
  &lt;span class="s"&gt;Reduces KMS API calls by 99%&lt;/span&gt;
  &lt;span class="s"&gt;One bucket-level key cached, used for multiple objects&lt;/span&gt;
  &lt;span class="s"&gt;Enable for high-volume SSE-KMS buckets&lt;/span&gt;

&lt;span class="s"&gt;BUCKET OWNERSHIP&lt;/span&gt;
  &lt;span class="s"&gt;Owner Enforced  = ACLs off, bucket owner owns all (use this)&lt;/span&gt;
  &lt;span class="s"&gt;Owner Preferred = owner gets ownership if ACL included&lt;/span&gt;
  &lt;span class="s"&gt;Object Writer   = uploader owns (legacy, avoid)&lt;/span&gt;

&lt;span class="s"&gt;EXPLICIT DENY&lt;/span&gt;
  &lt;span class="s"&gt;Always overrides any Allow, from any source&lt;/span&gt;
  &lt;span class="s"&gt;Evaluation&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Explicit Deny → Explicit Allow → Implicit Deny&lt;/span&gt;

&lt;span class="s"&gt;CLOUDTRAIL&lt;/span&gt;
  &lt;span class="s"&gt;Logs every API call&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;who, what, when, where, result&lt;/span&gt;
  &lt;span class="s"&gt;Data events (object-level) must be explicitly enabled&lt;/span&gt;
  &lt;span class="s"&gt;Use for security auditing of S3 + KMS access&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: What is the difference between an S3 bucket and an S3 object?&lt;/strong&gt;&lt;br&gt;
A bucket is the container — like a top-level folder — created in a specific Region with a globally unique name. An object is the actual file stored inside the bucket, uniquely identified by its key (path). The bucket holds the object; the object is the data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What makes an S3 object URL unique and when does it stop working?&lt;/strong&gt;&lt;br&gt;
The URL is formed from the bucket name, region, and object key — all of which are unique. It stops working when the object is deleted or when access permissions are revoked. For private objects, you need a pre-signed URL with an expiry time to share temporary access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: What is SSE-KMS and how does it work?&lt;/strong&gt;&lt;br&gt;
SSE-KMS uses AWS Key Management Service for encryption. On upload, S3 requests a data key from KMS, encrypts the file with the plaintext key, and stores both the encrypted file and the encrypted key (discarding the plaintext key). On download, S3 verifies IAM and KMS permissions, asks KMS to decrypt the encrypted key, then decrypts and returns the file. You control the KMS key and every usage is logged in CloudTrail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: What are S3 Bucket Keys and why would you enable them?&lt;/strong&gt;&lt;br&gt;
S3 Bucket Keys are a bucket-level data key cached in S3, used to encrypt multiple objects instead of calling KMS for every single operation. This reduces KMS API calls by up to 99%, significantly reducing cost and avoiding KMS rate limit throttling — essential for high-throughput S3 buckets using SSE-KMS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: What is the difference between SSE-KMS and SSE-C?&lt;/strong&gt;&lt;br&gt;
SSE-KMS means AWS generates and manages the data keys inside KMS — you control the master key but AWS handles the actual encryption. SSE-C means you provide your own encryption key with every request — AWS uses it and immediately discards it, never storing it. If you lose your SSE-C key, your data is gone forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: An IAM user has S3FullAccess but cannot delete objects from a bucket. Why?&lt;/strong&gt;&lt;br&gt;
There's an explicit Deny somewhere overriding the Allow. It could be in the bucket policy, an SCP at the Organizations level, or a permission boundary. Explicit Deny always wins over any Allow, from any source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: What is Bucket Owner Enforced and why is it recommended?&lt;/strong&gt;&lt;br&gt;
It disables ACLs entirely and ensures the bucket owner always owns all objects regardless of who uploaded them. It's the simplest and most secure model — no ACL confusion, no cross-account ownership issues, and access is controlled purely through bucket policies and IAM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q8: Write a bucket policy that denies all HTTP (non-HTTPS) access.&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="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;"Principal"&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="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;"s3:*"&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="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket"&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:s3:::my-bucket/*"&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;"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;"Bool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"aws:SecureTransport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔬 Practice Tasks
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create and configure a bucket:&lt;/strong&gt; Create an S3 bucket with versioning enabled, Block Public Access on, and a lifecycle policy that moves objects to Standard-IA after 30 days and Glacier after 90 days.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write bucket policies:&lt;/strong&gt; Write a JSON bucket policy that: (a) allows your IAM user to read all objects, (b) allows an EC2 role to upload objects, and (c) denies all non-HTTPS requests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SSE-KMS lab:&lt;/strong&gt; Enable SSE-KMS on a bucket. Upload a file. Download it. Open CloudTrail and find the &lt;code&gt;kms:GenerateDataKey&lt;/code&gt; and &lt;code&gt;kms:Decrypt&lt;/code&gt; events corresponding to your upload and download.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pre-signed URL:&lt;/strong&gt; Using boto3, upload a private file to S3, generate a pre-signed URL valid for 5 minutes, open it in your browser, wait 5 minutes, try again — confirm it expires.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CLI practice:&lt;/strong&gt; Using AWS CLI, sync a local folder to S3, list the objects, update one file locally, sync again, and confirm only the changed file was re-uploaded (not everything).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Versioning test:&lt;/strong&gt; Enable versioning on a bucket. Upload a file, modify it, upload again with the same key, upload a third time. List all versions. Retrieve v1. Delete the latest version and confirm v2 becomes current.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write the JSON and YAML:&lt;/strong&gt; Without looking at notes, write from scratch: (a) a CloudFormation YAML snippet that creates an S3 bucket with versioning, (b) an S3 bucket policy in JSON that makes all objects in &lt;code&gt;/public/*&lt;/code&gt; readable by everyone.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;AWS Session 7 — S3 Essentials | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AWS ELB &amp; Auto Scaling — Building Self-Healing, Scalable Infrastructure</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Fri, 03 Jul 2026 01:51:46 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-elb-auto-scaling-building-self-healing-scalable-infrastructure-567g</link>
      <guid>https://dev.to/tejas_shinkar/aws-elb-auto-scaling-building-self-healing-scalable-infrastructure-567g</guid>
      <description>&lt;p&gt;Elastic Load Balancing &amp;amp; Auto Scaling&lt;/p&gt;

&lt;h3&gt;
  
  
  Traffic Distribution · ASG · Scaling Policies · ALB vs NLB · Health Checks · Zero-Downtime Deployments
&lt;/h3&gt;




&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This session covers the two services that make AWS infrastructure truly production-grade: ELB and Auto Scaling.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Purpose of Load Balancing&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;How Load Balancing Works (Algorithms)&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Horizontal vs Vertical Scaling&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Scaling Policies — Dynamic, Scheduled, Predictive&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Auto Scaling Group (ASG) — Core Config&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;ASG Lifecycle &amp;amp; Key Metrics&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Health Checks &amp;amp; Replacement Policies&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Session Stickiness&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Connection Draining (Deregistration Delay)&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Instance Refresh — Zero-Downtime Deployments&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Load Balancer Types — ALB vs NLB vs GWLB&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Target Groups &amp;amp; Health Check Config&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;ALB Deep Dive — Routing Rules&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Architecture — ALB + ASG&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Monitoring &amp;amp; Troubleshooting&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Best Practices&lt;/td&gt;
&lt;td&gt;DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;AWS CLI Commands&lt;/td&gt;
&lt;td&gt;Practical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;Lab — Create ELB + Auto Scaling Group (7 Steps)&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;Cleanup Checklist&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Assignment&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why Load Balancing Exists
&lt;/h2&gt;

&lt;p&gt;Imagine a popular restaurant with one cashier — as soon as the lunch rush hits, the queue grows, the cashier gets overwhelmed, and eventually everything grinds to a halt. The fix? More cashiers, with a manager at the door directing customers to whoever is free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Load Balancer is that manager.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It sits in front of your EC2 instances and distributes incoming traffic across them — so no single server gets overwhelmed, and if one fails, the others keep running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without a Load Balancer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single point of failure — one server down means your app is down&lt;/li&gt;
&lt;li&gt;No horizontal scaling — you're stuck with one server&lt;/li&gt;
&lt;li&gt;No health checking — dead servers still receive traffic&lt;/li&gt;
&lt;li&gt;Manual failover — someone has to intervene&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With a Load Balancer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High availability across multiple AZs&lt;/li&gt;
&lt;li&gt;Horizontal scalability — add more servers on demand&lt;/li&gt;
&lt;li&gt;Automatic health checks — dead servers removed instantly&lt;/li&gt;
&lt;li&gt;Automatic failover — zero manual intervention&lt;/li&gt;
&lt;li&gt;SSL/TLS termination — offloads HTTPS decryption from backend servers&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How Load Balancing Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Request flow — what actually happens:&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;1. User's browser sends HTTP request to your domain
2. DNS resolves your domain → returns ALB's IP address
3. Request hits the Load Balancer
4. LB picks a healthy backend EC2 instance
5. Forwards request to that instance
6. Instance processes and responds
7. LB returns the response to the user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Load Distribution Algorithms:&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;Algorithm&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;th&gt;AWS Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Round Robin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requests go in rotation: 1→2→3→1→2→3&lt;/td&gt;
&lt;td&gt;ALB default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Least Connections&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Route to instance with fewest active connections&lt;/td&gt;
&lt;td&gt;Good for variable request lengths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IP Hash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Client IP always maps to same backend&lt;/td&gt;
&lt;td&gt;Sticky sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Weighted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Instance A gets 70%, B gets 30%&lt;/td&gt;
&lt;td&gt;Canary deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flow Hash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hash of protocol+IP+port&lt;/td&gt;
&lt;td&gt;NLB default for TCP/UDP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert Tip:&lt;/strong&gt; ALB uses weighted round robin by default. NLB uses flow hash for TCP/UDP traffic.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Horizontal vs Vertical Scaling
&lt;/h2&gt;

&lt;p&gt;This is one of the most fundamental concepts in cloud architecture — and a guaranteed interview question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Your app is a delivery service. Vertical scaling = buying a bigger truck. Horizontal scaling = buying more trucks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vertical Scaling (Scale Up)
&lt;/h3&gt;

&lt;p&gt;Upgrade the existing instance — stop it, change the type, restart.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;t3.micro → t3.medium → t3.large → t3.xlarge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;✅ Simple — no distributed system complexity&lt;/li&gt;
&lt;li&gt;✅ Better for databases (data consistency is easier)&lt;/li&gt;
&lt;li&gt;❌ Has a hard ceiling — biggest instance type is the limit&lt;/li&gt;
&lt;li&gt;❌ Requires downtime during the type change&lt;/li&gt;
&lt;li&gt;❌ Cannot auto-scale&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Horizontal Scaling (Scale Out)
&lt;/h3&gt;

&lt;p&gt;Add more instances — distribute load across them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 × t3.micro → 2 × t3.micro → 4 × t3.micro → back to 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;✅ Theoretically unlimited scalability&lt;/li&gt;
&lt;li&gt;✅ Fault tolerant — one fails, others keep running&lt;/li&gt;
&lt;li&gt;✅ Scales dynamically with Auto Scaling Groups&lt;/li&gt;
&lt;li&gt;✅ No downtime when scaling&lt;/li&gt;
&lt;li&gt;❌ Requires a Load Balancer to distribute traffic&lt;/li&gt;
&lt;li&gt;❌ Requires stateless app design (or sticky sessions for state)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cloud-native best practice: always use horizontal scaling with ASG.&lt;/strong&gt; Vertical scaling is for legacy systems or databases that can't easily distribute state.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Scaling Policies — Three Types
&lt;/h2&gt;

&lt;p&gt;How does AWS know &lt;em&gt;when&lt;/em&gt; to scale? Scaling Policies define the trigger.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Dynamic Scaling — Target Tracking (Most Common)
&lt;/h3&gt;

&lt;p&gt;Monitor a metric and automatically adjust to keep it at a target value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Metric: Average CPU Utilization
Target: 50%

CPU rises to 75% → ASG adds instances until CPU ≈ 50%
CPU drops to 20% → ASG removes instances until CPU ≈ 50%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS creates two CloudWatch alarms automatically — one for scale-out, one for scale-in. You don't configure alarms manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Most production workloads. Simplest to set up and maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Scheduled Scaling
&lt;/h3&gt;

&lt;p&gt;Scale at pre-defined times — for predictable, known traffic patterns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every weekday 8:30 AM  → set Desired = 6  (office hours ramp up)
Every weekday 7:00 PM  → set Desired = 2  (evening wind down)
Every Saturday 12:00 AM → set Desired = 8 (weekend traffic spike)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Business-hours traffic, batch jobs at fixed times, known seasonal spikes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Predictive Scaling (ML-based)
&lt;/h3&gt;

&lt;p&gt;AWS analyses at least 2 weeks of historical metric data, forecasts future demand, and scales EC2 instances &lt;strong&gt;before&lt;/strong&gt; traffic arrives — not in reaction to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Historical pattern: Every Friday at 5PM, traffic doubles
Predictive: ASG pre-scales at 4:45PM — instances ready before surge hits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Recurring, predictable patterns where reactive scaling causes latency spikes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert Tip:&lt;/strong&gt; Predictive Scaling requires a minimum of 2 weeks of historical data to build its forecast model.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Auto Scaling Group (ASG)
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;ASG&lt;/strong&gt; is the component that actually manages your EC2 fleet — launching, terminating, and replacing instances automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; ASG is like a staffing agency for your servers. You tell it: "I need at least 2 people, ideally 4, maximum 10 — and if anyone quits or gets sick, hire a replacement immediately."&lt;/p&gt;

&lt;h3&gt;
  
  
  The Three Capacity Numbers (Core Concept)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MAX     [4] ──── Hard ceiling. ASG will NEVER exceed this, even under extreme load
DESIRED [2] ──── ASG always tries to maintain exactly this many healthy instances
MIN     [1] ──── Hard floor. ASG will NEVER go below this, even at zero traffic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ASG constantly compares running instances against Desired capacity. If an instance fails a health check, it terminates it and launches a replacement — no human intervention needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  ASG Configuration Components
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Launch Template&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blueprint for every EC2 ASG launches — AMI, instance type, SG, User Data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Min / Desired / Max&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Capacity boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Availability Zones&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Which AZs to spread instances across (always 2+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load Balancer / Target Group&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Where to register launched instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Health Check Type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;EC2 (VM-level) or ELB (app-level) — always use ELB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scaling Policies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dynamic, scheduled, or predictive rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Termination Policy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Which instances to remove during scale-in (default: oldest)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ Always use &lt;strong&gt;Launch Templates&lt;/strong&gt; not Launch Configurations — templates support versioning, Instance Refresh, and newer EC2 features.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ASG Lifecycle — Instance States
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scale-out triggered
      ↓
  Pending ──── being launched by ASG
      ↓
  InService ── running, healthy, serving traffic ← normal operating state
      ↓
  Draining ─── marked for removal, existing connections completing
      ↓
  Terminated ─ deleted, no longer in ASG

  Unhealthy ── failed health check → immediately replaced with new instance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Health Checks &amp;amp; Replacement Policies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Two Types of Health Checks
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;EC2 Health Check (VM level)&lt;/strong&gt;&lt;br&gt;
Checks if the instance is running — system and instance status checks pass. Doesn't know if your application is actually responding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ELB Health Check (App level)&lt;/strong&gt;&lt;br&gt;
The ALB sends a real HTTP request to your configured path (e.g., &lt;code&gt;/health&lt;/code&gt;). If the app doesn't return HTTP 200, the instance is marked unhealthy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;Always use ELB health checks for web applications.&lt;/strong&gt; An instance can be "running" while Apache is crashed inside — EC2 checks miss this entirely. ELB checks catch it within 60 seconds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Health Check Configuration
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Interval&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30 seconds&lt;/td&gt;
&lt;td&gt;How often checks are sent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Timeout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5 seconds&lt;/td&gt;
&lt;td&gt;How long to wait for response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Healthy Threshold&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 consecutive successes&lt;/td&gt;
&lt;td&gt;Instance declared healthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unhealthy Threshold&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 consecutive failures&lt;/td&gt;
&lt;td&gt;Instance declared unhealthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grace Period&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;300 seconds&lt;/td&gt;
&lt;td&gt;Time after launch before checks begin (allows app boot time)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Self-Healing Flow
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Health check fails twice
      ↓
ALB marks instance "Unhealthy" → stops sending traffic to it
      ↓
ASG receives notification
      ↓
ASG terminates the unhealthy instance
      ↓
ASG launches a new replacement from Launch Template
      ↓
Replacement passes health checks → added to Target Group
      ↓
Total time: ~5-10 minutes, fully automated ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Session Stickiness (Sticky Sessions)
&lt;/h2&gt;

&lt;p&gt;By default, ALB routes each request to any healthy instance via round robin — a user's second request might go to a different server than their first. For stateless apps that's fine. For &lt;strong&gt;stateful apps&lt;/strong&gt; (shopping carts, auth sessions), the session exists only on one server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sticky Sessions&lt;/strong&gt; fixes this: ALB sets a cookie in the browser that pins the user to one specific backend instance for the session duration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User logs in → ALB routes to EC2-1 → sets cookie: "stick to EC2-1"
Next request → user sends cookie → ALB routes to EC2-1 again ✅
Session expires / cookie deleted → back to normal round robin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; Shopping carts, user authentication, stateful apps with in-memory sessions.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Uneven load distribution&lt;/li&gt;
&lt;li&gt;If the pinned instance fails, the session is lost anyway&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Proper long-term fix:&lt;/strong&gt; Externalize session state to &lt;strong&gt;ElastiCache (Redis)&lt;/strong&gt; or &lt;strong&gt;DynamoDB&lt;/strong&gt; so any instance can serve any user without needing stickiness.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Connection Draining (Deregistration Delay)
&lt;/h2&gt;

&lt;p&gt;When an instance is removed (scale-in, replacement, deployment), what happens to users mid-request on that instance?&lt;/p&gt;

&lt;p&gt;Without draining: requests are immediately dropped → users get errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connection Draining:&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;Instance marked for removal
      ↓
ALB immediately stops sending NEW requests to this instance
      ↓
ALB waits for EXISTING in-flight requests to finish
      ↓
Default wait: up to 300 seconds
      ↓
After all connections finish (or timeout) → instance terminated
      ↓
Zero dropped requests ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what enables &lt;strong&gt;zero-downtime scaling and deployments&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Instance Refresh — Zero-Downtime Deployments
&lt;/h2&gt;

&lt;p&gt;When you update your application (new AMI, new config), you need to replace all running instances with the new version. Instance Refresh automates this within ASG.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refresh strategies:&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;Strategy&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rolling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One instance at a time&lt;/td&gt;
&lt;td&gt;Slowest&lt;/td&gt;
&lt;td&gt;Lowest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rolling with Stagger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;X% in parallel (25%→50%→75%)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Blue/Green&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All new instances launched, traffic switched, old terminated&lt;/td&gt;
&lt;td&gt;Fastest&lt;/td&gt;
&lt;td&gt;Higher cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Workflow:&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;Update Launch Template (new AMI version)
      ↓
Start Instance Refresh in ASG console
      ↓
ASG launches new instance from updated template
      ↓
Health checks pass on new instance
      ↓
Old instance: connection draining → terminated
      ↓
Repeat for all instances
      ↓
All instances running new AMI — zero downtime ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;DevOps relevance:&lt;/strong&gt; Instance Refresh is the ASG equivalent of a rolling update in Kubernetes. Same concept, different tool.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Load Balancer Types — ALB vs NLB vs GWLB
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ALB&lt;/th&gt;
&lt;th&gt;NLB&lt;/th&gt;
&lt;th&gt;GWLB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OSI Layer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Layer 7 (Application)&lt;/td&gt;
&lt;td&gt;Layer 4 (Transport)&lt;/td&gt;
&lt;td&gt;Layer 3 (Network)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP, HTTPS&lt;/td&gt;
&lt;td&gt;TCP, UDP, TLS&lt;/td&gt;
&lt;td&gt;All IP protocols&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Routing basis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Path, host, header, query string&lt;/td&gt;
&lt;td&gt;IP + Port (flow hash)&lt;/td&gt;
&lt;td&gt;Transparent proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~25-100ms&lt;/td&gt;
&lt;td&gt;&amp;lt;100 microseconds&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Static IP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSL Termination&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use Case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Web apps, APIs, microservices&lt;/td&gt;
&lt;td&gt;Gaming, IoT, VoIP, real-time&lt;/td&gt;
&lt;td&gt;Firewalls, IDS/IPS appliances&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Decision guide:&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;Web app or REST API?                 → ALB (almost always)
Ultra-low latency TCP/UDP required?  → NLB
Traffic must pass through a firewall/security appliance? → GWLB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Classic Load Balancer (CLB)&lt;/strong&gt; exists but is legacy — never use for new deployments.&lt;/p&gt;

&lt;p&gt;🎯 &lt;strong&gt;Cert scenarios:&lt;/strong&gt; "Which LB supports path-based routing?" → ALB only. "Which provides a static IP?" → NLB. "Which for real-time gaming at massive scale?" → NLB.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ALB Deep Dive — Routing Rules
&lt;/h2&gt;

&lt;p&gt;ALB operates at Layer 7 — it understands HTTP. This enables routing based on the actual content of each request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Listener Rules (evaluated top-to-bottom):

Rule 1: Host = api.example.com
        → Forward to: api-target-group (3x t3.medium)

Rule 2: Host = admin.example.com
        → Forward to: admin-target-group (restricted access)

Rule 3: Path = /images/*
        → Forward to: media-target-group (or S3)

Rule 4: Path = /api/*
        → Forward to: api-target-group

Rule 5: HTTP (port 80) — any path
        → Redirect: 301 to HTTPS

Default Rule: no other rule matched
        → Forward to: web-target-group (main homepage)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One ALB can route to multiple backend services — this is the foundation of microservices architecture on AWS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Target Groups &amp;amp; Health Check Config
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Target Group&lt;/strong&gt; is a named pool of EC2 instances (or IPs, or Lambdas) that the ALB routes traffic to. One ALB → multiple target groups via listener rules.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Target Type&lt;/td&gt;
&lt;td&gt;Instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocol&lt;/td&gt;
&lt;td&gt;HTTP or HTTPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port&lt;/td&gt;
&lt;td&gt;App's listening port (80, 8080, 3000, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPC&lt;/td&gt;
&lt;td&gt;Must match your EC2 instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health Check Path&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/health&lt;/code&gt; — must return HTTP 200-299&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ When using ASG — never manually register instances to the Target Group. Let ASG handle registration/deregistration automatically as instances launch and terminate.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Architecture — ALB + ASG (Production Standard)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                     Internet Users
                           │
                       Route 53
                  (domain → ALB DNS)
                           │
               Application Load Balancer
             (Internet-facing, 2+ AZs, SG-ALB)
                           │
                     Target Group
               (health-checked instance pool)
                           │
            Auto Scaling Group (min=1, desired=2, max=4)
         ┌─────────────────┼──────────────────┐
         │                 │                  │
    EC2 (AZ-a)         EC2 (AZ-b)        EC2 (AZ-b)
    SG-EC2, :80        SG-EC2, :80       SG-EC2, :80
         │
     (shared state)
    RDS Database
    or ElastiCache

CloudWatch CPU metric → Scaling Policy → ASG scales in/out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Three architectural pillars achieved:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High Availability&lt;/strong&gt; — multi-AZ ALB and instances&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fault Tolerance&lt;/strong&gt; — ASG self-heals on any instance failure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Efficiency&lt;/strong&gt; — scale down at night, up during peaks&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Monitoring &amp;amp; Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key CloudWatch Metrics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ALB Metrics:&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;Alert When&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TargetResponseTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;gt; 500ms sustained&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RequestCount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sudden unexpected drop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HTTPCode_Target_5XX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;gt; 0 sustained&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HealthyHostCount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Below Desired capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UnHealthyHostCount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Greater than 0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;ASG Metrics:&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;What it shows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GroupDesiredCapacity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Target count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GroupInServiceInstances&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Healthy, running count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GroupPendingInstances&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Currently launching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GroupTerminatingInstances&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Being removed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Common Issues &amp;amp; Fixes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;UnHealthyHostCount &amp;gt; 0&lt;/code&gt;&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;Check: SG-EC2 allows port 80 FROM SG-ALB (not from internet)
Check: Health check path actually returns 200 (try curl from EC2)
Check: App running → systemctl status httpd
Check: Grace period → did instance have 300s to boot before checks started?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ASG not scaling despite high CPU&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;Check: Scaling policy threshold — is CPU actually crossing it?
Check: Max capacity — already at the ceiling?
Check: Cooldown — previous scale action still in cooldown?
Check: CloudWatch alarm — is it actually in ALARM state?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Requests timing out after ALB&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Check: EC2 instances responding? Try curl from inside the VPC
Check: Security group — SG-EC2 must allow HTTP FROM SG-ALB only
Check: App logs — any errors being thrown?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Session lost when instance replaced&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;Short-term: Enable sticky sessions on the Target Group
Long-term: Move session state to ElastiCache (Redis) or DynamoDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALB
  ✅ Always deploy across 2+ AZs
  ✅ Use path-based routing to consolidate microservices on one ALB
  ✅ Enable access logs for debugging and compliance
  ✅ Add HTTPS listener + redirect HTTP → HTTPS (use ACM for free cert)
  ✅ Use dedicated health check endpoint /health (not just /)

AUTO SCALING
  ✅ Use Target Tracking (simplest) over Step Scaling
  ✅ Set cooldown 300-600 seconds to avoid scaling oscillation
  ✅ Set Grace Period long enough for your app to boot (300s minimum)
  ✅ Use Launch Templates (not Launch Configurations)
  ✅ Test scaling with a load test before going to production
  ✅ Combine Scheduled + Target Tracking for predictable + reactive scaling

HEALTH CHECKS
  ✅ Always use ELB health checks (not EC2 only)
  ✅ Healthy threshold: 2, Unhealthy threshold: 2 (fast detection)
  ✅ Enable connection draining (default 300s)

SECURITY
  ✅ SG-EC2 inbound: HTTP from SG-ALB only (never 0.0.0.0/0)
  ✅ Users hit ALB only — EC2 instances never exposed to internet directly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  AWS CLI Commands
&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;# Create Application Load Balancer&lt;/span&gt;
aws elbv2 create-load-balancer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; web-app-alb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnets&lt;/span&gt; subnet-aaa subnet-bbb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--security-groups&lt;/span&gt; sg-alb-xxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scheme&lt;/span&gt; internet-facing &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; application

&lt;span class="c"&gt;# Create Target Group&lt;/span&gt;
aws elbv2 create-target-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; web-server-tg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--protocol&lt;/span&gt; HTTP &lt;span class="nt"&gt;--port&lt;/span&gt; 80 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-xxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-check-path&lt;/span&gt; /

&lt;span class="c"&gt;# Create Auto Scaling Group&lt;/span&gt;
aws autoscaling create-auto-scaling-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--auto-scaling-group-name&lt;/span&gt; web-app-asg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--launch-template&lt;/span&gt; &lt;span class="nv"&gt;LaunchTemplateName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web-server-lt,Version&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'$Latest'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--min-size&lt;/span&gt; 1 &lt;span class="nt"&gt;--max-size&lt;/span&gt; 4 &lt;span class="nt"&gt;--desired-capacity&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--availability-zones&lt;/span&gt; us-east-1a us-east-1b

&lt;span class="c"&gt;# Attach Target Group to ASG&lt;/span&gt;
aws autoscaling attach-load-balancer-target-groups &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--auto-scaling-group-name&lt;/span&gt; web-app-asg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-group-arns&lt;/span&gt; arn:aws:elasticloadbalancing:...

&lt;span class="c"&gt;# Create Target Tracking Policy (CPU 50%)&lt;/span&gt;
aws autoscaling put-scaling-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--auto-scaling-group-name&lt;/span&gt; web-app-asg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-name&lt;/span&gt; cpu-target-tracking &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-type&lt;/span&gt; TargetTrackingScaling &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-tracking-configuration&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'{"TargetValue":50.0,"PredefinedMetricSpecification":{"PredefinedMetricType":"ASGAverageCPUUtilization"}}'&lt;/span&gt;

&lt;span class="c"&gt;# Describe ASG&lt;/span&gt;
aws autoscaling describe-auto-scaling-groups &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--auto-scaling-group-names&lt;/span&gt; web-app-asg

&lt;span class="c"&gt;# Manually set desired capacity&lt;/span&gt;
aws autoscaling set-desired-capacity &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--auto-scaling-group-name&lt;/span&gt; web-app-asg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--desired-capacity&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧪 LAB — Create ELB + Auto Scaling Group
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Security Groups (Create First)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;SG-ALB:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inbound:  HTTP (80) from 0.0.0.0/0
Outbound: All traffic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;SG-EC2:&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;Inbound:  HTTP (80) from SG-ALB  ← reference the SG, not CIDR
Outbound: All traffic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ SG-EC2's inbound rule must reference SG-ALB as source — not &lt;code&gt;0.0.0.0/0&lt;/code&gt;. This blocks direct internet access to EC2. Only ALB can reach them.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Step 1 — Create Launch Template
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2 → Launch Templates → Create launch template

Name:            web-server-lt
AMI:             Amazon Linux 2023 (64-bit x86)
Instance Type:   t2.micro / t3.micro (Free Tier)
Key Pair:        your-key-pair
Security Group:  SG-EC2
Storage:         8 GiB gp3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;User Data (paste in Advanced Details):&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;#!/bin/bash&lt;/span&gt;
yum update &lt;span class="nt"&gt;-y&lt;/span&gt;
yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; httpd
systemctl start httpd
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;httpd

&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="s2"&gt;"http://169.254.169.254/latest/api/token"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token-ttl-seconds: 21600"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;AZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token: &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://169.254.169.254/latest/meta-data/placement/availability-zone&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;INSTANCE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token: &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://169.254.169.254/latest/meta-data/instance-id&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token: &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://169.254.169.254/latest/meta-data/placement/region&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /var/www/html/index.html &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;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;body style="font-family:Arial;text-align:center;padding:60px;background:#0A1628;color:#fff;"&amp;gt;
  &amp;lt;h1 style="color:#FF9900;"&amp;gt;Hello from AWS!&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;Instance ID: &amp;lt;b style="color:#FF9900;"&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;$INSTANCE_ID&lt;/span&gt;&lt;span class="sh"&gt;&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;Availability Zone: &amp;lt;b style="color:#FF9900;"&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;$AZ&lt;/span&gt;&lt;span class="sh"&gt;&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;Region: &amp;lt;b style="color:#FF9900;"&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="sh"&gt;&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Checkpoint: &lt;code&gt;web-server-lt&lt;/code&gt; listed in EC2 → Launch Templates&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2 — Create Target Group
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2 → Load Balancing → Target Groups → Create target group

Target type:         Instances
Name:                web-server-tg
Protocol / Port:     HTTP / 80
VPC:                 Default VPC
Health check path:   /
Healthy threshold:   2
Unhealthy threshold: 2
Timeout:             5 seconds
Interval:            30 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ On "Register targets" page — skip it entirely, click Create directly. ASG will register instances automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;✅ Checkpoint: &lt;code&gt;web-server-tg&lt;/code&gt; shows status "unused" — this is correct, no instances yet.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3 — Create Application Load Balancer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2 → Load Balancers → Create Load Balancer → Application Load Balancer

Name:           web-app-alb
Scheme:         Internet-facing
VPC:            Default VPC
Subnets:        Select 2 subnets in DIFFERENT AZs (mandatory — fails with one)
Security Group: SG-ALB (remove default SG)
Listener:       HTTP : 80 → Forward to web-server-tg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait ~2-3 minutes for state to become &lt;strong&gt;Active&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;✅ Checkpoint: Copy ALB DNS name — you'll need it to test.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4 — Create Auto Scaling Group
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2 → Auto Scaling Groups → Create Auto Scaling group

Name:             web-app-asg
Launch template:  web-server-lt (Latest)
VPC + Subnets:    Same 2 subnets as ALB

Load balancing:   Attach to existing → web-server-tg | HTTP
Health check:     ELB, Grace period: 300 seconds

Desired: 2  |  Min: 1  |  Max: 4
Scaling: None for now (add in Step 5)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Checkpoint: 2 EC2 instances launch within 1-2 min, both show &lt;strong&gt;Healthy&lt;/strong&gt; in Target Group.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 5 — Configure Scaling Policy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ASG → web-app-asg → Automatic scaling → Create dynamic scaling policy

Policy type:   Target tracking scaling
Policy name:   cpu-target-tracking
Metric:        Average CPU Utilization
Target value:  50
Warmup:        300 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS auto-creates two CloudWatch alarms (scale-out + scale-in).&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 6 — Test &amp;amp; Verify
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Open ALB DNS in browser&lt;/span&gt;
http://&amp;lt;ALB-DNS-name&amp;gt;
&lt;span class="c"&gt;# → "Hello from AWS!" + Instance ID + AZ&lt;/span&gt;

&lt;span class="c"&gt;# Refresh 5-10 times&lt;/span&gt;
&lt;span class="c"&gt;# → Instance ID changes = ALB routing to different instances ✅&lt;/span&gt;
&lt;span class="c"&gt;# → AZ changes = multi-AZ distribution ✅&lt;/span&gt;

&lt;span class="c"&gt;# Trigger scale-out (SSH into one EC2)&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; your-key.pem ec2-user@&amp;lt;ec2-public-ip&amp;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; stress
stress &lt;span class="nt"&gt;--cpu&lt;/span&gt; 4 &lt;span class="nt"&gt;--timeout&lt;/span&gt; 600

&lt;span class="c"&gt;# Watch in console:&lt;/span&gt;
&lt;span class="c"&gt;# EC2 → Auto Scaling Groups → Activity tab (new instances launching)&lt;/span&gt;
&lt;span class="c"&gt;# CloudWatch → Alarms (CPU alarm goes to ALARM state)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected timeline after stress starts:&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;T+0:00  CPU spikes
T+1:00  CloudWatch alarm: OK → ALARM
T+3:00  New EC2 launches
T+5:00  New instance healthy → ALB routes to 3 instances
T+10:00 Stress ends → CPU drops
T+18:00 ASG scale-in → back to Desired=2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 7 — Monitor with CloudWatch
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CloudWatch → Metrics → EC2 → By Auto Scaling Group
  → web-app-asg → CPUUtilization → Period: 1 min

CloudWatch → Metrics → ApplicationELB
  → Add: RequestCount, HealthyHostCount, TargetResponseTime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧹 Cleanup — Delete in This Exact Order
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Auto Scaling Group  → terminates all EC2 instances automatically
   Wait 2-3 min for all instances to fully terminate

2. Application Load Balancer

3. Target Group

4. Launch Template

5. Security Groups  → delete SG-EC2 first, then SG-ALB
   (SG-ALB referenced by SG-EC2 — must delete SG-EC2 first)

6. CloudWatch Alarms  → delete auto-created scaling alarms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💰 ALB = ~$16/month + EC2 costs. Always clean up after labs.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📝 Assignment
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Task 1 (Mandatory) — Prove Self-Healing:&lt;/strong&gt;&lt;br&gt;
Manually terminate one running EC2 instance. Watch ASG Activity tab — confirm it auto-launches a replacement. Verify ALB kept serving traffic with zero downtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task 2 (Intermediate) — Step Scaling Policy:&lt;/strong&gt;&lt;br&gt;
Create a Step Scaling policy: add 2 instances if CPU &amp;gt; 70%, add 1 if CPU 50-70%, remove 1 if CPU &amp;lt; 20%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task 3 (Intermediate) — HTTPS:&lt;/strong&gt;&lt;br&gt;
Add HTTPS listener (port 443) to ALB. Use AWS Certificate Manager for a free certificate. Add listener rule: HTTP → HTTPS redirect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task 4 (Advanced) — Scheduled Scaling:&lt;/strong&gt;&lt;br&gt;
Scale up to 4 instances at 9AM weekdays, down to 1 at 6PM weekdays. Set times in UTC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task 5 (Advanced) — CloudWatch Dashboard:&lt;/strong&gt;&lt;br&gt;
Build a dashboard with 4 widgets: CPU%, RequestCount, HealthyHostCount, GroupInServiceInstances.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOAD BALANCING
  Manager directing traffic to healthy servers
  Eliminates single point of failure
  ALB algorithm: weighted round robin (default)

SCALING
  Horizontal = more instances (cloud-native, preferred)
  Vertical   = bigger instance (simple, has ceiling, needs downtime)

SCALING POLICIES
  Target Tracking = keep metric at target, AWS manages alarms (use this)
  Scheduled       = scale at specific times
  Predictive      = ML-based, needs 2 weeks history

ASG CAPACITY
  MIN     = hard floor, never below
  DESIRED = ASG always works to maintain this count
  MAX     = hard ceiling, never exceed

HEALTH CHECKS
  EC2  = is instance running? (basic)
  ELB  = is app returning HTTP 200? (always use this)
  Grace Period = time after launch before first check (300s default)

LB TYPES
  ALB  = Layer 7, HTTP/HTTPS, path/host routing → web apps
  NLB  = Layer 4, TCP/UDP, microsecond latency → gaming/IoT
  GWLB = Layer 3, security appliances, firewalls

STICKY SESSIONS
  Cookie pins user to one EC2 for session duration
  Proper fix: external session store (Redis/ElastiCache)

CONNECTION DRAINING
  In-flight requests complete before instance terminates
  Default: 300 seconds | Enables zero-downtime scaling

INSTANCE REFRESH
  Rolling AMI replacement across entire ASG fleet
  Zero-downtime | Kubernetes rolling update equivalent

SECURITY GROUP PATTERN
  SG-ALB: HTTP 80 from 0.0.0.0/0
  SG-EC2: HTTP 80 FROM SG-ALB only
  Direct EC2 access blocked from internet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: Horizontal vs Vertical scaling — what's the difference and which does AWS recommend?&lt;/strong&gt;&lt;br&gt;
Horizontal = add more instances, requires LB, unlimited scale, fault tolerant. Vertical = upgrade existing instance, simple, has a hard ceiling, requires downtime. AWS recommends horizontal scaling for cloud-native apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What are Min, Desired, and Max capacity in an ASG?&lt;/strong&gt;&lt;br&gt;
Min = hard floor, ASG never drops below. Max = hard ceiling, ASG never exceeds. Desired = the target count ASG constantly works to maintain. Scaling policies adjust Desired within Min-Max boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: Why use ELB health checks instead of EC2 health checks?&lt;/strong&gt;&lt;br&gt;
EC2 checks only verify the VM is running. ELB checks send a real HTTP request and verify the app returns 200. An EC2 can be "running" while Apache is crashed — EC2 checks miss it, ELB checks catch it within 60 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: What is Connection Draining?&lt;/strong&gt;&lt;br&gt;
It allows in-flight requests to complete before an instance is terminated. ALB immediately stops sending new requests but waits up to 300 seconds for active connections to finish — enabling zero-downtime scaling and deployments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: ALB vs NLB — when do you use each?&lt;/strong&gt;&lt;br&gt;
ALB for HTTP/HTTPS web apps and microservices — understands application content, supports path/host routing. NLB for ultra-low latency TCP/UDP workloads (gaming, IoT, VoIP) or when a static IP is required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: Why reference SG-ALB as source in SG-EC2 instead of 0.0.0.0/0?&lt;/strong&gt;&lt;br&gt;
Referencing SG-ALB as source means only traffic that came through the ALB can reach EC2. Direct internet access to the EC2 IP is blocked. More secure, no IP management needed as instances scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: What is Instance Refresh?&lt;/strong&gt;&lt;br&gt;
ASG's native mechanism to replace all running instances with a new Launch Template version (new AMI or config). It handles draining, replacement, and health checks automatically. Equivalent to a Kubernetes rolling update.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AWS Session 6 — ELB &amp;amp; Auto Scaling | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AWS EFS Essentials — Shared File Storage Across Multiple EC2 Instances</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Wed, 01 Jul 2026 06:41:40 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-efs-essentials-shared-file-storage-across-multiple-ec2-instances-5f6m</link>
      <guid>https://dev.to/tejas_shinkar/aws-efs-essentials-shared-file-storage-across-multiple-ec2-instances-5f6m</guid>
      <description>&lt;p&gt;Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This session covers Amazon EFS — shared storage that multiple EC2 instances can read/write simultaneously.&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;What is Amazon EFS&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;EFS vs EBS vs S3&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;EFS Architecture (Mount Targets, AZs, NFS)&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Mounting — What It Means&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Mount Targets &amp;amp; Security Group Config&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;EFS Storage Classes&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;EFS Lifecycle Management &amp;amp; Policies&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;EFS Performance Modes &amp;amp; Throughput Modes&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Benefits of EFS&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Security &amp;amp; Encryption&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;EFS Pricing &amp;amp; Cost Optimization&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Lab: Launch 2 EC2 Instances&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Lab: Create &amp;amp; Configure EFS&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Lab: Mount EFS on Both Instances&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Lab: Demonstrate File Sharing&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Cleanup Checklist&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Assignment — Independent Repeat&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What is Amazon EFS?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;EFS = Elastic File System&lt;/strong&gt; — a fully managed, auto-scaling shared file system that &lt;strong&gt;multiple EC2 instances can mount and use at the same time&lt;/strong&gt;, both reading and writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Think of EBS as a personal hard drive — it belongs to one laptop only. EFS is like a shared Google Drive folder that your entire team can open simultaneously from different computers, see the same files, and edit them in real time.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Uses the &lt;strong&gt;NFS 4.1 protocol&lt;/strong&gt; (Network File System — standard Linux file sharing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless&lt;/strong&gt; — no capacity to provision, no servers to manage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-scales&lt;/strong&gt; — grows from KB to PB automatically, shrinks when files are deleted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highly available&lt;/strong&gt; — data replicated across multiple AZs in a Region&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay-as-you-go&lt;/strong&gt; — billed per GB actually stored, no pre-provisioning&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  EFS vs EBS vs S3 — The Big Comparison
&lt;/h2&gt;

&lt;p&gt;This is one of the most commonly tested comparisons in AWS certifications.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;EBS&lt;/th&gt;
&lt;th&gt;EFS&lt;/th&gt;
&lt;th&gt;S3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single EC2 instance only&lt;/td&gt;
&lt;td&gt;Multiple EC2 instances simultaneously&lt;/td&gt;
&lt;td&gt;Internet / API, unlimited clients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Block storage&lt;/td&gt;
&lt;td&gt;NFS v4.1&lt;/td&gt;
&lt;td&gt;HTTP/REST (object storage)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AZ-specific&lt;/td&gt;
&lt;td&gt;Regional (multi-AZ)&lt;/td&gt;
&lt;td&gt;Region-wide, globally accessible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual (resize volume)&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;td&gt;Unlimited, automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very high, low latency&lt;/td&gt;
&lt;td&gt;High (shared)&lt;/td&gt;
&lt;td&gt;Variable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use Case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OS disks, databases&lt;/td&gt;
&lt;td&gt;Shared content, CMS, ML training&lt;/td&gt;
&lt;td&gt;Backups, static assets, objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Provisioned (pay for allocated size)&lt;/td&gt;
&lt;td&gt;Usage-based (pay for what's stored)&lt;/td&gt;
&lt;td&gt;Usage-based&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Simple decision rule:&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;Need a disk for ONE EC2 instance (like an OS drive)?        → EBS
Need a shared folder MULTIPLE EC2 instances can access?      → EFS
Need to store files accessible from anywhere via internet?   → S3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Interview Tip:&lt;/strong&gt; "Can two EC2 instances share an EBS volume?" — Only with io1/io2 Multi-Attach, and even then both must be in the same AZ. EFS is the natural answer when the question is about shared access across multiple instances in different AZs.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  EFS Architecture
&lt;/h2&gt;

&lt;p&gt;EFS spans &lt;strong&gt;multiple Availability Zones&lt;/strong&gt; within a Region. Here's how it's structured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Amazon EFS File System
                  (stored redundantly across AZs)
                              │
        ┌─────────────────────┼─────────────────────┐
        │                     │                      │
      AZ 1                  AZ 2                   AZ 3
   Mount Target          Mount Target           Mount Target
   (ENI + IP)            (ENI + IP)             (ENI + IP)
        │                     │                      │
   EC2 Instance          EC2 Instance           EC2 Instance
   /mnt/efs              /mnt/efs               /mnt/efs

   All instances access the SAME file system via NFS (port 2049)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mount Target&lt;/strong&gt; — this is the key piece. It's an &lt;strong&gt;ENI (Elastic Network Interface)&lt;/strong&gt; that EFS creates inside each AZ of your VPC. It's the "doorway" through which EC2 instances in that AZ connect to EFS over NFS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One mount target per AZ (auto-created when you set up EFS)&lt;/li&gt;
&lt;li&gt;Has its own private IP and security group&lt;/li&gt;
&lt;li&gt;EC2 instances in that AZ connect to the &lt;strong&gt;local mount target&lt;/strong&gt; for best performance&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Mounting — What It Actually Means
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mounting&lt;/strong&gt; is the process of attaching a file system to a directory on your OS so it behaves like a normal local folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Mounting EFS is like plugging in a USB drive and having it appear as a folder you can open, save files into, and read from — except this "USB drive" lives in AWS and multiple computers can plug into it at once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before mounting: /mnt/efs is just an empty folder&lt;/span&gt;
&lt;span class="c"&gt;# After mounting: /mnt/efs IS the EFS file system&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; nfs4 &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;nfsvers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4.1 &amp;lt;EFS-DNS-Name&amp;gt;:/ /mnt/efs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once mounted, every &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;echo &amp;gt;&lt;/code&gt;, &lt;code&gt;cp&lt;/code&gt; command on &lt;code&gt;/mnt/efs&lt;/code&gt; is actually reading/writing to the shared EFS file system — not local disk.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mount Targets &amp;amp; Security Group Configuration
&lt;/h2&gt;

&lt;p&gt;This is the part that trips people up in labs — &lt;strong&gt;two separate security groups working together.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Two Security Groups
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;EC2 Security Group (&lt;code&gt;lab-ec2-sg&lt;/code&gt;)&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;Inbound:  SSH (22) from My IP
Outbound: NFS (2049) to EFS-SG  [allows EC2 to reach EFS]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;EFS Security Group (&lt;code&gt;lab-efs-sg&lt;/code&gt;)&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;Inbound:  NFS (2049) from lab-ec2-sg  [only EC2 instances can reach EFS]
Outbound: Allow all (default)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why two security groups referencing each other?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a common AWS pattern — instead of allowing an IP range, you allow &lt;strong&gt;traffic from a specific security group&lt;/strong&gt;. This means: "Only instances that have the EC2 security group attached are allowed to talk to EFS on port 2049." Clean, scalable, and secure — you never have to know or update IP addresses.&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 (has lab-ec2-sg)
        │
        │  NFS traffic, port 2049
        ▼
EFS Mount Target (has lab-efs-sg, allows inbound from lab-ec2-sg)
        │
        ▼
Access Granted ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Common lab mistake:&lt;/strong&gt; Creating the EFS file system and trying to mount immediately — it fails because the EFS security group doesn't yet allow NFS traffic from the EC2 security group. You must add that inbound rule first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Mount Target Requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;One mount target per AZ in your VPC&lt;/li&gt;
&lt;li&gt;Requires a subnet assignment&lt;/li&gt;
&lt;li&gt;Has a private IP auto-assigned&lt;/li&gt;
&lt;li&gt;DNS-based access — EFS gives you one DNS name that automatically resolves to the correct local mount target for whichever AZ you're connecting from&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Persistence Across Reboots
&lt;/h3&gt;

&lt;p&gt;By default, if your EC2 instance restarts, the EFS mount is &lt;strong&gt;gone&lt;/strong&gt; — you'd have to mount it manually again. To make it automatic:&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;# Add to /etc/fstab&lt;/span&gt;
&amp;lt;EFS-DNS-Name&amp;gt;:/ /mnt/efs nfs4 defaults,_netdev 0 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;_netdev&lt;/code&gt; option tells the OS to wait for networking to be ready before attempting the mount — critical since EFS is a network resource, not local disk.&lt;/p&gt;




&lt;h2&gt;
  
  
  EFS Storage Classes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Storage Class&lt;/th&gt;
&lt;th&gt;Use Case&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;&lt;strong&gt;Standard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Active data, accessed frequently&lt;/td&gt;
&lt;td&gt;Higher per GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrequent Access (IA)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Files not accessed often&lt;/td&gt;
&lt;td&gt;~50% cheaper per GB + a per-access fee&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; Standard is the default — best for files you're actively using. IA is for files that just sit there most of the time (old logs, archived reports) — cheaper to store, but costs a small fee each time you access them. The trade-off only makes sense if access is genuinely rare.&lt;/p&gt;




&lt;h2&gt;
  
  
  EFS Lifecycle Management &amp;amp; Policies
&lt;/h2&gt;

&lt;p&gt;This is &lt;strong&gt;automatic tiering&lt;/strong&gt; — EFS moves files between Standard and IA based on actual usage, without you lifting a finger.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File created → Standard storage (full price)
        │
        │  Not accessed for X days (your chosen threshold)
        ▼
Auto-moved to Infrequent Access (cheaper)
        │
        │  Someone accesses the file again
        ▼
Auto-moved back to Standard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Configurable transition periods:&lt;/strong&gt; 7, 14, 30, 60, or 90 days of inactivity (or disabled entirely).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; You don't have to manually audit and move files — EFS handles cost optimization automatically based on real access patterns. Set it once during file system creation and forget about it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert Perspective:&lt;/strong&gt; "How do you reduce EFS storage cost for rarely accessed files without manual intervention?" → Enable Lifecycle Management with a transition policy to IA.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  EFS Performance Modes &amp;amp; Throughput Modes
&lt;/h2&gt;

&lt;p&gt;These are two &lt;strong&gt;separate&lt;/strong&gt; settings that often get confused — performance mode is about latency/concurrency, throughput mode is about data transfer speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Modes (set once, cannot change later)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Optimized For&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;General Purpose&lt;/strong&gt; (default)&lt;/td&gt;
&lt;td&gt;Lowest latency&lt;/td&gt;
&lt;td&gt;Web serving, CMS, most workloads — use this unless you have a specific reason not to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Max I/O&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Maximum throughput, more concurrent operations&lt;/td&gt;
&lt;td&gt;Big data, media processing, highly parallel workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Throughput Modes (can change later)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;How it Works&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Bursting&lt;/strong&gt; (default)&lt;/td&gt;
&lt;td&gt;Throughput scales with file system size, bursts higher when needed&lt;/td&gt;
&lt;td&gt;Default choice, cost-effective for most workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Provisioned&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You specify a guaranteed throughput, independent of storage size&lt;/td&gt;
&lt;td&gt;Predictable high-throughput needs regardless of how much is stored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Max I/O&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optimized for highest throughput and ops/second&lt;/td&gt;
&lt;td&gt;Heavy parallel processing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;Cert Perspective:&lt;/strong&gt; Know that Performance Mode is locked at creation, but Throughput Mode can be changed afterward. A scenario like "small file system but needs guaranteed high throughput" points to Provisioned Throughput.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Benefits of EFS
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;th&gt;What It Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Grows from KB to PB automatically, zero downtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;High Availability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data redundantly stored across multiple AZs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Durability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple copies protect against component failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Simple Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Standard Linux NFS interface — no app code changes needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost-Optimized&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;IA storage class cuts costs ~50% for inactive files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Security &amp;amp; Encryption
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;Security groups control all access (as covered above)&lt;/li&gt;
&lt;li&gt;VPC isolation — EFS only accessible within your VPC by default&lt;/li&gt;
&lt;li&gt;NFS traffic always on port 2049&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Data Encryption:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;At rest&lt;/strong&gt; — via AWS KMS, enable when creating the file system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In transit&lt;/strong&gt; — via TLS, add &lt;code&gt;-o tls&lt;/code&gt; to your mount command&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Access Control (3 layers):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;File System Policy&lt;/strong&gt; — IAM-style resource policy on the EFS itself (grant/deny access)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POSIX file permissions&lt;/strong&gt; — standard Linux permissions (&lt;code&gt;chmod&lt;/code&gt;, &lt;code&gt;chown&lt;/code&gt;) work exactly as expected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM integration&lt;/strong&gt; — controls who can call EFS API operations (create, delete, modify)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always enable encryption when creating the file system (can't add it retroactively)&lt;/li&gt;
&lt;li&gt;Restrict security groups to required ports only — never open NFS to 0.0.0.0/0&lt;/li&gt;
&lt;li&gt;Use VPC security groups for isolation, exactly like the EC2-SG ↔ EFS-SG pattern above&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  EFS Pricing &amp;amp; Cost Optimization
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What you pay for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standard Storage — per GB stored&lt;/li&gt;
&lt;li&gt;Infrequent Access — ~50% lower per GB, plus a small fee each time data is retrieved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost optimization checklist:&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;✅ Enable Lifecycle Management — auto-move inactive files to IA
✅ Choose sensible transition days based on actual access patterns
✅ Use Bursting Throughput (default) unless you specifically need Provisioned
✅ Monitor usage via CloudWatch metrics
✅ Clean up unused files regularly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧪 LAB — Full Hands-On Walkthrough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lab Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            VPC (Default)
        ┌─────────────────────────┐
        │   Availability Zone      │
        │  ┌──────────┐  ┌──────────┐
        │  │   EC2-1   │  │   EC2-2   │
        │  │ /mnt/efs  │  │ /mnt/efs  │
        │  └─────┬────┘  └─────┬────┘
        │        │  NFS (2049)  │
        │        └──────┬───────┘
        │                ▼
        │         Amazon EFS
        │       (Mount Target)
        └─────────────────────────┘

Security Groups:
  EC2-SG  → allows SSH (22) inbound from My IP
  EFS-SG  → allows NFS (2049) inbound from EC2-SG only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Lab Step 1 — Launch 2 EC2 Instances
&lt;/h3&gt;

&lt;p&gt;Repeat this entire process twice (&lt;code&gt;EC2-Instance-1&lt;/code&gt; and &lt;code&gt;EC2-Instance-2&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. EC2 Console → Launch Instances
2. Name: EC2-Instance-1 (then EC2-Instance-2)
3. AMI: Amazon Linux 2 (HVM) — Free tier eligible
4. Instance type: t2.micro / t3.micro (Free tier)
5. Key Pair: Create new → lab-key-pair → .pem format
6. Network: Default VPC, any subnet, Auto-assign public IP: Enable
7. Security Group: Create lab-ec2-sg → Allow SSH (22) from My IP
8. Storage: 8 GB gp2 root volume (default)
9. Launch Instance — repeat for Instance 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Use the SAME key pair, VPC, and subnet for both instances&lt;/strong&gt; — keeps the lab simple and ensures they can communicate.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Lab Step 2 — Create and Configure EFS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. AWS Console → Services → EFS → Create file system
2. Name: lab-efs
3. VPC: Select SAME VPC as your EC2 instances
4. Quick Create (defaults) or Customize
5. EFS auto-creates mount targets in each AZ of the VPC
6. Edit mount target Security Group → create/use lab-efs-sg
7. Add Inbound Rule to lab-efs-sg: NFS (2049) from lab-ec2-sg
8. Create — note the File System ID: fs-xxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;EFS Settings used (defaults):&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;Performance mode:  General Purpose
Throughput mode:   Bursting
Encryption:        Enabled (KMS)
Storage class:     Standard (multi-AZ redundancy)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security Group Rule Summary:&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;Security Group&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;lab-ec2-sg&lt;/td&gt;
&lt;td&gt;Inbound&lt;/td&gt;
&lt;td&gt;22 (SSH)&lt;/td&gt;
&lt;td&gt;My IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lab-efs-sg&lt;/td&gt;
&lt;td&gt;Inbound&lt;/td&gt;
&lt;td&gt;2049 (NFS)&lt;/td&gt;
&lt;td&gt;lab-ec2-sg&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Lab Step 3 — Mount EFS on Both Instances
&lt;/h3&gt;

&lt;p&gt;Open &lt;strong&gt;two separate SSH terminals&lt;/strong&gt; — one per instance. Run identical steps on both.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Update packages&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum update &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Install NFS/EFS utilities&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; amazon-efs-utils
&lt;span class="c"&gt;# (or: sudo yum install -y nfs-utils — depending on AMI)&lt;/span&gt;

&lt;span class="c"&gt;# Step 3: Create mount directory&lt;/span&gt;
&lt;span class="nb"&gt;sudo mkdir &lt;/span&gt;efs
&lt;span class="c"&gt;# or: sudo mkdir /mnt/efs&lt;/span&gt;

&lt;span class="c"&gt;# Step 4: Mount EFS&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; efs &lt;span class="nt"&gt;-o&lt;/span&gt; tls fs-012f61809a9d6e846:/ /efs
&lt;span class="c"&gt;# or using DNS name:&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; nfs4 &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;nfsvers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4.1 &amp;lt;EFS-DNS-Name&amp;gt;:/ /mnt/efs

&lt;span class="c"&gt;# Step 5: Verify the mount&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="c"&gt;# or&lt;/span&gt;
mount | &lt;span class="nb"&gt;grep &lt;/span&gt;efs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;If the mount fails on the first attempt&lt;/strong&gt; — this is expected if you haven't yet added the NFS inbound rule to the EFS security group. Add it, then retry the mount command. This is intentional in the lab flow to show &lt;em&gt;why&lt;/em&gt; the security group rule matters.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Lab Step 4 — Demonstrate File Sharing (The Core Proof)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;On EC2 Instance 1 (Writer):&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;cd&lt;/span&gt; /mnt/efs

&lt;span class="c"&gt;# Write a file&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Hello from EC2 Instance 1!'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee &lt;/span&gt;shared-file.txt

&lt;span class="c"&gt;# Append a timestamp&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Timestamp: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; shared-file.txt

&lt;span class="c"&gt;# Create a folder and copy a file into it&lt;/span&gt;
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /mnt/efs/shared-folder
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/hostname /mnt/efs/shared-folder/host1.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;On EC2 Instance 2 (Reader) — same EFS mount:&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;cd&lt;/span&gt; /mnt/efs

&lt;span class="c"&gt;# List files — see what Instance 1 wrote, instantly&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /mnt/efs
&lt;span class="c"&gt;# Output: shared-file.txt  shared-folder/&lt;/span&gt;

&lt;span class="c"&gt;# Read the file Instance 1 created&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;shared-file.txt
&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# Hello from EC2 Instance 1!&lt;/span&gt;
&lt;span class="c"&gt;# Timestamp: Mon Jan 01 10:00:00 UTC&lt;/span&gt;

&lt;span class="c"&gt;# Write back from Instance 2&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Reply from Instance 2!'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; shared-file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verify on EITHER instance:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /mnt/efs/shared-file.txt
&lt;span class="c"&gt;# Hello from EC2 Instance 1!&lt;/span&gt;
&lt;span class="c"&gt;# Timestamp: Mon Jan 01 10:00:00 UTC&lt;/span&gt;
&lt;span class="c"&gt;# Reply from Instance 2!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this proves:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both instances read/write the &lt;strong&gt;same file system&lt;/strong&gt; simultaneously&lt;/li&gt;
&lt;li&gt;Changes from one instance are &lt;strong&gt;instantly&lt;/strong&gt; visible on the other&lt;/li&gt;
&lt;li&gt;No manual file transfer, sync, or copying needed — this is the entire value of EFS&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Permissions Note
&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;# Grant full access to all users on the mount&lt;/span&gt;
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;777 /mnt/efs

&lt;span class="c"&gt;# Now you can write without sudo&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'No sudo needed!'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /mnt/efs/test.txt
&lt;span class="nb"&gt;cat&lt;/span&gt; /mnt/efs/test.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧹 Cleanup Checklist (Don't Skip This!)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Unmount EFS on both instances: sudo umount /mnt/efs
✅ Terminate both EC2 instances
✅ Delete EFS file system (lab-efs)
✅ Delete security groups: lab-ec2-sg, lab-efs-sg
✅ Delete key pair if no longer needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ EFS bills per GB stored — even small test files left running add up over time if forgotten. Always clean up after labs.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📝 Assignment — Independent Repeat
&lt;/h2&gt;

&lt;p&gt;Complete this without step-by-step guidance, using everything from the lab above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.  Launch EC2 #1 — Amazon Linux 2, t2.micro, new key pair, public IP enabled
2.  Launch EC2 #2 — same config, same key pair, same VPC/subnet
3.  Create EFS — name: my-efs, default VPC, default settings
4.  Configure SGs — EC2 SG: SSH (22). EFS SG: NFS (2049) from EC2 SG
5.  Mount on EC2 #1 — install nfs-utils, mkdir /mnt/efs, mount EFS
6.  Mount on EC2 #2 — same mount steps
7.  Verify Mounts — run df -h on both, confirm EFS appears
8.  Write from EC2 #1 — create a file with your name at /mnt/efs/yourname.txt
9.  Read from EC2 #2 — cat /mnt/efs/yourname.txt, confirm visible
10. Write from EC2 #2 — append a second line to the same file
11. Cleanup — unmount, terminate instances, delete EFS and SGs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Submission deliverables (screenshots):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;EC2 Instances list — both Running&lt;/li&gt;
&lt;li&gt;EFS file system page — showing created file system&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;df -h&lt;/code&gt; output on Instance 1 — &lt;code&gt;/mnt/efs&lt;/code&gt; mounted&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;df -h&lt;/code&gt; output on Instance 2 — &lt;code&gt;/mnt/efs&lt;/code&gt; mounted&lt;/li&gt;
&lt;li&gt;Terminal — file written on Instance 1&lt;/li&gt;
&lt;li&gt;Terminal — same file read from Instance 2&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Grading breakdown:&lt;/strong&gt; EC2 launch (25 pts) · EFS + SG config (25 pts) · Mounted on both (25 pts) · File sharing both ways (25 pts)&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Quick Revision
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHAT IS EFS
  Fully managed, shared, auto-scaling NFS file system
  Multiple EC2 instances mount it simultaneously
  Protocol: NFS v4.1, Port: 2049

EFS vs EBS vs S3
  EBS = single instance, block storage, AZ-locked
  EFS = multiple instances, NFS, Region-wide (multi-AZ)
  S3  = internet/API access, object storage, unlimited

ARCHITECTURE
  Mount Target = ENI in each AZ, the doorway to EFS
  One per AZ, auto-created, has its own private IP + SG

SECURITY GROUP PATTERN
  EC2-SG  → outbound NFS to EFS-SG
  EFS-SG  → inbound NFS (2049) FROM EC2-SG only
  Never open NFS to 0.0.0.0/0

STORAGE CLASSES
  Standard = frequent access, higher cost
  IA       = infrequent access, ~50% cheaper + access fee

LIFECYCLE POLICY
  Auto-moves files Standard → IA after X days inactive (7/14/30/60/90)
  Auto-moves back to Standard when accessed again

PERFORMANCE MODES (set once)
  General Purpose = default, lowest latency, most workloads
  Max I/O = max throughput, more concurrency, big data

THROUGHPUT MODES (changeable)
  Bursting     = default, scales with FS size
  Provisioned  = fixed guaranteed throughput
  Max I/O      = highest throughput/ops

ENCRYPTION
  At rest  → KMS (enable at creation)
  In transit → TLS (-o tls in mount command)

MOUNT COMMAND
  sudo mount -t efs -o tls fs-xxxx:/ /efs
  or
  sudo mount -t nfs4 -o nfsvers=4.1 &amp;lt;EFS-DNS&amp;gt;:/ /mnt/efs

PERSISTENCE
  Add to /etc/fstab with _netdev option to survive reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💼 Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: What is the main difference between EFS and EBS?&lt;/strong&gt;&lt;br&gt;
EBS is block storage attached to a single EC2 instance within one AZ — like a personal hard drive. EFS is a shared NFS file system that multiple EC2 instances across multiple AZs can mount and access simultaneously — like a shared network drive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: Why does an EFS mount initially fail in the lab even though the file system was created successfully?&lt;/strong&gt;&lt;br&gt;
The EFS security group doesn't yet have an inbound rule allowing NFS (port 2049) traffic from the EC2 security group. Until that rule is added, EC2 instances cannot reach the mount target, so the mount command times out or fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: How does EFS achieve high availability?&lt;/strong&gt;&lt;br&gt;
EFS automatically creates a mount target in each Availability Zone of your VPC and stores data redundantly across multiple AZs within the Region — so it tolerates an AZ failure without data loss or downtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: What's the difference between EFS Performance Mode and Throughput Mode?&lt;/strong&gt;&lt;br&gt;
Performance Mode (General Purpose or Max I/O) is set once at creation and determines latency characteristics — it can't be changed later. Throughput Mode (Bursting, Provisioned, or Max I/O) controls how much data transfer capacity you get and can be changed after creation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: A company has files that are rarely accessed after 30 days but must remain available. How would you optimize EFS costs?&lt;/strong&gt;&lt;br&gt;
Enable EFS Lifecycle Management with a transition policy set to 30 days. Files inactive for 30 days automatically move to Infrequent Access storage class at roughly 50% lower storage cost. If accessed again, they automatically move back to Standard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: Why do EC2-SG and EFS-SG reference each other instead of using IP addresses?&lt;/strong&gt;&lt;br&gt;
Referencing security groups instead of IPs is more secure and scalable — it means "only resources carrying this specific security group can connect," regardless of how many instances exist or what their IPs are. No manual IP management needed as you scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔬 Practice Tasks
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Complete the Assignment above independently (full EC2 + EFS lab without referring to lab steps).&lt;/li&gt;
&lt;li&gt;After mounting EFS on one instance, deliberately remove the NFS inbound rule from the EFS security group, then try writing a new file. Observe what happens. Re-add the rule and confirm it works again.&lt;/li&gt;
&lt;li&gt;Enable Lifecycle Management on your EFS with a 7-day transition to IA. Create a file, wait (or simulate), and check the EFS Console to see which storage class it reports.&lt;/li&gt;
&lt;li&gt;Add your EFS mount to &lt;code&gt;/etc/fstab&lt;/code&gt; on one instance. Reboot the instance and confirm the mount persists automatically without running the mount command again.&lt;/li&gt;
&lt;li&gt;Compare: create a 1GB file directly on EBS root volume vs on the EFS mount. Time how long a &lt;code&gt;cp&lt;/code&gt; operation takes on each, and note any latency differences.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;AWS Session 5 — EFS Essentials | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer&lt;/em&gt;&lt;/p&gt;

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