<?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 Database Mini Projects - RDS, Aurora, DynamoDB &amp; ElastiCache</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Fri, 18 Sep 2026 03:41:29 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-database-mini-projects-rds-aurora-dynamodb-elasticache-49lp</link>
      <guid>https://dev.to/tejas_shinkar/aws-database-mini-projects-rds-aurora-dynamodb-elasticache-49lp</guid>
      <description>&lt;h3&gt;
  
  
  Five Production-Style Builds Across RDS, Aurora, DynamoDB &amp;amp; ElastiCache
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey. Instead of practicing RDS, Aurora, DynamoDB, and ElastiCache as isolated labs, I built five small connected projects instead, each one following the same production discipline: a dedicated environment, IAM least privilege, validation, failover or performance testing, and cleanup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Full repository: &lt;a href="https://github.com/tejasshinkar/Aws-Database-mini-projects" rel="noopener noreferrer"&gt;github.com/tejasshinkar/Aws-Database-mini-projects&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Concepts: &lt;br&gt;
AWS RDS and Elasticache: &lt;a href="https://dev.to/tejas_shinkar/aws-rds-elastic-cache-managed-databases-multi-az-failover-in-memory-caching-5dce"&gt;https://dev.to/tejas_shinkar/aws-rds-elastic-cache-managed-databases-multi-az-failover-in-memory-caching-5dce&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Aurora and Dynamodb: &lt;a href="https://dev.to/tejas_shinkar/aws-aurora-elasticache-patterns-dynamodb-the-complete-data-layer-1k3e"&gt;https://dev.to/tejas_shinkar/aws-aurora-elasticache-patterns-dynamodb-the-complete-data-layer-1k3e&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 Projects 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;Project&lt;/th&gt;
&lt;th&gt;Main AWS Services&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;RDS + EC2 Application&lt;/td&gt;
&lt;td&gt;EC2, RDS, IAM, Security Groups, Python/Flask&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;RDS High Availability + Read Replica&lt;/td&gt;
&lt;td&gt;RDS, Multi-AZ, Read Replica, EC2&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Aurora High Availability + Failover&lt;/td&gt;
&lt;td&gt;Amazon Aurora, RDS&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;DynamoDB Design + Performance&lt;/td&gt;
&lt;td&gt;DynamoDB, Partition/Sort Keys, GSI, LSI&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;DynamoDB + ElastiCache for Valkey&lt;/td&gt;
&lt;td&gt;DynamoDB, ElastiCache for Valkey, EC2, IAM, Python&lt;/td&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Approach Applied Across All Five
&lt;/h2&gt;

&lt;p&gt;Every project in this set followed the same working process rather than five unrelated one-off exercises:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understand the AWS service or architecture pattern before building it.&lt;/li&gt;
&lt;li&gt;Create and configure the required AWS resources.&lt;/li&gt;
&lt;li&gt;Apply IAM and network-security controls appropriate to the workload.&lt;/li&gt;
&lt;li&gt;Implement or test the actual solution, not just provision it.&lt;/li&gt;
&lt;li&gt;Troubleshoot configuration and permission issues as they came up.&lt;/li&gt;
&lt;li&gt;Capture evidence through screenshots and test results.&lt;/li&gt;
&lt;li&gt;Document the implementation and the cleanup process.&lt;/li&gt;
&lt;li&gt;Remove unused AWS resources to avoid unnecessary charges.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Project 1 — RDS + EC2 Application
&lt;/h2&gt;

&lt;p&gt;A Flask application hosted on EC2, connected to an Amazon RDS database. The focus was on wiring the application and database together the way a real deployment would, using IAM and Security Groups to control access rather than hardcoding database credentials into the application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills exercised:&lt;/strong&gt; RDS configuration and database connectivity, EC2 administration and application hosting, IAM roles and least-privilege access, Security Groups and network troubleshooting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project 2 — RDS High Availability + Read Replica
&lt;/h2&gt;

&lt;p&gt;Took the same RDS + EC2 foundation further to explore how RDS handles availability and read scaling in practice: Multi-AZ for automatic failover, and a Read Replica for offloading read traffic from the primary instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills exercised:&lt;/strong&gt; RDS Multi-AZ concepts, Read Replica configuration, database high availability and scalability concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project 3 — Aurora High Availability + Failover
&lt;/h2&gt;

&lt;p&gt;Moved from standard RDS engines to Amazon Aurora specifically to work with its replication model, cluster endpoints, and failover behavior directly, rather than only reading about how Aurora's architecture is supposed to behave.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills exercised:&lt;/strong&gt; Aurora replication and failover concepts, cluster endpoint behavior, database high availability patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project 4 — DynamoDB Design + Performance
&lt;/h2&gt;

&lt;p&gt;A shift out of the relational world entirely: designing a DynamoDB table's partition keys, sort keys, Global Secondary Indexes, and Local Secondary Indexes, then testing how those design decisions actually affect access patterns and performance under load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills exercised:&lt;/strong&gt; DynamoDB data modeling and key design, performance testing and access-pattern evaluation, NoSQL schema design trade-offs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project 5 — DynamoDB + ElastiCache for Valkey
&lt;/h2&gt;

&lt;p&gt;Added a caching layer in front of DynamoDB and implemented the lazy-loading (cache-aside) pattern along with cache invalidation, the same approach that shows up constantly in systems that can't afford to hit the database on every single read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills exercised:&lt;/strong&gt; ElastiCache for Valkey integration, lazy loading and cache-aside architecture, cache invalidation strategy, Python automation with the AWS SDK (Boto3).&lt;/p&gt;




&lt;h2&gt;
  
  
  Repository Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws-database-mini-projects/
│
├── README.md
│
├── 01-rds-ec2-application/
├── 02-rds-ha-read-replica/
├── 03-aurora-ha-failover/
├── 04-dynamodb-design-performance/
└── 05-dynamodb-elasticache-valkey/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each project directory contains its own README, implementation details, and test evidence where applicable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technologies Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud:&lt;/strong&gt; Amazon Web Services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute:&lt;/strong&gt; Amazon EC2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relational Databases:&lt;/strong&gt; Amazon RDS, Amazon Aurora&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NoSQL Database:&lt;/strong&gt; Amazon DynamoDB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching:&lt;/strong&gt; ElastiCache for Valkey&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Networking:&lt;/strong&gt; VPC, Security Groups, Subnets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; AWS IAM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation:&lt;/strong&gt; Python, Boto3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Control:&lt;/strong&gt; Git and GitHub&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Connecting an EC2-hosted application to a managed database is straightforward technically, but doing it with least-privilege IAM and scoped Security Groups instead of hardcoded credentials is what makes it production-appropriate.&lt;/li&gt;
&lt;li&gt;RDS Multi-AZ and Read Replicas solve two different problems, availability and read scaling, and shouldn't be conflated with each other.&lt;/li&gt;
&lt;li&gt;Aurora's replication and failover behavior is different enough from standard RDS that it's worth testing directly rather than assuming the concepts transfer one-to-one.&lt;/li&gt;
&lt;li&gt;Good DynamoDB performance starts at the schema design stage. Partition key, sort key, and index choices made early determine what access patterns are actually efficient later.&lt;/li&gt;
&lt;li&gt;A cache is only as good as its invalidation strategy. Lazy loading solves the "don't cache what nobody asked for" problem, but it has to be paired with a real invalidation approach or it just serves stale data faster.&lt;/li&gt;
&lt;li&gt;The habit that mattered most across all five projects wasn't any single AWS service. It was the repeated discipline of scoping access, validating the result, and cleaning up afterward.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;These projects were built for learning, experimentation, and portfolio development. Resource configurations are simplified compared with a real production environment, and all AWS resources were deleted or stopped after testing to avoid unexpected costs.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>aws</category>
      <category>database</category>
      <category>devops</category>
      <category>python</category>
    </item>
    <item>
      <title>When You Know Something's Wrong But Can't Say Why Yet - Jesse Pinkman Energy</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Thu, 17 Sep 2026 13:40:04 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/when-you-know-somethings-wrong-but-cant-say-why-yet-jesse-pinkman-energy-1966</link>
      <guid>https://dev.to/tejas_shinkar/when-you-know-somethings-wrong-but-cant-say-why-yet-jesse-pinkman-energy-1966</guid>
      <description>&lt;p&gt;You know that feeling in a code review where something's off, but you can't point to a specific line and say "this is broken"?&lt;/p&gt;

&lt;p&gt;That's Jesse Pinkman energy. The guy who couldn't explain the chemistry, but somehow always knew when a batch was going to go bad before it actually did.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj1zvgs9qltein5jv2gzt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj1zvgs9qltein5jv2gzt.png" alt=" " width="275" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Except in code review, it's usually the other way around. The senior engineer can't always explain the deep architectural reasoning either. Sometimes it's just pattern recognition built from years of watching similar things break in production. And that feeling gets dismissed constantly, because "I don't like this" doesn't sound like proper feedback in a pull request comment.&lt;/p&gt;

&lt;p&gt;Here's the reframe worth sitting with. Vague unease during a code review is data. It's just data you haven't translated into words yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8v3qpnid4qtphd2eqdr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8v3qpnid4qtphd2eqdr.png" alt=" " width="380" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why every engineer has felt this at least once
&lt;/h2&gt;

&lt;p&gt;If you've worked in software for more than a few months, you already know this feeling by a dozen other names. A hunch. A nagging doubt. That little itch at the back of your head that shows up right when you're about to click approve.&lt;/p&gt;

&lt;p&gt;It usually shows up around a few very specific situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A function that technically works but feels like it's holding the whole system together with tape&lt;/li&gt;
&lt;li&gt;An architecture decision that solves today's problem while quietly creating three more for next quarter&lt;/li&gt;
&lt;li&gt;A "quick fix" that's clearly going to become permanent the moment it merges&lt;/li&gt;
&lt;li&gt;Error handling that only covers the happy path, and everyone in the room quietly knows it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these show up as a compile error. None of them fail a test. They just sit there, uncomfortable, until either you say something, or you don't, and the code ships anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  The silence trap in software engineering teams
&lt;/h2&gt;

&lt;p&gt;Most engineers who get that "something's wrong here" feeling do one of two things.&lt;/p&gt;

&lt;p&gt;They stay quiet, because they can't fully articulate it yet and don't want to sound unsure in front of the team. Or they approve the PR anyway, because pushing back without a clean, specific reason feels unfair to the person who wrote it, especially if that person is more senior or under deadline pressure.&lt;/p&gt;

&lt;p&gt;Both paths lead to the exact same place. The code ships. The feeling was right. Three weeks later it's an incident report, a rollback, or a very awkward retro, and everyone's trying to reconstruct what actually went wrong, when someone in that original review already half knew.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Chemistry is the study of change."&lt;/em&gt; And so is a good code review, if anyone actually says what they're noticing before it turns into a postmortem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3usa9ersdre8b8a2zxz8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3usa9ersdre8b8a2zxz8.png" alt=" " width="416" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is one of the quieter, less talked about parts of engineering team communication. We optimize for clear, actionable, confident feedback, and that's genuinely good advice for most review comments. But the earliest warning signs almost never arrive pre packaged as clear and actionable. They arrive fuzzy. They arrive as a feeling first, and a reason second, if at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the best reviewers actually do differently
&lt;/h2&gt;

&lt;p&gt;The engineers who catch problems early aren't always the ones with the deepest technical knowledge in the room. Sometimes they're just the ones willing to say "I don't know why yet, but can we double check this" before it ships, instead of after it breaks.&lt;/p&gt;

&lt;p&gt;You don't need a fully formed argument to raise a concern in a pull request. A few templates that work surprisingly well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"This feels fragile, can you walk me through what happens if X fails"&lt;/li&gt;
&lt;li&gt;"I don't have a specific objection yet, but this reminds me of something that broke before, can we talk it through"&lt;/li&gt;
&lt;li&gt;"Genuinely not sure if this is a problem or just unfamiliar to me, flagging it anyway"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these require certainty. All of them get the uncertainty out of your head and into the conversation, which is really the whole point of a code review in the first place.&lt;/p&gt;

&lt;p&gt;Half the time, just saying the vague thing out loud is enough to resolve it. Either the author explains their reasoning and the feeling settles, or explaining it out loud is exactly what surfaces the real issue neither of you had named yet. Either outcome is a win. The only losing move is staying quiet.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick framework: name it before you nail it
&lt;/h2&gt;

&lt;p&gt;If you want something more concrete than "trust your gut," try this three step version the next time a review feels off but you can't fully explain why:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Name the feeling, not the fix.&lt;/strong&gt; Say what you're noticing, not what you think should change. "This feels overly coupled" is enough to open the conversation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask, don't accuse.&lt;/strong&gt; "What happens if this fails" lands very differently than "this will fail."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let the conversation do the diagnosis.&lt;/strong&gt; You don't have to arrive with the answer. The discussion itself usually produces it faster than either person thinking alone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This works because it separates two things that get merged together way too often: being right, and being useful. You don't have to be right to be useful in a review. You just have to be honest about what you're noticing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real cost of staying quiet
&lt;/h2&gt;

&lt;p&gt;Nobody puts "ignored my own intuition" as a root cause in an incident report, but if you read between the lines of enough postmortems, it's there more often than people admit. Someone had a feeling. Someone didn't say it. The system found out the hard way instead.&lt;/p&gt;

&lt;p&gt;This isn't a call to nitpick every PR into oblivion or become the reviewer everyone dreads. It's the opposite, actually. It's permission to flag the fuzzy thing without needing to dress it up as a fully formed critique first. Good engineering culture makes room for "I'm not sure, but," not just "here's exactly what's wrong."&lt;/p&gt;

&lt;p&gt;So, be honest with yourself for a second. Have you ever sat on a bad feeling about a pull request because you couldn't justify it yet? Or worse, has someone on your team raised one and gotten waved off because it "wasn't specific enough"?&lt;/p&gt;

&lt;p&gt;Drop your Jesse Pinkman moment in the comments. The one where you knew, before you knew why.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; The feeling that something's off in a code review, even without a clear technical reason, is worth voicing. Silence doesn't make the risk go away, it just delays when the team finds out. Name the feeling, ask instead of accuse, and let the conversation do the rest.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#softwareengineering&lt;/code&gt; &lt;code&gt;#codereview&lt;/code&gt; &lt;code&gt;#devculture&lt;/code&gt; &lt;code&gt;#careeradvice&lt;/code&gt; &lt;code&gt;#programming&lt;/code&gt; &lt;code&gt;#developerexperience&lt;/code&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Iron Throne Problem: Why Everyone Wants Admin Access and Nobody Should Have It</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Wed, 16 Sep 2026 03:15:46 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/the-iron-throne-problem-why-everyone-wants-admin-access-and-nobody-should-have-it-394c</link>
      <guid>https://dev.to/tejas_shinkar/the-iron-throne-problem-why-everyone-wants-admin-access-and-nobody-should-have-it-394c</guid>
      <description>&lt;p&gt;Okay real talk — you know that moment in every office when someone says &lt;strong&gt;"can you just give me admin access, it'll be faster"?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yeah. That's the Iron Throne conversation. Every single time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Everyone who wants to sit on the Iron Throne, wants to sit on it because it's what they think they need. Nobody sits on it because they should."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F66jztt6n93vcb0wiytnt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F66jztt6n93vcb0wiytnt.png" alt=" " width="446" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the thing nobody says out loud: the person asking for full access almost never actually needs &lt;em&gt;full&lt;/em&gt; access. They need &lt;em&gt;one&lt;/em&gt; permission, for &lt;em&gt;one&lt;/em&gt; task, &lt;em&gt;right now&lt;/em&gt;. But "give me admin" is faster to ask for than "give me exactly what I need," so that's what gets asked. And granting it is faster than saying no. So it gets granted.&lt;/p&gt;

&lt;p&gt;And then six months later, nobody remembers who has access to what, or why — everyone's just... sitting on thrones they never should've had.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4dya492m3tfkh9f977qj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4dya492m3tfkh9f977qj.png" alt=" " width="319" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This isn't an AWS thing, or a security-team thing, or even really a tech thing. It's a &lt;em&gt;people&lt;/em&gt; thing. Least privilege isn't a compliance checkbox — it's just... not handing out kingdoms because someone asked nicely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "just this once" never stays "just this once"
&lt;/h2&gt;

&lt;p&gt;You've seen this play out before, even if you never called it the Iron Throne. Someone needs to fix one thing urgently, so they get temporary access. The fix works. Nobody remembers to take the access back. Three months later that "temporary" grant is just... permanent, invisible, and nobody's job to notice.&lt;/p&gt;

&lt;p&gt;It's not laziness, exactly. It's that revoking access requires someone to actively decide "you don't need this anymore," and that decision has a social cost that granting access never had. Saying yes is free. Saying "actually, give that back" feels like an accusation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The King eats last."&lt;/em&gt; — except in most orgs, the person who granted the access eats first, and everyone downstream inherits the mess.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flt7tnv2jo1irzecjh4v1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flt7tnv2jo1irzecjh4v1.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually costs you later
&lt;/h2&gt;

&lt;p&gt;Every access grant is a tiny bit of trust you can't easily take back. Nobody wants to be "that person" who revokes access — it feels petty, like you're accusing someone of something. So it just... stays. Forever. Until an audit, or worse, an incident, forces the question nobody wanted to ask: &lt;em&gt;wait, why does the intern have prod access?&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Power resides where men believe it resides."&lt;/em&gt; — and access resides wherever nobody bothered to clean it up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9mkcfhymg9vy27k8xjxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9mkcfhymg9vy27k8xjxg.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the part that never makes it into onboarding docs. Nobody sits you down on day one and says "hey, by the way, every permission you're granted outlives its usefulness unless someone actively kills it." So permissions just accumulate, quietly, like sediment. And the org chart of "who can technically do what" ends up looking nothing like the org chart of "who's actually supposed to be doing what."&lt;/p&gt;

&lt;h2&gt;
  
  
  So what actually works?
&lt;/h2&gt;

&lt;p&gt;Not some grand security overhaul. Not a 40-page access policy nobody reads. Just one habit, repeated constantly: ask &lt;strong&gt;"what's the smallest thing that solves this?"&lt;/strong&gt; before &lt;strong&gt;"what's the easiest thing to grant?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's it. That's the whole philosophy. It's less exciting than a security framework, and it works better than most of them, because it doesn't rely on anyone remembering to clean up later. It just... never lets the mess start.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92l35cg0apcdwt6vx5h4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92l35cg0apcdwt6vx5h4.png" alt=" " width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The uncomfortable truth is that most of us have been on both sides of this. You've asked for more access than you needed because asking twice felt annoying. You've granted more than someone needed because saying "let me scope this properly" felt like friction nobody had time for. Neither of those decisions feels dangerous in the moment. They only feel dangerous in hindsight, usually during an incident review, usually at a time nobody wanted to be having that conversation.&lt;/p&gt;

&lt;p&gt;So — be honest. Have you ever been handed the Iron Throne of some system just because it was easier than figuring out what you actually needed? Or worse, have you &lt;em&gt;been&lt;/em&gt; the one handing it out?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#softwareengineering&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt; &lt;code&gt;#careeradvice&lt;/code&gt; &lt;code&gt;#techculture&lt;/code&gt;&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>management</category>
      <category>security</category>
      <category>software</category>
    </item>
    <item>
      <title>Server Data Archival to Amazon S3</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Tue, 15 Sep 2026 02:03:24 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/server-data-archival-to-amazon-s3-1cao</link>
      <guid>https://dev.to/tejas_shinkar/server-data-archival-to-amazon-s3-1cao</guid>
      <description>&lt;h3&gt;
  
  
  Building a production-oriented archival workflow with Python, EC2, IAM and S3
&lt;/h3&gt;

&lt;p&gt;A common infrastructure problem sounds simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What should happen to data when an old server is about to be decommissioned?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Deleting the server isn't the difficult part. The difficult part is making sure the &lt;strong&gt;right data is preserved&lt;/strong&gt;, the migration is verified, and the process can be safely repeated without creating duplicates or silently losing files.&lt;/p&gt;

&lt;p&gt;That became the problem I decided to solve.&lt;/p&gt;




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

&lt;p&gt;Imagine a legacy server with a &lt;code&gt;/data&lt;/code&gt; directory containing years of application data across &lt;code&gt;logs/&lt;/code&gt;, &lt;code&gt;reports/&lt;/code&gt;, &lt;code&gt;application/&lt;/code&gt;, and &lt;code&gt;backups/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before decommissioning it, we need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identify which files are still within the retention period&lt;/li&gt;
&lt;li&gt;Archive eligible files&lt;/li&gt;
&lt;li&gt;Skip older files&lt;/li&gt;
&lt;li&gt;Preserve the directory structure&lt;/li&gt;
&lt;li&gt;Upload them securely to Amazon S3&lt;/li&gt;
&lt;li&gt;Verify that uploads actually succeeded&lt;/li&gt;
&lt;li&gt;Record what happened&lt;/li&gt;
&lt;li&gt;Handle failures&lt;/li&gt;
&lt;li&gt;Safely rerun the process without duplicating data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of treating this as an S3 upload exercise, I approached it as an &lt;strong&gt;infrastructure automation problem&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Breaking the Problem Down
&lt;/h2&gt;

&lt;p&gt;I divided the work into &lt;strong&gt;three sections&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Section 1 — Script Development
&lt;/h3&gt;

&lt;p&gt;First, I focused on the automation itself. The Python script needed to follow a clear pipeline:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scan → validate → check retention → check existing archive → upload → verify → log&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This gave us a clear separation between the business logic and the AWS infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Section 2 — Server Setup
&lt;/h3&gt;

&lt;p&gt;Next, I created a simulated legacy server using an &lt;strong&gt;Amazon Linux EC2 instance&lt;/strong&gt;. Instead of simply running everything as root, I created a &lt;code&gt;/data&lt;/code&gt; directory for the source server data, &lt;code&gt;/opt/server-archival&lt;/code&gt; for the application, &lt;code&gt;/var/log/server-archival&lt;/code&gt; for application logs, a dedicated &lt;code&gt;archiver&lt;/code&gt; system user, and a Python virtual environment.&lt;/p&gt;

&lt;p&gt;The goal was to make the environment behave more like something we'd actually encounter on a managed server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Section 3 — Migration &amp;amp; Hardening
&lt;/h3&gt;

&lt;p&gt;Once the script and server were ready, I connected the environment to S3. The EC2 instance received an IAM role scoped to only &lt;code&gt;s3:PutObject&lt;/code&gt; and &lt;code&gt;s3:GetObject&lt;/code&gt; — no AWS credentials were hardcoded into the Python script.&lt;/p&gt;

&lt;p&gt;The final workflow became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Legacy EC2
    │
    │ /data
    ▼
Python Archival Script
    │
    ├── Retention Check
    ├── Idempotency Check
    ├── Upload
    ├── Verification
    └── Logging
    │
    │ IAM Role
    ▼
Amazon S3
    │
    └── archive/
         ├── logs/
         ├── reports/
         └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Retention Logic
&lt;/h2&gt;

&lt;p&gt;The requirement was to archive files &lt;strong&gt;within the last 5 years&lt;/strong&gt;. Initially, I considered simply subtracting &lt;code&gt;5 × 365&lt;/code&gt; days, but that isn't completely calendar-accurate because of leap years — so the implementation was changed to use calendar-aware date calculation.&lt;/p&gt;

&lt;p&gt;This also gave us a chance to test the &lt;strong&gt;boundary condition&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File just inside the 5-year window → Archive&lt;/li&gt;
&lt;li&gt;File just outside the window → Skip&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That kind of boundary testing is easy to overlook in a basic lab.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Preserving the File Structure
&lt;/h2&gt;

&lt;p&gt;S3 doesn't have traditional directories — instead, object keys are used. For example, &lt;code&gt;/data/reports/report.csv&lt;/code&gt; became &lt;code&gt;s3://legacy-server-archive-bucket/archive/reports/report.csv&lt;/code&gt;. The &lt;code&gt;archive/&lt;/code&gt; prefix gives us a clean separation between the original server path and the S3 archive.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Making the Process Idempotent
&lt;/h2&gt;

&lt;p&gt;This was one of the most important parts of the project. A migration script shouldn't blindly upload the same file every time it runs, so before uploading, the script checks whether the corresponding S3 object already exists:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the object already exist? Yes → skip. No → upload.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This makes the workflow &lt;strong&gt;idempotent&lt;/strong&gt; — if the script crashes halfway through and we run it again, already archived files don't need to be uploaded again.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Verification
&lt;/h2&gt;

&lt;p&gt;Uploading successfully isn't enough. After the upload, the script performs an S3 &lt;code&gt;HeadObject&lt;/code&gt; check to verify that the object exists:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not just: upload → assume success. Instead: upload → verify → log success/failure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That makes the workflow safer for an actual migration scenario.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Logging
&lt;/h2&gt;

&lt;p&gt;The script maintains structured logs in a &lt;code&gt;timestamp | level | message&lt;/code&gt; format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO | /data/reports/report.csv → Uploaded and verified
INFO | /data/backups/backup.tar.gz → Skip
INFO | /data/logs/app.log → Already archived, skipping upload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the end, it generates a summary covering files scanned, eligible, uploaded, already archived, skipped, and failed — making the execution auditable instead of relying only on terminal output.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Problems We Actually Faced
&lt;/h2&gt;

&lt;p&gt;This project became much more valuable because things didn't always work on the first attempt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amazon Linux vs Ubuntu.&lt;/strong&gt; The initial bootstrap approach used &lt;code&gt;apt&lt;/code&gt;, but the EC2 instance was Amazon Linux, so the setup had to be adapted to use &lt;code&gt;dnf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python runtime.&lt;/strong&gt; Boto3 raised a runtime-support warning with the original Python version. Instead of ignoring it, I upgraded the environment to &lt;strong&gt;Python 3.11&lt;/strong&gt; and recreated only the virtual environment — the application, data, and logs remained separate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The S3 403 problem.&lt;/strong&gt; This was probably the most interesting AWS issue. Our IAM policy intentionally didn't include &lt;code&gt;s3:ListBucket&lt;/code&gt;. When checking whether an object existed, S3 returned &lt;code&gt;403 AccessDenied&lt;/code&gt; instead of the expected &lt;code&gt;404 Not Found&lt;/code&gt;. At first this looked like an error in the idempotency logic, but it turned into an important AWS permissions lesson: &lt;strong&gt;S3's response to a missing object can depend on what permissions the caller has.&lt;/strong&gt; We adapted the check accordingly without simply adding broader permissions, preserving the least-privilege design.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Testing With Realistic Load
&lt;/h2&gt;

&lt;p&gt;Instead of testing with only 2–3 files, I created a larger test dataset — &lt;strong&gt;57 files&lt;/strong&gt; spread across different directories, file ages, eligible files, old files, and already-archived files.&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;First Run&lt;/th&gt;
&lt;th&gt;Second Run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Files scanned&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eligible&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uploaded&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Already archived&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skipped&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This gave us actual evidence that the idempotency mechanism worked.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Production-Oriented Practices
&lt;/h2&gt;

&lt;p&gt;Throughout the project, I deliberately tried to move beyond simply making the code "work."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔐 Least-privilege IAM&lt;/strong&gt; — only the required S3 object permissions were granted.&lt;br&gt;
&lt;strong&gt;🔑 No credentials in code&lt;/strong&gt; — the EC2 IAM role provided temporary AWS credentials through the instance role.&lt;br&gt;
&lt;strong&gt;👤 Dedicated service user&lt;/strong&gt; — the archival application didn't need to run as root.&lt;br&gt;
&lt;strong&gt;🔄 Idempotency&lt;/strong&gt; — repeated execution doesn't create duplicate uploads.&lt;br&gt;
&lt;strong&gt;🛡️ Validation&lt;/strong&gt; — the server path is validated before processing.&lt;br&gt;
&lt;strong&gt;🔎 Verification&lt;/strong&gt; — every successful upload is checked.&lt;br&gt;
&lt;strong&gt;♻️ Retry handling&lt;/strong&gt; — Boto3 was configured with standard retry behavior.&lt;br&gt;
&lt;strong&gt;📝 Structured logging&lt;/strong&gt; — execution results and failures are recorded.&lt;br&gt;
&lt;strong&gt;🧪 Failure &amp;amp; boundary testing&lt;/strong&gt; — the workflow was tested against different file ages, permissions, and AWS behavior.&lt;br&gt;
&lt;strong&gt;🏃 Dry-run capability&lt;/strong&gt; — the script can identify what would be archived without actually uploading it.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. What I Took Away
&lt;/h2&gt;

&lt;p&gt;The biggest learning wasn't &lt;strong&gt;how to upload a file to S3&lt;/strong&gt;. It was learning to think about a cloud problem as a system:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What happens if it fails?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;What happens if I run it twice?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;What permissions does it really need?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;How do I verify the result?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;How do I know what happened after the process finishes?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those questions changed the project from a simple AWS exercise into a &lt;strong&gt;production-oriented cloud automation workflow&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Legacy Server (EC2)
       │
       │ /data
       ▼
Python Archival Automation
       │
       ├── Validate
       ├── Retention Filter
       ├── Idempotency
       ├── Upload
       ├── Verification
       └── Logging
       │
       ▼
IAM Role
(Least Privilege)
       │
       ▼
Amazon S3
       │
       └── archive/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tech stack:&lt;/strong&gt; AWS EC2 · Amazon S3 · IAM · Python · Boto3 · Linux · Cloud Automation&lt;/p&gt;

&lt;p&gt;The project is available on GitHub as &lt;code&gt;server-data-archival&lt;/code&gt;, including the implementation, IAM policy, documentation, and test evidence.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>The Easy Part of Coding Got Automated. The Judgment Part Didn't.</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Fri, 11 Sep 2026 12:55:31 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/the-easy-part-of-coding-got-automated-the-judgment-part-didnt-5aa3</link>
      <guid>https://dev.to/tejas_shinkar/the-easy-part-of-coding-got-automated-the-judgment-part-didnt-5aa3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs93z9vsmf96ty4x7ad3d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs93z9vsmf96ty4x7ad3d.png" alt=" " width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I Thought Coding Was the Hard Part. Then AI Started Writing It For Me.&lt;/p&gt;

&lt;p&gt;Asked Claude to write me a Lambda function. Done in 10 seconds.&lt;br&gt;
Asked it which IAM permissions it actually needed. Silence.&lt;/p&gt;

&lt;p&gt;So I read the generated code myself. It had asked for s3:&lt;em&gt;, dynamodb:&lt;/em&gt;, and a wildcard resource. All Claude did was give the function room to work, not room to fail safely, but I'm the one who has to decide the difference.&lt;/p&gt;

&lt;p&gt;Turns out the easy part got automated. The judgment part didn't.&lt;br&gt;
I spent longer scoping the IAM policy down to specific actions and resources than the AI spent writing the whole function.&lt;/p&gt;

&lt;p&gt;That's the actual shift nobody's meme-ing about: the code got cheap, review got expensive. Writing was never the bottleneck for anyone who was good at their job. Knowing what "good" looks like still is.&lt;/p&gt;

&lt;p&gt;What's one thing AI still can't decide for you?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>aws</category>
      <category>programming</category>
    </item>
    <item>
      <title>AWS Route 53 Hands-On Labs</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:53:29 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-route-53-hands-on-labs-5cc8</link>
      <guid>https://dev.to/tejas_shinkar/aws-route-53-hands-on-labs-5cc8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This practical set covered the core Route 53 DNS and routing concepts through progressively more realistic scenarios.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the concepts behind these practicals, see the Route 53 session: &lt;a href="https://dev.to/tejas_shinkar/aws-route-53-dns-fundamentals-hosted-zones-routing-policies-resolvers-2727"&gt;AWS Route 53: DNS Fundamentals, Hosted Zones, Routing Policies &amp;amp; Resolvers&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical 1 - Public Hosted Zone + A Record
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Understand a public hosted zone, create an A record, point it to an EC2 public IP, and understand TTL behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Did
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Created a &lt;strong&gt;Public Hosted Zone&lt;/strong&gt;: &lt;code&gt;route53-lab.example&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Route 53 automatically created the &lt;code&gt;NS&lt;/code&gt; and &lt;code&gt;SOA&lt;/code&gt; records.&lt;/li&gt;
&lt;li&gt;Created an A record: &lt;code&gt;www.route53-lab.example → EC2 public IP&lt;/code&gt;, TTL &lt;code&gt;60&lt;/code&gt; seconds, Routing &lt;code&gt;Simple&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Used &lt;strong&gt;Route 53 → Test record&lt;/strong&gt; and confirmed &lt;code&gt;No Error&lt;/code&gt;, with the response resolving to the EC2 public IP.&lt;/li&gt;
&lt;li&gt;Changed the A record's value and tested again to confirm the update took effect.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;The Route 53 record was successfully created and tested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Learning
&lt;/h3&gt;

&lt;p&gt;TTL controls how long a DNS resolver may cache an answer, it does not mean Route 53 itself takes that amount of time to update. Because no real domain was owned or delegated, actual public-browser resolution and real-world TTL propagation could not be tested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup
&lt;/h3&gt;

&lt;p&gt;Deleted the custom A record first, then deleted the hosted zone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical 2 - Private Hosted Zone
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Create internal DNS that resolves within an associated VPC.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Did
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Created a &lt;strong&gt;Private Hosted Zone&lt;/strong&gt;: &lt;code&gt;internal.local&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Associated it with the VPC containing the EC2 instance.&lt;/li&gt;
&lt;li&gt;Created &lt;code&gt;payment.internal.local → 10.200.1.45&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;SSH'd into the EC2 and ran:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nslookup payment.internal.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verified the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server:  172.31.0.2
Address: 172.31.0.2#53

Name:    payment.internal.local
Address: 10.200.1.45
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;Private DNS resolution worked successfully; &lt;code&gt;172.31.0.2&lt;/code&gt; is the VPC DNS resolver, and it correctly returned &lt;code&gt;10.200.1.45&lt;/code&gt; as the answer for the record, using the Private Hosted Zone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup
&lt;/h3&gt;

&lt;p&gt;Deleted the custom A record and then deleted the private hosted zone.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Understand how Public and Private Hosted Zones with the same domain can provide different DNS answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Built
&lt;/h3&gt;

&lt;p&gt;Created both a Public and a Private Hosted Zone using the same domain, &lt;code&gt;split.local&lt;/code&gt; - the Private Hosted Zone was associated with the EC2's VPC.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public&lt;/strong&gt; &lt;code&gt;www.split.local&lt;/code&gt; → &lt;code&gt;13.127.147.3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private&lt;/strong&gt; &lt;code&gt;www.split.local&lt;/code&gt; → &lt;code&gt;172.31.12.251&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;From the EC2 inside the VPC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nslookup www.split.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name:    www.split.local
Address: 172.31.12.251
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;private answer won&lt;/strong&gt; for the query originating inside the associated VPC. The private A record was then removed and the query repeated - the result was &lt;code&gt;NXDOMAIN&lt;/code&gt;, not a fallback to the public zone. This confirmed the private hosted zone continued to take precedence for VPC-originated queries; it simply doesn't have a fallback path to the public zone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;Split-horizon behavior was demonstrated successfully.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Inside the associated VPC → the Private Hosted Zone answers. From public DNS → the Public Hosted Zone answers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The public side wasn't independently verified from outside the VPC, since the test domain wasn't a real delegated domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup
&lt;/h3&gt;

&lt;p&gt;Deleted the custom records and both hosted zones.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical 4 - Weighted Routing / Canary Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Understand weighted DNS routing and how it can support canary-style releases.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Built
&lt;/h3&gt;

&lt;p&gt;Created a Public Hosted Zone (&lt;code&gt;canary-lab.example&lt;/code&gt;) with two A records sharing the same name, &lt;code&gt;app.canary-lab.example&lt;/code&gt;, both with TTL &lt;code&gt;60&lt;/code&gt; and Routing &lt;code&gt;Weighted&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Record&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;th&gt;Record ID&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;td&gt;&lt;code&gt;13.127.147.3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stable&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canary&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.2.3.4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;canary&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Used &lt;strong&gt;Route 53 → Test record&lt;/strong&gt; repeatedly. &lt;code&gt;13.127.147.3&lt;/code&gt; came back most of the time, while &lt;code&gt;1.2.3.4&lt;/code&gt; appeared much less frequently - roughly consistent with the 90/10 split, though not an exact 1-in-10 guarantee on any given batch of queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;Weighted routing behavior was demonstrated. In production, this pattern supports gradual rollout:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;90% old version / 10% new version → monitor → 70/30 → 50/50 → 10/90 → 0/100&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Important Distinction
&lt;/h3&gt;

&lt;p&gt;Route 53 weighted routing chooses the endpoint returned in the DNS response — it is not an application-layer proxy and doesn't replace an ALB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup
&lt;/h3&gt;

&lt;p&gt;Deleted the weighted records and hosted zone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical 5 - Failover Routing + Health Check
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Configure a primary/secondary DNS failover setup and verify that Route 53 serves the secondary endpoint when the primary becomes unhealthy.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Built
&lt;/h3&gt;

&lt;p&gt;Two EC2 instances (Primary and Secondary), each returning a distinct response body (&lt;code&gt;PRIMARY SERVER&lt;/code&gt; / &lt;code&gt;SECONDARY SERVER&lt;/code&gt;) so the active endpoint could be identified from the response alone.&lt;/p&gt;

&lt;p&gt;A Route 53 health check (&lt;code&gt;primary-ec2-health&lt;/code&gt;) was created: HTTP, port 80, path &lt;code&gt;/&lt;/code&gt;, 30-second interval, failure threshold 3.&lt;/p&gt;

&lt;p&gt;A Public Hosted Zone (&lt;code&gt;failover-lab.example&lt;/code&gt;) was created with &lt;code&gt;app.failover-lab.example&lt;/code&gt; configured as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Primary&lt;/strong&gt; - A record, Failover = PRIMARY, → &lt;code&gt;13.127.147.3&lt;/code&gt;, health check attached, TTL 60&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secondary&lt;/strong&gt; - A record, Failover = SECONDARY, → &lt;code&gt;65.0.61.115&lt;/code&gt;, TTL 60&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;With the primary stopped/unhealthy, the Route 53 Test Record result changed to &lt;code&gt;65.0.61.115&lt;/code&gt;, demonstrating the failover path:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Primary unhealthy → Route 53 → Secondary&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;The primary-to-secondary failover behavior was successfully demonstrated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting Observation
&lt;/h3&gt;

&lt;p&gt;When the Primary EC2 was started again, the Route 53 health check continued to show &lt;code&gt;Unhealthy&lt;/code&gt;, with health-checker details reporting &lt;code&gt;Connection timed out&lt;/code&gt; from multiple health-checker locations. Recovery from Secondary back to Primary was therefore &lt;strong&gt;not successfully verified&lt;/strong&gt; in this lab — the issue was connectivity to the public HTTP endpoint, not the failover record configuration itself, which had already demonstrated the secondary path correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Learning
&lt;/h3&gt;

&lt;p&gt;Failover routing depends on &lt;strong&gt;endpoint health&lt;/strong&gt;, not merely whether an EC2 instance is powered on. Health-check detection combined with DNS caching/TTL also means client-visible failover isn't necessarily instantaneous.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup
&lt;/h3&gt;

&lt;p&gt;Deleted the primary/secondary failover records, the hosted zone, the Route 53 health check, and the Secondary EC2. The original EC2 used for other labs was retained.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical 6 - Resolver Endpoints (Conceptual)
&lt;/h2&gt;

&lt;p&gt;This practical was kept conceptual rather than deploying Route 53 Resolver endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario A - On-Premises → AWS Private Hosted Zone
&lt;/h3&gt;

&lt;p&gt;An on-premises server needs to resolve &lt;code&gt;app.internal&lt;/code&gt;, where the name exists in an AWS Private Hosted Zone:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On-premises server → on-prem DNS → AWS Resolver Inbound Endpoint → VPC DNS / Private Hosted Zone → AWS private resource&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;strong&gt;Inbound Resolver Endpoint&lt;/strong&gt; receives DNS queries from outside the VPC.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario B — EC2 → On-Premises DNS
&lt;/h3&gt;

&lt;p&gt;An EC2 instance needs to resolve &lt;code&gt;server.company.local&lt;/code&gt;, where the authoritative DNS server exists on-premises:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EC2 → VPC DNS → AWS Resolver Outbound Endpoint → network connection to on-premises DNS → on-prem DNS server → DNS response&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Key Distinction
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Inbound endpoint&lt;/strong&gt; → DNS queries coming &lt;em&gt;into&lt;/em&gt; AWS/VPC from external networks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outbound endpoint&lt;/strong&gt; → DNS queries going &lt;em&gt;out&lt;/em&gt; from AWS/VPC to external DNS servers.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Route 53 Core Mental Model
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Simple routing:&lt;/strong&gt; one record → one/simple answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weighted routing:&lt;/strong&gt; same name, multiple records, weighted distribution — useful for canary releases, gradual migration, and traffic splitting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failover routing:&lt;/strong&gt; primary + health check → healthy routes to Primary, unhealthy routes to Secondary — useful for high availability and disaster recovery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private Hosted Zone:&lt;/strong&gt; VPC → Private Hosted Zone → internal DNS names — useful for internal services, private application endpoints, and service discovery patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Split-Horizon DNS:&lt;/strong&gt; the same domain name resolves differently depending on whether the query comes from the public view or the private VPC view — useful whenever the same hostname needs to answer differently based on where the requester is.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Core interview mental model:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public Hosted Zone → Internet-facing DNS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private Hosted Zone → VPC/internal DNS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weighted → distribute DNS answers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failover → choose Primary while healthy, Secondary when unhealthy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inbound Resolver → queries into AWS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outbound Resolver → queries from AWS to external DNS&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>AWS Observability &amp; Automation — CloudWatch, CloudTrail &amp; EventBridge</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Fri, 04 Sep 2026 12:14:28 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-observability-automation-cloudwatch-cloudtrail-eventbridge-2k8</link>
      <guid>https://dev.to/tejas_shinkar/aws-observability-automation-cloudwatch-cloudtrail-eventbridge-2k8</guid>
      <description>&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;The Big Picture — Why Three Different Services&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;CloudWatch — Monitoring &amp;amp; Architecture&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;CloudWatch Metrics — Namespace, Dimensions, Statistic, Period&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;High-Resolution Metrics&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;CloudWatch Logs — Groups, Streams, Events, Retention&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;CloudWatch Logs Insights&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;CloudWatch Dashboards&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;CloudWatch Alarms — States, Evaluation, Actions&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;CloudWatch Logs → S3 Export&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Multi-Account and Multi-Region Monitoring&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;EC2 Instance Recovery&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;CloudTrail — Management Events&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;CloudTrail — Data Events&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;CloudTrail Insights&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;Network Activity Events vs VPC Flow Logs&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;EventBridge — Event Bus &amp;amp; Rules&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;EventBridge Scheduler&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;CloudWatch vs CloudTrail vs EventBridge&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;How the Services Work Together&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;Production Mental Model&lt;/td&gt;
&lt;td&gt;DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;Practice Tasks 1–5&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
&lt;/h2&gt;

&lt;p&gt;AWS environments continuously generate operational and audit data. The challenge isn't collecting it — it's turning it into something useful before it turns into an incident:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Collect → Monitor → Store → Analyze → Act&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each of the three services in this post answers a different question, and mixing them up is one of the most common gaps in AWS troubleshooting interviews and real incident response alike:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch&lt;/strong&gt; → &lt;em&gt;How is the system behaving?&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudTrail&lt;/strong&gt; → &lt;em&gt;What happened in the AWS account, and who did it?&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge&lt;/strong&gt; → &lt;em&gt;An event happened — what should happen next?&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They're rarely used in isolation. A real incident usually touches all three: CloudWatch tells you something's wrong, CloudTrail tells you what changed, and EventBridge is what automatically reacts the next time it happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Amazon CloudWatch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is Monitoring?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; It's 2 AM. Checkout requests on an e-commerce site start failing. Nobody manually noticed — an alarm did, and paged the on-call engineer with the exact metric that crossed its threshold, five minutes before customers started tweeting about it.&lt;/p&gt;

&lt;p&gt;That's what monitoring is for: continuously observing systems and applications so abnormal behavior gets detected, investigated, and acted on — ideally before a human notices the hard way. Common signals worth watching include EC2 CPU utilization and network traffic, RDS connections, SQS queue depth, Lambda errors, application error rates, and request latency.&lt;/p&gt;

&lt;p&gt;The operational loop behind this: a problem occurs → telemetry is collected → CloudWatch ingests it → the anomaly is detected and investigated → an alarm, automation, or remediation responds. The production goal is never "watch a dashboard all day" — it's catching meaningful changes early and responding to them, ideally with as little human involvement as the situation allows.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudWatch Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    DATA SOURCES
                         │
        ┌────────────────┼─────────────────┐
        │                │                 │
   AWS Resources    Applications      Multiple
   EC2/ECS/RDS/     &amp;amp; Services        Accounts/Regions
   Lambda/S3/etc.
        │                │                 │
        └────────────────┼─────────────────┘
                         ▼
                  AMAZON CLOUDWATCH
                         │
          ┌──────────────┼──────────────┐
          ▼              ▼              ▼
       Metrics          Logs          Alarms
          │              │              │
          ▼              ▼              ▼
     Dashboards     Logs Insights     Actions
                                     / Integrations
                                          │
                    ┌─────────────────────┼─────────────────┐
                    ▼                     ▼                 ▼
                   SNS                 Lambda          Auto Scaling
                    │
                    ▼
               Notifications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful mental model to keep in mind for everything that follows: &lt;strong&gt;metrics measure behavior, logs describe events and details, alarms detect a condition, and actions respond to that condition.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudWatch Metrics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Your monitoring dashboard shows &lt;code&gt;CPUUtilization&lt;/code&gt; climbing from 8% at 10:00, to 11% at 10:01, to 37% at 10:02, to 82% at 10:03 — a metric is exactly that: a numerical measurement observed over time, and that climb is what your alarm will eventually key off of.&lt;/p&gt;

&lt;p&gt;Common AWS metrics worth knowing by heart: EC2's &lt;code&gt;CPUUtilization&lt;/code&gt;, &lt;code&gt;NetworkIn&lt;/code&gt;/&lt;code&gt;NetworkOut&lt;/code&gt;, and disk I/O; SQS's &lt;code&gt;ApproximateNumberOfMessagesVisible&lt;/code&gt;; Lambda's &lt;code&gt;Invocations&lt;/code&gt;, &lt;code&gt;Errors&lt;/code&gt;, and &lt;code&gt;Duration&lt;/code&gt;; and RDS's &lt;code&gt;CPUUtilization&lt;/code&gt; and &lt;code&gt;DatabaseConnections&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Every metric is identified by a combination of parts, and understanding each one is what separates "I set up a metric" from "I understand what I'm actually alarming on":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Namespace&lt;/strong&gt; — groups related metrics, e.g. &lt;code&gt;AWS/EC2&lt;/code&gt;, &lt;code&gt;AWS/SQS&lt;/code&gt;, &lt;code&gt;AWS/Lambda&lt;/code&gt;, &lt;code&gt;AWS/RDS&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metric name&lt;/strong&gt; — what's being measured, e.g. &lt;code&gt;CPUUtilization&lt;/code&gt;, &lt;code&gt;Errors&lt;/code&gt;, &lt;code&gt;Duration&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dimensions&lt;/strong&gt; — identify the specific resource, e.g. &lt;code&gt;InstanceId=i-0123456789&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Statistic&lt;/strong&gt; — how CloudWatch summarizes the raw values: Average, Minimum, Maximum, Sum, SampleCount&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Period&lt;/strong&gt; — the time interval per datapoint, e.g. 1 minute, 5 minutes, 1 hour&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put together, a complete metric reference looks like: namespace &lt;code&gt;AWS/EC2&lt;/code&gt;, metric &lt;code&gt;CPUUtilization&lt;/code&gt;, dimension &lt;code&gt;InstanceId=i-0123456789&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The period choice genuinely changes alarm behavior — &lt;code&gt;CPU &amp;gt; 80% for 1 minute&lt;/code&gt; will fire far more readily than &lt;code&gt;CPU &amp;gt; 80% for 30 minutes&lt;/code&gt;. Alarm design should match the failure condition that actually matters to the business, not just "make the number go away."&lt;/p&gt;

&lt;h3&gt;
  
  
  High-Resolution Metrics
&lt;/h3&gt;

&lt;p&gt;CloudWatch also supports high-resolution custom metrics for workloads that need finer-grained monitoring than the standard 1-minute granularity. The production decision here isn't &lt;em&gt;"higher resolution is always better"&lt;/em&gt; — it's &lt;em&gt;"use the smallest monitoring interval that's actually useful for the problem."&lt;/em&gt; Higher resolution helps with fast-changing systems, but it increases telemetry volume and cost, so it should be a deliberate choice, not a default.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudWatch Logs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; A metric shows &lt;code&gt;Lambda Errors = 37&lt;/code&gt; — useful, but it doesn't tell you &lt;em&gt;why&lt;/em&gt;. The corresponding log line does: &lt;code&gt;ERROR Database connection timeout, requestId=..., service=order-service&lt;/code&gt;. That's the core distinction — a &lt;strong&gt;metric&lt;/strong&gt; tells you what level or how many, a &lt;strong&gt;log&lt;/strong&gt; tells you what exactly happened. For real troubleshooting, you need both.&lt;/p&gt;

&lt;p&gt;CloudWatch Logs is organized hierarchically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/aws/lambda/order-service
    │
    ├── Stream A
    │     ├── Event
    │     ├── Event
    │     └── Event
    │
    └── Stream B
          ├── Event
          └── Event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;Log Group&lt;/strong&gt; is a logical collection of related logs (e.g. &lt;code&gt;/aws/lambda/order-service&lt;/code&gt;). A &lt;strong&gt;Log Stream&lt;/strong&gt; is a sequence of log events from a particular source or execution context within that group. A &lt;strong&gt;Log Event&lt;/strong&gt; is a single entry, like &lt;code&gt;2026-09-04 10:31:12 ERROR Database timeout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log retention&lt;/strong&gt; shouldn't default to "forever." A production logging strategy decides how long each log group should be kept, weighing troubleshooting needs, compliance, cost, security requirements, and audit requirements — and operational logs often warrant a different retention period than security/audit logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudWatch Logs Insights
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; An incident just happened, and someone asks "how many &lt;code&gt;AccessDenied&lt;/code&gt; errors did we see in the last hour, and from which service?" Scrolling through raw logs isn't an option at scale — this is exactly what Logs Insights is for: querying and aggregating large volumes of log data to turn raw logs into searchable operational evidence.&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;fields&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical questions it answers well: which requests generated 500 errors, how many &lt;code&gt;AccessDenied&lt;/code&gt; errors happened, which service is producing the most failures, and what happened during a specific incident window.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudWatch Dashboards
&lt;/h3&gt;

&lt;p&gt;A dashboard provides a centralized operational view. A production application dashboard might track request rate, latency, 5xx errors, EC2 CPU, RDS connections, SQS queue depth, and Lambda errors — but the goal should be decision-making, not visualizing everything you possibly can. A genuinely useful dashboard answers one clear question: &lt;em&gt;can users currently use the application normally?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudWatch Alarms
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; &lt;code&gt;CPUUtilization &amp;gt; 80%&lt;/code&gt; is set as the alarm condition. At 30% CPU, the alarm sits in &lt;code&gt;OK&lt;/code&gt;. At 92% CPU, it flips to &lt;code&gt;ALARM&lt;/code&gt;. If there isn't yet enough usable data — a brand-new alarm, or a metric that hasn't produced enough datapoints — it shows &lt;code&gt;INSUFFICIENT_DATA&lt;/code&gt; instead of guessing.&lt;/p&gt;

&lt;p&gt;The basic flow: a metric is evaluated against a condition → the alarm enters a state → that state triggers an action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alarm evaluation settings matter more than they look.&lt;/strong&gt; Behavior depends on the metric, statistic, period, threshold, evaluation periods, and datapoints-to-alarm — because production alerting should avoid turning every temporary spike into an unnecessary page. &lt;code&gt;CPU &amp;gt; 80% for 1 datapoint&lt;/code&gt; is far more trigger-happy than &lt;code&gt;CPU &amp;gt; 80% for several consecutive evaluation periods&lt;/code&gt;. Good alerting detects meaningful failure conditions, not just unusual-looking numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alarms and actions are two separate concerns.&lt;/strong&gt; A CloudWatch alarm can trigger SNS notifications, Auto Scaling actions, EC2 recovery, or other supported integrations — for example, &lt;code&gt;CPU &amp;gt; threshold → CloudWatch Alarm → SNS → Email/other subscribers&lt;/code&gt;. The alarm makes the decision; SNS (or whatever the target is) handles distribution. Keeping that distinction clear avoids a lot of confused debugging later ("the alarm fired but nobody got notified" is almost always an SNS/target problem, not an alarm problem).&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudWatch Logs → S3 Export
&lt;/h3&gt;

&lt;p&gt;CloudWatch Logs is built for active operations and investigation; S3 is built for durable long-term storage and archival. A logging design should intentionally decide what stays "hot" in CloudWatch versus what gets exported to S3 (&lt;code&gt;CloudWatch Logs → export/archive → Amazon S3&lt;/code&gt;) — common reasons include long-term retention, compliance, archival, external analytics, and central data storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Account and Multi-Region Monitoring
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Your organization runs Production, Development, Staging, and Security in separate AWS accounts, spread across two Regions. Asking an operator to individually check each account during an incident doesn't scale.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Account A ─────┐
Account B ─────┤
Account C ─────┼──→ Central Monitoring Account
Region 1 ──────┤
Region 2 ──────┘
                         │
                         ▼
                 Dashboards / Alarms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A centralized monitoring approach provides visibility from a single monitoring account instead — this is the basis of centralized observability at organizational scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  EC2 Instance Recovery
&lt;/h3&gt;

&lt;p&gt;EC2 can be configured to recover automatically from certain underlying system/host failures using a CloudWatch alarm — commonly based on &lt;code&gt;StatusCheckFailed_System&lt;/code&gt;, which flows &lt;code&gt;StatusCheckFailed_System → CloudWatch Alarm → EC2 Recovery&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The important distinction: &lt;strong&gt;instance recovery targets underlying host/system problems, not arbitrary application failures.&lt;/strong&gt; An underlying host issue is exactly what recovery is for — but Apache crashing, or an application bug, is a completely different failure mode that instance recovery does nothing to fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  AWS CloudTrail
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is CloudTrail?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Someone deletes an S3 bucket that shouldn't have been deletable. CloudWatch won't tell you who did it — that's not what it's for. CloudTrail is: it answers what API call happened, who performed it, when it happened, which resource was involved, and what request context was recorded.&lt;/p&gt;

&lt;p&gt;The cleanest way to keep CloudWatch and CloudTrail straight: &lt;strong&gt;CloudWatch asks "how is the system behaving?" CloudTrail asks "what activity happened in AWS?"&lt;/strong&gt; CloudTrail is therefore heavily used for auditing, security investigation, change tracking, incident investigation, governance, and compliance.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudTrail Management Events
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Management events&lt;/strong&gt; cover control-plane operations that change or inspect AWS resources — things like &lt;code&gt;CreateBucket&lt;/code&gt;, &lt;code&gt;RunInstances&lt;/code&gt;, &lt;code&gt;CreateRole&lt;/code&gt;, &lt;code&gt;AttachRolePolicy&lt;/code&gt;, &lt;code&gt;CreateSubnet&lt;/code&gt;, or &lt;code&gt;DeleteSecurityGroup&lt;/code&gt;. Mental model: management events are mainly about AWS resource and configuration activity. If the question is &lt;em&gt;"who changed this security configuration?"&lt;/em&gt;, CloudTrail's management events are the natural place to look.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudTrail Data Events
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Data events&lt;/strong&gt; focus on activity against supported resources' actual data — for S3, that means things like &lt;code&gt;GetObject&lt;/code&gt;, &lt;code&gt;PutObject&lt;/code&gt;, and &lt;code&gt;DeleteObject&lt;/code&gt;. The key distinction: a &lt;strong&gt;management event&lt;/strong&gt; asks "what happened to the AWS resource/configuration?" while a &lt;strong&gt;data event&lt;/strong&gt; asks "what happened to the underlying data?" Data events can be high-volume and are configured separately from management events for exactly that reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudTrail Insights
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Your S3 bucket normally sees about 10 &lt;code&gt;DeleteObject&lt;/code&gt; calls a minute. Suddenly it's 2,000 a minute. That kind of deviation from a learned baseline is exactly what CloudTrail Insights is built to surface. Mental model: &lt;strong&gt;CloudTrail records activity; CloudTrail Insights detects unusual behavior within that activity.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Network Activity Events vs VPC Flow Logs
&lt;/h3&gt;

&lt;p&gt;CloudTrail also supports network activity event logging for supported AWS network-related operations — but don't confuse this with VPC Flow Logs. They are not the same thing: &lt;strong&gt;CloudTrail&lt;/strong&gt; covers service/account activity, while &lt;strong&gt;Flow Logs&lt;/strong&gt; cover network traffic metadata. If you need to know "who called this API," that's CloudTrail. If you need to know "what packets moved between these two hosts," that's Flow Logs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Amazon EventBridge
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is EventBridge?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; An EC2 instance terminates, and instead of someone noticing it's gone twenty minutes later, an automated notification hits the operations team's Slack the moment it happens — no polling, no cron job checking instance state every few minutes. That's the value EventBridge adds: it's primarily an event routing and automation service.&lt;/p&gt;

&lt;p&gt;Its basic model: an &lt;strong&gt;event&lt;/strong&gt; arrives on an &lt;strong&gt;event bus&lt;/strong&gt;, a &lt;strong&gt;rule&lt;/strong&gt; examines the event and decides whether it matches, and if it does, the event is routed to a &lt;strong&gt;target&lt;/strong&gt;. Events can originate from AWS services, your own applications, custom applications, or supported SaaS integrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Bus
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                          Event Bus
                    ┌────────┼────────┐
                    ▼        ▼        ▼
                 Rule A   Rule B   Rule C
                    │        │        │
                    ▼        ▼        ▼
                 Lambda     SNS      SQS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event bus is where events land and get routed. This is what keeps producers and consumers loosely coupled — the service emitting the event doesn't need to know or care what eventually consumes it.&lt;/p&gt;

&lt;h3&gt;
  
  
  EventBridge Rules
&lt;/h3&gt;

&lt;p&gt;A rule means: &lt;em&gt;when an event matches this pattern, take this action.&lt;/em&gt; For example, a rule that watches for EC2 terminations:&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;"source"&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;"aws.ec2"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail-type"&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;"EC2 Instance State-change Notification"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"terminated"&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;The resulting flow: &lt;code&gt;EC2 terminated → EventBridge → rule matches → SNS / Lambda / SQS&lt;/code&gt;. This is a genuinely production-relevant event-driven automation pattern — the exact one behind the "someone gets notified automatically" scenario above.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Scheduler&lt;/strong&gt; answers a fundamentally different question than a rule does: not &lt;em&gt;"when an event happens, react,"&lt;/em&gt; but &lt;em&gt;"when should something happen?"&lt;/em&gt; Instead of waiting on an event trigger (&lt;code&gt;Event happens → EventBridge Rule&lt;/code&gt;), you schedule the action directly (&lt;code&gt;2:00 AM every day → EventBridge Scheduler → Lambda&lt;/code&gt;). Common uses: running nightly cleanup, generating a morning report, invoking a Lambda periodically, or triggering a one-time task at a specific moment.&lt;/p&gt;

&lt;p&gt;The key distinction to keep straight: &lt;strong&gt;event-driven&lt;/strong&gt; means &lt;em&gt;something happens, then react&lt;/em&gt;; &lt;strong&gt;time-driven&lt;/strong&gt; means &lt;em&gt;a scheduled time arrives, then execute.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  CloudWatch vs CloudTrail vs EventBridge
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Primary Question&lt;/th&gt;
&lt;th&gt;Typical Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CloudWatch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How is my system behaving?&lt;/td&gt;
&lt;td&gt;Metrics, logs, alarms, dashboards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CloudTrail&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What AWS activity happened?&lt;/td&gt;
&lt;td&gt;API auditing, investigation, governance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EventBridge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What should happen after an event?&lt;/td&gt;
&lt;td&gt;Routing, automation, event-driven workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A handful of quick scenario-to-service mappings worth having memorized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EC2 CPU is too high&lt;/strong&gt; → CloudWatch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Someone deleted an S3 object&lt;/strong&gt; → CloudTrail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2 termination should notify an operations team&lt;/strong&gt; → EventBridge → SNS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU breach should send an email&lt;/strong&gt; → CloudWatch Alarm → SNS → Email&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every night at 2 AM, run cleanup&lt;/strong&gt; → EventBridge Scheduler → Lambda&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How the Services Work Together
&lt;/h2&gt;

&lt;p&gt;Real production systems rarely use just one of these — they're stitched together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  Application
                      │
             ┌────────┼─────────┐
             ▼        ▼         ▼
          Metrics    Logs    API Activity
             │        │         │
             ▼        ▼         ▼
         CloudWatch CloudWatch CloudTrail
             │        │
             ▼        ▼
           Alarm   Logs Insights
             │
             ▼
            SNS


CloudTrail / AWS event
          ↓
      EventBridge
          ↓
     Lambda / SNS / SQS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a continuous operational loop: &lt;strong&gt;observe → detect → investigate → decide → act.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Mental Model
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; An incident just started. Instead of randomly clicking through the AWS Console, work through these five questions in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What is the system doing?&lt;/strong&gt; → CloudWatch metrics/logs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What exactly happened in the AWS environment?&lt;/strong&gt; → CloudTrail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What should automatically happen because of an event?&lt;/strong&gt; → EventBridge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How should people or systems be notified?&lt;/strong&gt; → SNS / other targets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What remediation should occur?&lt;/strong&gt; → Auto Scaling / Lambda / Systems Manager / recovery actions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That maps to a practical investigation flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Incident
                   │
        ┌──────────┼──────────┐
        ▼          ▼          ▼
   CloudWatch   CloudTrail  EventBridge
   telemetry     audit       automation
        │          │          │
        └──────────┼──────────┘
                   ▼
             Investigation
                   │
                   ▼
                Action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch&lt;/strong&gt; is the core operational monitoring and observability service: metrics, logs, dashboards, alarms, and related actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt; show numerical system behavior; &lt;strong&gt;logs&lt;/strong&gt; provide detailed event-level context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log Groups → Log Streams → Log Events&lt;/strong&gt; is the basic CloudWatch Logs hierarchy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs Insights&lt;/strong&gt; is for querying and aggregating large volumes of log data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alarms&lt;/strong&gt; evaluate metric conditions and can trigger actions such as SNS notifications, scaling, or recovery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudTrail&lt;/strong&gt; records AWS account/API activity and is central to auditing and investigation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Management Events&lt;/strong&gt; focus on control-plane activity; &lt;strong&gt;Data Events&lt;/strong&gt; capture supported data-plane activity such as S3 object operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudTrail Insights&lt;/strong&gt; helps detect unusual API activity against established baselines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge&lt;/strong&gt; routes events to targets and enables event-driven automation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge Scheduler&lt;/strong&gt; is for time-driven execution.&lt;/li&gt;
&lt;li&gt;Production observability often combines all three services rather than choosing only one.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Remember the three questions:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudWatch — How is it behaving?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudTrail — What happened?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EventBridge — What should happen next?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;h3&gt;
  
  
  Practice Task 1 — Custom Metric, Alarm &amp;amp; Notification
&lt;/h3&gt;

&lt;p&gt;Publish a custom CloudWatch metric from an application (or a simple script) representing something business-relevant — e.g. failed login attempts or order-processing time — and configure an alarm on it that notifies an SNS topic when the threshold is breached. Pick a period and evaluation-periods combination deliberately, and be able to justify why it won't fire on a harmless single-datapoint spike.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Task 2 — Investigate an Incident with Logs Insights
&lt;/h3&gt;

&lt;p&gt;Given a Lambda function producing a realistic mix of successful and failing invocations, write Logs Insights queries to answer: how many errors occurred in the last hour, which error message is most frequent, and what the request pattern looked like in the five minutes immediately before the first failure. Produce a short summary of what the queries reveal, as if handing it to an incident commander.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Task 3 — Trace a Change with CloudTrail
&lt;/h3&gt;

&lt;p&gt;Make a deliberate, traceable change in your account — modify a security group rule, delete an S3 object, or alter an IAM policy — then use CloudTrail to answer who made the change, exactly when, from what source IP, and using which API call. Separately, identify whether the change would show up as a management event or a data event, and explain why.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Task 4 — Event-Driven Automation with EventBridge
&lt;/h3&gt;

&lt;p&gt;Build an EventBridge rule that reacts to a real AWS event (e.g. an EC2 instance state change or an S3 object creation) and routes it to a target of your choice (SNS, Lambda, or SQS). Trigger the underlying event for real, and confirm the target actually received and processed it — not just that the rule matched in the console.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Task 5 — Full-Stack Incident Response Scenario
&lt;/h3&gt;

&lt;p&gt;Simulate a small production incident end to end: use CloudWatch to detect a threshold breach (e.g. elevated errors or CPU), use CloudTrail to confirm whether any recent account change correlates with the timing of the incident, and use EventBridge to automatically trigger a remediation action (e.g. invoking a Lambda function or notifying a team) the moment the CloudWatch alarm changes state. Document the full chain — metric → alarm → event → automation — as a single incident timeline.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>AWS SNS + SQS Hands-On Lab</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Thu, 03 Sep 2026 12:28:47 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-sns-sqs-hands-on-lab-1d24</link>
      <guid>https://dev.to/tejas_shinkar/aws-sns-sqs-hands-on-lab-1d24</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Two practical exercises covering SNS fan-out with SQS/DLQ, and CloudWatch alarm notifications delivered through SNS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the concepts behind these practicals, see: &lt;a href="https://dev.to/tejas_shinkar/aws-sqs-sns-decoupling-services-with-queues-topics-fan-out-patterns-bdk"&gt;AWS SQS + SNS — Decoupling Services with Queues, Topics &amp;amp; Fan-Out Patterns&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SNS → SQS Fan-Out with Retry and Dead-Letter Queue
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Build an order-event messaging flow where SNS publishes an event to an SQS queue, then deliberately leave a message unprocessed to observe SQS retries and its eventual move to a dead-letter queue.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Publisher
   │
   ▼
SNS Topic
 order-events
   │
   ▼
SQS: order-processing-queue
   │
   ├── processed successfully → deleted
   │
   └── maxReceiveCount (3) exceeded
             │
             ▼
     SQS DLQ: order-processing-dlq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SNS Topic:&lt;/strong&gt; &lt;code&gt;order-events&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQS main queue:&lt;/strong&gt; &lt;code&gt;order-processing-queue&lt;/code&gt; — Standard type, 30s visibility timeout, 4-day retention, 20s receive wait time, DLQ &lt;code&gt;order-processing-dlq&lt;/code&gt;, &lt;code&gt;maxReceiveCount = 3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQS DLQ:&lt;/strong&gt; &lt;code&gt;order-processing-dlq&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1 — Build the Queue and Topic
&lt;/h3&gt;

&lt;p&gt;Created the DLQ (&lt;code&gt;order-processing-dlq&lt;/code&gt;), then the main queue (&lt;code&gt;order-processing-queue&lt;/code&gt;) configured to use that DLQ with &lt;code&gt;maxReceiveCount = 3&lt;/code&gt;. Created the SNS topic (&lt;code&gt;order-events&lt;/code&gt;) and subscribed the main queue to it using the SQS subscription protocol. Verified the queue policy allowed SNS to deliver messages: principal &lt;code&gt;sns.amazonaws.com&lt;/code&gt;, action &lt;code&gt;sqs:SendMessage&lt;/code&gt;, restricted to source ARN &lt;code&gt;order-events&lt;/code&gt; — so only that specific topic can publish into the queue.&lt;/p&gt;

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

&lt;p&gt;Published a test order event to SNS:&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;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer"&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;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2499&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"created"&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;The message appeared in &lt;code&gt;order-processing-queue&lt;/code&gt;, confirming SNS → SQS delivery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Receive-Count / DLQ Test
&lt;/h3&gt;

&lt;p&gt;The message was polled repeatedly without being deleted, to observe the retry-to-DLQ behavior:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Receive #1 → not deleted → visibility timeout expires → Receive #2 → not deleted → Receive #3 → &lt;code&gt;maxReceiveCount&lt;/code&gt; reached → message moved to DLQ&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The message then appeared in &lt;code&gt;order-processing-dlq&lt;/code&gt;, showing a receive count of &lt;code&gt;4&lt;/code&gt; once polled there — that count reflects its full receive history on the main queue plus the DLQ, not that SNS published it four times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;SNS → SQS delivery confirmed. SQS receive → not deleted → retried → &lt;code&gt;maxReceiveCount&lt;/code&gt; reached → moved to DLQ, confirmed end to end.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Key Practical Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Receiving an SQS message does not automatically delete it.&lt;/li&gt;
&lt;li&gt;A consumer should delete a message only after successful processing.&lt;/li&gt;
&lt;li&gt;Visibility timeout gives the consumer time to complete processing.&lt;/li&gt;
&lt;li&gt;Repeated unsuccessful processing can be isolated in a DLQ.&lt;/li&gt;
&lt;li&gt;SNS decouples the publisher from downstream consumers.&lt;/li&gt;
&lt;li&gt;The SQS queue policy can restrict which SNS topic can send messages.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  CloudWatch CPU Alarm → SNS Email Notifications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Create a CloudWatch alarm that monitors EC2 CPU utilization and sends an SNS notification to multiple confirmed email subscriptions when the threshold is exceeded.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2 Instance
   │ CPUUtilization metric
   ▼
CloudWatch Alarm
 ec2-high-cpu-alert
   │ threshold breached → ALARM
   ▼
SNS Topic: cloudwatch-alerts
   │
   ├── Email 1 (confirmed)
   └── Email 2 (confirmed)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SNS Topic:&lt;/strong&gt; &lt;code&gt;cloudwatch-alerts&lt;/code&gt;, with two confirmed email subscriptions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Alarm:&lt;/strong&gt; &lt;code&gt;ec2-high-cpu-alert&lt;/code&gt; — metric &lt;code&gt;CPUUtilization&lt;/code&gt;, statistic Average, 1-minute period, condition &lt;code&gt;&amp;gt; 10%&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1 — Verify SNS Delivery Independently
&lt;/h3&gt;

&lt;p&gt;Before involving CloudWatch, a direct test message was published straight to &lt;code&gt;cloudwatch-alerts&lt;/code&gt;. Both confirmed email addresses received it, proving SNS delivery worked on its own — so any later failure could be isolated to the alarm side rather than the notification side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Trigger the Alarm
&lt;/h3&gt;

&lt;p&gt;The alarm started in &lt;code&gt;OK&lt;/code&gt; once CloudWatch had sufficient data. CPU load was then generated and later stopped:&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;# Generate load&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;1..4&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &amp;amp; &lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Stop load&lt;/span&gt;
pkill &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CPU crossed the 10% threshold and the alarm state changed to &lt;code&gt;ALARM&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;EC2 CPU &amp;gt; 10% → CloudWatch Alarm → &lt;code&gt;ALARM&lt;/code&gt; → SNS &lt;code&gt;cloudwatch-alerts&lt;/code&gt; → Email 1 ✅ → Email 2 ✅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both confirmed subscriptions received the notification, verifying the full path from metric breach to alarm state change to SNS fan-out to multiple recipients.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Practical Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;SNS works well as a notification fan-out layer for operational alerts.&lt;/li&gt;
&lt;li&gt;Testing SNS separately helps isolate notification-delivery issues from alarm configuration.&lt;/li&gt;
&lt;li&gt;CloudWatch alarms depend on evaluated metric datapoints before changing state.&lt;/li&gt;
&lt;li&gt;A controlled CPU-load test is enough to validate the alarm end-to-end.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Practical Completion
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Practical&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SNS → SQS fan-out&lt;/td&gt;
&lt;td&gt;✅ Verified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQS retry / visibility behavior&lt;/td&gt;
&lt;td&gt;✅ Verified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQS → DLQ after repeated receives&lt;/td&gt;
&lt;td&gt;✅ Verified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudWatch CPU alarm&lt;/td&gt;
&lt;td&gt;✅ Verified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudWatch → SNS notification&lt;/td&gt;
&lt;td&gt;✅ Verified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple email recipients&lt;/td&gt;
&lt;td&gt;✅ Verified&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>AWS Lambda Hands-On Lab</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Wed, 02 Sep 2026 14:25:17 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-lambda-hands-on-practical-1326</link>
      <guid>https://dev.to/tejas_shinkar/aws-lambda-hands-on-practical-1326</guid>
      <description>&lt;h1&gt;
  
  
  ⚡ S3 → Lambda Image Resize Pipeline
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;An event-driven image-processing practical using Amazon S3 and AWS Lambda — verified end-to-end with CloudWatch, then deliberately broken at the IAM layer to practice diagnosing a real production-style failure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Objective
&lt;/h2&gt;

&lt;p&gt;Build a pipeline where an image uploaded to S3 automatically triggers a Lambda function, which reads the image, resizes it, and writes the generated thumbnail back to a separate S3 prefix — then validate both the working path and a deliberate failure path:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Normal operation: S3 upload → Lambda → resize → thumbnail&lt;/p&gt;

&lt;p&gt;Failure handling: S3 upload → Lambda → &lt;code&gt;AccessDenied&lt;/code&gt; → CloudWatch diagnosis&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    S3 Bucket
        s3-image-resize-lambda-lab
                 /          \
                /            \
        uploads/            thumbnails/
            │                    ▲
            │ ObjectCreated      │ PutObject
            ▼                    │
        ┌────────────────────────────┐
        │          Lambda            │
        │    s3-image-processor      │
        │                            │
        │  1. Read image from S3     │
        │  2. Resize with Pillow     │
        │  3. Write thumbnail to S3  │
        └────────────────────────────┘
                     │
                     ▼
                CloudWatch Logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The S3 trigger was scoped to the &lt;code&gt;uploads/&lt;/code&gt; prefix only, and Lambda writes its output to &lt;code&gt;thumbnails/&lt;/code&gt; — keeping the generated thumbnail from ever re-triggering the function.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;S3 bucket:&lt;/strong&gt; &lt;code&gt;s3-image-resize-lambda-lab&lt;/code&gt;, with prefixes &lt;code&gt;uploads/&lt;/code&gt; (input) and &lt;code&gt;thumbnails/&lt;/code&gt; (output)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda:&lt;/strong&gt; &lt;code&gt;s3-image-processor&lt;/code&gt; — Python 3.12, x86_64&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda Layer:&lt;/strong&gt; &lt;code&gt;pillow-image-processing&lt;/code&gt; — Pillow wasn't in the base Python runtime, so it was added as a custom layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger:&lt;/strong&gt; S3 &lt;code&gt;ObjectCreated&lt;/code&gt; events on &lt;code&gt;uploads/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution role:&lt;/strong&gt; &lt;code&gt;s3:GetObject&lt;/code&gt; on &lt;code&gt;uploads/*&lt;/code&gt;, &lt;code&gt;s3:PutObject&lt;/code&gt; on &lt;code&gt;thumbnails/*&lt;/code&gt; — deliberately scoped rather than broad bucket access&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Build the Pipeline
&lt;/h2&gt;

&lt;p&gt;The S3 trigger was configured for all object-create events, scoped to the &lt;code&gt;uploads/&lt;/code&gt; prefix with no suffix filter (event type &lt;code&gt;s3:ObjectCreated:*&lt;/code&gt;), which also auto-granted S3 the permission to invoke the Lambda. The execution role was given exactly two permissions — &lt;code&gt;s3:GetObject&lt;/code&gt; on &lt;code&gt;arn:aws:s3:::s3-image-resize-lambda-lab/uploads/*&lt;/code&gt; and &lt;code&gt;s3:PutObject&lt;/code&gt; on &lt;code&gt;arn:aws:s3:::s3-image-resize-lambda-lab/thumbnails/*&lt;/code&gt; — intentionally avoiding a broader S3 grant.&lt;/p&gt;

&lt;p&gt;The handler logic follows a straightforward sequence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read S3 event → extract bucket + object key → get the original image from S3 → open with Pillow → resize while preserving aspect ratio → swap &lt;code&gt;uploads/&lt;/code&gt; for &lt;code&gt;thumbnails/&lt;/code&gt; in the key → put the resized image back into S3&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The thumbnail target is derived directly from the source key — &lt;code&gt;uploads/photo.png&lt;/code&gt; becomes &lt;code&gt;thumbnails/photo.png&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Verify the Normal Workflow
&lt;/h2&gt;

&lt;p&gt;An image was uploaded to &lt;code&gt;uploads/&lt;/code&gt;. Lambda was invoked automatically, and the resized thumbnail appeared under &lt;code&gt;thumbnails/&lt;/code&gt; moments later. CloudWatch confirmed the full round trip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New file uploaded: s3://s3-image-resize-lambda-lab/uploads/56291ef0-ccd4-4201-8334-1915f49fd835.png

Thumbnail created: s3://s3-image-resize-lambda-lab/thumbnails/56291ef0-ccd4-4201-8334-1915f49fd835.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every stage of the path checked out: S3 upload, the S3 → Lambda trigger, Lambda execution, the image read, the resize, and the thumbnail write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Break It on Purpose, Then Diagnose
&lt;/h2&gt;

&lt;p&gt;To simulate a real permissions failure, the &lt;code&gt;s3:GetObject&lt;/code&gt; statement for &lt;code&gt;uploads/*&lt;/code&gt; was removed from the Lambda execution role (leaving &lt;code&gt;s3:PutObject&lt;/code&gt; on &lt;code&gt;thumbnails/*&lt;/code&gt; untouched), and a new image was uploaded to &lt;code&gt;uploads/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The S3 event still fired and Lambda still ran — it just failed partway through. CloudWatch isolated exactly where:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ERROR] AccessDenied
An error occurred (AccessDenied) when calling the GetObject operation:
User: arn:aws:sts::...:assumed-role/s3-image-processor-role-.../s3-image-processor
is not authorized to perform: s3:GetObject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pinned the failure precisely on the execution role's permissions, not the S3 trigger, the Lambda Layer, or the runtime — the trigger fired correctly and the function only failed once its code tried to read the source image. The original &lt;code&gt;s3:GetObject&lt;/code&gt; grant was then restored and the successful flow re-verified before cleaning up the lab environment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The event path worked; the execution permission did not.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Troubleshooting Pattern
&lt;/h2&gt;

&lt;p&gt;The sequence that actually isolated the fault, worth reusing on any event-driven pipeline:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did the upload happen? → did S3 invoke Lambda? → did Lambda start? → where did Lambda fail? → check the CloudWatch error → check the execution-role permission → restore only the required permission&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is more useful than simply checking whether the final thumbnail exists, because it separates an &lt;strong&gt;event/configuration problem&lt;/strong&gt; from an &lt;strong&gt;execution/permission problem&lt;/strong&gt; instead of treating "nothing happened" as one undifferentiated failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separate input and output prefixes&lt;/strong&gt; (&lt;code&gt;uploads/&lt;/code&gt; vs &lt;code&gt;thumbnails/&lt;/code&gt;) stop the pipeline's own output from re-triggering itself — for larger systems, separate input/output buckets give an even stronger isolation boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope Lambda permissions to the exact paths needed&lt;/strong&gt; (&lt;code&gt;GetObject&lt;/code&gt; on &lt;code&gt;uploads/*&lt;/code&gt;, &lt;code&gt;PutObject&lt;/code&gt; on &lt;code&gt;thumbnails/*&lt;/code&gt;) rather than granting broad, bucket-wide S3 access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch is the first place to look&lt;/strong&gt; when an event-driven pipeline fails — a successful S3 upload says nothing about whether downstream processing succeeded, since the failure can occur well after the event reaches Lambda.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event-driven systems fail at multiple independent boundaries&lt;/strong&gt;, and a production troubleshooting process should identify &lt;em&gt;which&lt;/em&gt; boundary failed before changing anything:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;S3 upload → S3 event → Lambda invocation → IAM authorization → S3 read → image processing → S3 write&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Normal path:&lt;/strong&gt; image upload → S3 &lt;code&gt;/uploads/&lt;/code&gt; → &lt;code&gt;ObjectCreated&lt;/code&gt; event → Lambda → Pillow resize → S3 &lt;code&gt;/thumbnails/&lt;/code&gt; → thumbnail ✅&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure path:&lt;/strong&gt; image upload → S3 &lt;code&gt;/uploads/&lt;/code&gt; → &lt;code&gt;ObjectCreated&lt;/code&gt; event → Lambda → &lt;code&gt;GetObject&lt;/code&gt; → &lt;code&gt;AccessDenied&lt;/code&gt; ❌ → CloudWatch identifies the missing permission&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Outcome:&lt;/strong&gt; successfully built and verified an S3-triggered Lambda image-processing pipeline end to end, then deliberately broke and correctly diagnosed an IAM permission failure using CloudWatch logs.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>So… If AI Can Write Code, What Are Software Engineers Getting Paid For? 👀</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Sun, 30 Aug 2026 16:17:23 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/so-if-ai-can-write-code-what-are-software-engineers-getting-paid-for-193m</link>
      <guid>https://dev.to/tejas_shinkar/so-if-ai-can-write-code-what-are-software-engineers-getting-paid-for-193m</guid>
      <description>&lt;p&gt;A few years ago, being the person who could turn a requirement into working code was a pretty valuable skill.&lt;/p&gt;

&lt;p&gt;Now you can give an AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Build me a REST API with authentication, tests and documentation.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;…and get something surprisingly usable in minutes.&lt;/p&gt;

&lt;p&gt;So naturally, the uncomfortable question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If AI can write the code, what exactly are software engineers getting paid for?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's where it gets interesting.&lt;/p&gt;

&lt;p&gt;Imagine an AI-generated feature gets merged.&lt;/p&gt;

&lt;p&gt;The tests pass.&lt;/p&gt;

&lt;p&gt;The deployment succeeds.&lt;/p&gt;

&lt;p&gt;And two hours later, production starts throwing errors.&lt;/p&gt;

&lt;p&gt;Now someone has to figure out:&lt;/p&gt;

&lt;p&gt;Why only certain users are affected?&lt;/p&gt;

&lt;p&gt;Why did latency suddenly spike?&lt;/p&gt;

&lt;p&gt;Is the application broken, or is the database struggling?&lt;/p&gt;

&lt;p&gt;Did the new code introduce a race condition?&lt;/p&gt;

&lt;p&gt;Is the fix actually safe to deploy?&lt;/p&gt;

&lt;p&gt;AI can help investigate all of this.&lt;/p&gt;

&lt;p&gt;But somebody still has to &lt;strong&gt;understand the system well enough to know whether the answer makes sense.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And developers themselves seem to know this.&lt;/p&gt;

&lt;p&gt;Stack Overflow's 2025 survey found that &lt;strong&gt;84% of developers use or plan to use AI tools&lt;/strong&gt;, yet 46% said they don't trust the accuracy of AI output. The biggest frustration? AI solutions that are &lt;em&gt;almost&lt;/em&gt; right — with 45% saying debugging AI-generated code can take more time.&lt;/p&gt;

&lt;p&gt;There's another interesting signal: developers are much less willing to hand AI high-responsibility work like deployment and monitoring.&lt;/p&gt;

&lt;p&gt;Maybe that's the shift.&lt;/p&gt;

&lt;p&gt;The valuable engineer isn't necessarily the person who can type the most code anymore.&lt;/p&gt;

&lt;p&gt;It's the person who can:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;understand the problem → design the system → use AI effectively → question its output → debug what breaks → make the right trade-off.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI might be making code cheaper.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;good engineering judgment?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's still expensive.&lt;/p&gt;

&lt;p&gt;And maybe that's what we're actually getting paid for. 👀&lt;/p&gt;

</description>
      <category>software</category>
      <category>tech</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Thought Knowing AWS Was The Goal. I Was Wrong.</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Sat, 29 Aug 2026 16:27:31 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/i-thought-knowing-aws-was-the-goal-i-was-wrong-4j6m</link>
      <guid>https://dev.to/tejas_shinkar/i-thought-knowing-aws-was-the-goal-i-was-wrong-4j6m</guid>
      <description>&lt;p&gt;When I started learning AWS, I had this very simple idea in my head: learn the services, finish the modules, build a few projects, and eventually I’d be “good at cloud.” So I started collecting names — EC2, S3, IAM, VPC, EFS, CloudFront, Route 53, and a growing graveyard of AWS console tabs. 😂 At first, it felt productive because every completed service felt like another box checked. But somewhere during the networking labs, that started falling apart. I realized I could tell you what a NAT Gateway does, but still stop and ask myself, &lt;strong&gt;“Wait… why are we creating another subnet for this?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question actually changed how I started learning. Instead of asking, &lt;em&gt;“What does this AWS service do?”&lt;/em&gt;, I started asking, &lt;em&gt;“What problem are we solving that requires this thing?”&lt;/em&gt; Suddenly VPC started making more sense. A route table wasn't just another AWS resource; it was an answer to &lt;em&gt;“Where should this traffic go?”&lt;/em&gt; A NAT Gateway wasn't something you create because a tutorial told you to; it existed because a private machine needed controlled outbound internet access without becoming publicly reachable. Even NACLs became interesting when I deliberately broke one and watched an otherwise healthy request just… hang. 💀&lt;/p&gt;

&lt;p&gt;And I started noticing the same thing outside networking. IAM isn't really about memorizing policies — it's about deciding &lt;strong&gt;who should be allowed to do what, and from where&lt;/strong&gt;. CloudFront isn't just “AWS CDN” — it's figuring out which responses are safe to reuse, which ones are personalized, and what happens when your cache thinks two requests are the same when they aren't. Docker isn't impressive because you know &lt;code&gt;docker build&lt;/code&gt;; it's useful when you understand what should actually live inside a container, what should stay outside it, and how that behaves when the container gets replaced.&lt;/p&gt;

&lt;p&gt;I'm still nowhere near the point where I'd call myself a production DevOps engineer. Most of what I'm learning right now comes from courses, labs, breaking things, fixing them, and trying to reason about what these systems would look like in a real environment. And honestly, that's probably been more valuable than simply finishing lectures. Because the more I learn, the more I realize that companies probably don't need someone who can recite 100 AWS services from memory. They need someone who can look at a broken system and ask &lt;strong&gt;the right next question&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Maybe that's the difference between learning tools and learning engineering. One teaches you &lt;strong&gt;what exists&lt;/strong&gt;. The other teaches you &lt;strong&gt;why it exists, when to use it, and what happens when it breaks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'm curious about people who already work in Cloud/DevOps/SRE:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What was the first thing you learned in production that made you realize, “Yeah… courses really didn't prepare me for this”?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>learning</category>
      <category>career</category>
    </item>
    <item>
      <title>I Thought Caching Was Just About Speed. Then I Found the Security Trap. 💀</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Fri, 28 Aug 2026 05:20:01 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/i-thought-caching-was-just-about-speed-then-i-found-the-security-trap-gdi</link>
      <guid>https://dev.to/tejas_shinkar/i-thought-caching-was-just-about-speed-then-i-found-the-security-trap-gdi</guid>
      <description>&lt;p&gt;I used to think caching was a pretty simple performance trick: store something closer to the user, serve it faster, reduce the load on the backend, and everyone wins. Then I started digging into CloudFront cache keys and realized caching can become a &lt;strong&gt;data-isolation problem&lt;/strong&gt; if one small decision is wrong. Imagine an application with a &lt;code&gt;GET /profile&lt;/code&gt; endpoint where User A sends &lt;code&gt;Cookie: session=userA&lt;/code&gt; and gets a response like “Hello Alice, your orders: 12, your balance: ₹42,000.” CloudFront can cache that response so future requests don't always have to reach the origin. Sounds great, right? Until another user requests the same URL and the cache doesn't distinguish the thing that actually makes the response personal.&lt;/p&gt;

&lt;p&gt;Now User B sends &lt;code&gt;GET /profile&lt;/code&gt; with &lt;code&gt;Cookie: session=userB&lt;/code&gt;, and the origin would normally return completely different information such as “Hello Bob, your orders: 4, your balance: ₹8,000.” But if the cache key only considers &lt;code&gt;/profile&lt;/code&gt; and ignores the user-specific part of the request, CloudFront can see the request as equivalent to the object it already has. In that situation, User B could receive the response cached for User A. Nothing has crashed, the EC2 instance can be healthy, the database can be healthy, and CloudFront can even be behaving exactly according to its configuration. The actual problem is the &lt;strong&gt;caching strategy&lt;/strong&gt;. That's when caching stops being only a performance optimization and becomes a correctness and security concern.&lt;/p&gt;

&lt;p&gt;Now compare that with a public product catalogue. A request like &lt;code&gt;GET /products?id=101&lt;/code&gt; might return the same product information for every user: &lt;code&gt;iPhone 17&lt;/code&gt;, &lt;code&gt;₹79,999&lt;/code&gt;, &lt;code&gt;In Stock&lt;/code&gt;. That's a fantastic candidate for caching because thousands of users can safely reuse the same response. We want something like &lt;strong&gt;one cache object serving many requests&lt;/strong&gt;, not a separate cache entry for every visitor. But this is where another subtle problem appears: if every request contains an analytics cookie such as &lt;code&gt;analytics_id=83A91&lt;/code&gt;, &lt;code&gt;analytics_id=72B42&lt;/code&gt;, or &lt;code&gt;analytics_id=91K17&lt;/code&gt;, and those values don't change the product response, putting them into the cache key can create thousands or even millions of unnecessary cache variants. The application may still be correct, but cache reuse gets worse, cache efficiency drops, and more requests may end up going back to the origin.&lt;/p&gt;

&lt;p&gt;That leads to one of the most useful CloudFront distinctions I've learned: &lt;strong&gt;Cache Policy and Origin Request Policy are not answering the same question.&lt;/strong&gt; Cache Policy is essentially asking, “What makes this request a different cached object?” while Origin Request Policy is asking, “What information does the origin need to receive?” For example, an analytics cookie might need to reach the origin for logging or processing, while still not needing to create a unique cache entry for every user. The important part is to understand which request values actually change the response and which ones are simply metadata. A good cache design therefore doesn't blindly include every cookie, header, or query string just because it is available; it deliberately chooses only the dimensions that matter.&lt;/p&gt;

&lt;p&gt;The production lesson for me is that &lt;strong&gt;a high cache-hit ratio is not automatically a good outcome&lt;/strong&gt;. A cache that is extremely fast but returns the wrong user's data is a disaster, not an optimization. The real question isn't “How much can I cache?” but “What can I safely reuse, what makes the response different, and how do I make CloudFront understand that difference?” This also changes how I think about CDN architecture: cache design affects performance, origin load, freshness, correctness, and potentially security at the same time. Static assets such as versioned JavaScript, CSS, images, and public product data are usually much easier to cache aggressively, while personalized responses need a much more careful strategy. &lt;strong&gt;Caching is not just about speed — it's about knowing exactly when two requests are allowed to share the same answer.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>cloudsecurity</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
