<?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: AWS Community Builders </title>
    <description>The latest articles on DEV Community by AWS Community Builders  (@aws-builders).</description>
    <link>https://dev.to/aws-builders</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%2Forganization%2Fprofile_image%2F2794%2F88da75b6-aadd-4ea1-8083-ae2dfca8be94.png</url>
      <title>DEV Community: AWS Community Builders </title>
      <link>https://dev.to/aws-builders</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aws-builders"/>
    <language>en</language>
    <item>
      <title>What is AWS Blocks? How it differs from Amplify and App Studio, and what each one is aiming for</title>
      <dc:creator>Kento IKEDA</dc:creator>
      <pubDate>Fri, 19 Jun 2026 22:12:14 +0000</pubDate>
      <link>https://dev.to/aws-builders/what-is-aws-blocks-how-it-differs-from-amplify-and-app-studio-and-what-each-one-is-aiming-for-2kn0</link>
      <guid>https://dev.to/aws-builders/what-is-aws-blocks-how-it-differs-from-amplify-and-app-studio-and-what-each-one-is-aiming-for-2kn0</guid>
      <description>&lt;p&gt;On June 16, 2026, AWS announced &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/06/aws-blocks-preview/" rel="noopener noreferrer"&gt;AWS Blocks&lt;/a&gt; as a public preview. It is an open-source framework where the TypeScript you write for your backend becomes the AWS infrastructure that runs it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-devtools-labs/aws-blocks" rel="noopener noreferrer"&gt;https://github.com/aws-devtools-labs/aws-blocks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first thing many AWS users will think is "another full-stack tool." If you want a tool for frontend developers to build full-stack apps in TypeScript, Amplify Gen2 already exists. For people who don't write code, there is App Studio. I compared those three earlier in &lt;a href="https://zenn.dev/ikenyal/articles/c2c3ccf9fdd0cf" rel="noopener noreferrer"&gt;a write-up on App Studio, Amplify Gen1, and Amplify Gen2&lt;/a&gt;. Now Blocks joins them.&lt;/p&gt;

&lt;p&gt;This article organizes what AWS Blocks is from the official docs and the open-source code, lines it up against Amplify and App Studio, and finally sketches a map of what each one is aiming for. Rather than stopping at a feature-diff table, I want to get at why AWS is offering multiple entry points into full-stack development.&lt;/p&gt;

&lt;p&gt;A note: this is based on the official docs right after the preview announcement and on reading the open-source code. It is not based on long-term production use of Blocks. Read it with the understanding that implementation details may change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is AWS Blocks
&lt;/h2&gt;

&lt;p&gt;Here is how the official docs define it. &lt;a href="https://docs.aws.amazon.com/blocks/latest/devguide/what-is-blocks.html" rel="noopener noreferrer"&gt;AWS Blocks&lt;/a&gt; is a backend toolkit for full-stack applications, where each Block is a self-contained backend capability that bundles the application code, a local development setup, and the infrastructure to run it. Pick the Blocks you need and compose them, and the infrastructure following AWS best practices is defined automatically.&lt;/p&gt;

&lt;p&gt;Type safety doesn't stop inside the backend. Types flow from the backend all the way to the client, reaching web frameworks (Next.js, Nuxt, Astro, React, Vue, Svelte, Angular) and native targets (Swift, Kotlin, Dart/Flutter). From a single backend, you can generate typed client code for both web and mobile.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aws-devtools-labs/aws-blocks" rel="noopener noreferrer"&gt;https://github.com/aws-devtools-labs/aws-blocks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That said, at the time of the preview announcement the frontends officially listed are SPAs (Vite + React) and SSR frameworks (Next.js, Nuxt, Astro), with support expected to widen over time. Blocks itself adds no extra charge; you pay only for the AWS services you use, and you can deploy to all commercial AWS Regions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/06/aws-blocks-preview/" rel="noopener noreferrer"&gt;https://aws.amazon.com/about-aws/whats-new/2026/06/aws-blocks-preview/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, let's walk through the concepts you can't skip to understand Blocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Block = an npm package bundling infrastructure, runtime, and local implementation
&lt;/h3&gt;

&lt;p&gt;One Block is one npm package, holding the cloud resources, runtime code, and local implementation for a single capability. Instantiate one &lt;code&gt;KVStore&lt;/code&gt;, for example, and you get all at once: a DynamoDB table auto-provisioned at deploy time, runtime code that runs on Lambda, and an in-memory implementation for local development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/blocks/latest/devguide/concepts.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/blocks/latest/devguide/concepts.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The available Blocks cover most backend needs: data (&lt;code&gt;KVStore&lt;/code&gt;, &lt;code&gt;DistributedTable&lt;/code&gt;, &lt;code&gt;Database&lt;/code&gt;, &lt;code&gt;DistributedDatabase&lt;/code&gt;, &lt;code&gt;FileBucket&lt;/code&gt;), auth (&lt;code&gt;AuthBasic&lt;/code&gt;, &lt;code&gt;AuthCognito&lt;/code&gt;, &lt;code&gt;AuthOIDC&lt;/code&gt;), async work (&lt;code&gt;AsyncJob&lt;/code&gt;, &lt;code&gt;CronJob&lt;/code&gt;), AI (&lt;code&gt;Agent&lt;/code&gt;, &lt;code&gt;KnowledgeBase&lt;/code&gt;), communication (&lt;code&gt;Realtime&lt;/code&gt;, &lt;code&gt;EmailClient&lt;/code&gt;), configuration (&lt;code&gt;AppSetting&lt;/code&gt;), observability (&lt;code&gt;Logger&lt;/code&gt;, &lt;code&gt;Metrics&lt;/code&gt;, &lt;code&gt;Tracer&lt;/code&gt;, &lt;code&gt;Dashboard&lt;/code&gt;), and hosting (&lt;code&gt;Hosting&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/blocks/latest/devguide/what-is-blocks.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/blocks/latest/devguide/what-is-blocks.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What the official overview page doesn't give you, though, is a list of which AWS service each Block actually becomes inside. I was curious, so I read the open-source code (&lt;a href="https://github.com/aws-devtools-labs/aws-blocks" rel="noopener noreferrer"&gt;aws-devtools-labs/aws-blocks&lt;/a&gt;) to confirm the real services behind the main Blocks.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Block&lt;/th&gt;
&lt;th&gt;AWS service inside&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;KVStore&lt;/code&gt; / &lt;code&gt;DistributedTable&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;DynamoDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Database&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Aurora Serverless v2 (PostgreSQL 16.4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DistributedDatabase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Aurora DSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FileBucket&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AuthBasic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no dedicated infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;AuthCognito&lt;/code&gt; / &lt;code&gt;AuthOIDC&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Cognito&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AsyncJob&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SQS + Lambda&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CronJob&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EventBridge Scheduler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Strands Agents SDK + Bedrock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KnowledgeBase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bedrock + S3 Vectors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Realtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;API Gateway v2 (WebSocket)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EmailClient&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AppSetting&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SSM Parameter Store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Logger&lt;/code&gt; / &lt;code&gt;Metrics&lt;/code&gt; / &lt;code&gt;Tracer&lt;/code&gt; / &lt;code&gt;Dashboard&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CloudWatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Hosting&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CloudFront + S3 + WAF + Route 53 + ACM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few findings the overview alone doesn't reveal:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DistributedDatabase&lt;/code&gt; is Aurora DSQL, so DSQL's own constraints surface directly in the development experience. You can't mix DDL and DML in the same transaction, and only one DDL statement is allowed per transaction. Blocks rejects these on the client side with validation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;KnowledgeBase&lt;/code&gt; uses S3 Vectors, which arrived in 2025, as the vector store for RAG.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Agent&lt;/code&gt; sits on top of Strands Agents SDK, AWS's open-source agent framework.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Hosting&lt;/code&gt; is less "CloudFront + S3" and more a heavier part that also bundles WAF, Route 53, and ACM.&lt;/p&gt;

&lt;h3&gt;
  
  
  IFC layer = the entry point where code becomes infrastructure
&lt;/h3&gt;

&lt;p&gt;The backend entry point of Blocks lives in a single file, &lt;code&gt;aws-blocks/index.ts&lt;/code&gt;. Instantiate Blocks and define your API there, and the infrastructure is derived directly from that code. No separate file for infrastructure definitions.&lt;/p&gt;

&lt;p&gt;This idea of deriving infrastructure from code is called Infrastructure from Code (IFC), and in the source this backend part was named the IFC subpackage.&lt;/p&gt;

&lt;p&gt;In other words, the code that describes infrastructure and the code that describes the app aren't separated. Infrastructure grows out of the app's code. This is the heart of Blocks' philosophy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditional exports = the same import switches implementation by context
&lt;/h3&gt;

&lt;p&gt;The reason a single file can play three roles at once, namely local development, deploy, and production runtime, is Node.js conditional exports, which route the same import statement to a different implementation per context.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Resolved implementation&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Local development&lt;/td&gt;
&lt;td&gt;in-memory / filesystem&lt;/td&gt;
&lt;td&gt;the app runs on localhost alone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDK synthesis&lt;/td&gt;
&lt;td&gt;CDK constructs&lt;/td&gt;
&lt;td&gt;infrastructure is defined for CloudFormation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda runtime&lt;/td&gt;
&lt;td&gt;AWS SDK&lt;/td&gt;
&lt;td&gt;real services are called in production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript / IDE&lt;/td&gt;
&lt;td&gt;type definitions&lt;/td&gt;
&lt;td&gt;completion and type checking work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The same line &lt;code&gt;new KVStore(scope, 'todos')&lt;/code&gt; becomes a local store in development, a DynamoDB table at deploy time, and an SDK call in production. You never change the code. Without writing any configuration by hand, module resolution picks the implementation per context. Reading the source package.json, they are switched by conditions like &lt;code&gt;cdk&lt;/code&gt;, &lt;code&gt;aws-runtime&lt;/code&gt;, &lt;code&gt;types&lt;/code&gt;, and &lt;code&gt;default&lt;/code&gt; (the local mock).&lt;/p&gt;

&lt;h3&gt;
  
  
  ApiNamespace = type-safe RPC with no code generation
&lt;/h3&gt;

&lt;p&gt;The part that calls the backend from the frontend is handled by ApiNamespace. The frontend imports and calls methods defined in the backend directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Frontend: import the backend API directly&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../aws-blocks/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// TypeScript knows the return type too&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No code generation step, no API client initialization, no URL configuration. Change a backend method signature and the frontend gets a compile error instantly. Locally it goes through an HTTP server; in production it reaches Lambda through API Gateway.&lt;/p&gt;

&lt;p&gt;The transport underneath is JSON-RPC 2.0. When you call a typed method, it is converted into a JSON-RPC request internally and delivered to the backend. The developer never assembles a payload by hand; the transport stays hidden behind the types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local-first = everything runs without an AWS account
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;npm run dev&lt;/code&gt; and the whole app starts locally. Blocks resolve to local implementations (an in-memory store, local auth, an embedded DB), running at &lt;code&gt;http://localhost:3000&lt;/code&gt; with hot reload. No AWS account, no internet connection, no cloud billing. Local data is persisted under &lt;code&gt;.bb-data/&lt;/code&gt; at the project root.&lt;/p&gt;

&lt;p&gt;The "embedded DB" here turns out to be PGlite (a WebAssembly build of PostgreSQL). Not only &lt;code&gt;Database&lt;/code&gt; but also &lt;code&gt;DistributedDatabase&lt;/code&gt;, which uses Aurora DSQL, falls back to PGlite locally. It works because DSQL is PostgreSQL-compatible, so a near-real Postgres comes up locally without an AWS account.&lt;/p&gt;

&lt;p&gt;When you want to check behavior against real cloud services, &lt;code&gt;npm run sandbox&lt;/code&gt; deploys to a fast, disposable environment using hot-swap to Lambda. The mocks are swapped for real AWS services (DynamoDB, Aurora, S3, Lambda, and so on), and once you are done you can tear it all down with &lt;code&gt;npm run sandbox:destroy&lt;/code&gt;. To ship to production, you run &lt;code&gt;npm run deploy&lt;/code&gt;. The same backend code runs in all three.&lt;/p&gt;

&lt;h3&gt;
  
  
  Direct CDK access = if Blocks isn't enough, write it yourself
&lt;/h3&gt;

&lt;p&gt;Every Blocks app is a CDK app. You can use arbitrary CDK constructs alongside Blocks, and you can embed Blocks into an existing CDK stack.&lt;/p&gt;

&lt;p&gt;When you want to add a resource Blocks doesn't provide (SNS, Step Functions, and the like) or set up a custom domain, you write &lt;code&gt;aws-blocks/index.cdk.ts&lt;/code&gt; and access CDK constructs directly. Normally the infrastructure is derived from the backend definition (&lt;code&gt;aws-blocks/index.ts&lt;/code&gt;), so you don't need to touch CDK directly. When you do need it, you just write CDK and can build as far as you like, beyond the edges of the framework. By design, it is structurally hard to get trapped and stuck inside the abstraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  AGENTS.md bundled = agents write correct code from the start
&lt;/h3&gt;

&lt;p&gt;Blocks ships an agent-facing guide inside the npm package. Without adding a plugin, an AI coding agent is said to be steered toward writing correct code from the start.&lt;/p&gt;

&lt;p&gt;Its actual form turned out to be an &lt;code&gt;AGENTS.md&lt;/code&gt; placed in the project. Reading it, rather than carrying the full guide inline, it is a pointer to references. The detailed explanation, how to choose a Block, and how to use each Block live under &lt;code&gt;node_modules/@aws-blocks/blocks/&lt;/code&gt; in README.md, docs/index.md, and docs/.md, and it tells the agent to read those. Alongside, the Rules forbid anti-patterns: always use a Block for persistence (no local files, in-memory arrays, or local DBs), and don't assemble JSON-RPC payloads by hand.&lt;/p&gt;

&lt;p&gt;Rather than pouring everything into the agent's context, you have it read only the docs it needs when it needs them. This pointer approach is sound as a design for agent-facing docs, and it is worth noting that an official AWS framework ships with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it differs from Amplify and App Studio
&lt;/h2&gt;

&lt;p&gt;Let me line up what we have covered against Amplify Gen2 and App Studio.&lt;/p&gt;

&lt;p&gt;Amplify Gen2 is a development platform where you describe data models, business logic, and authn/authz in TypeScript and the appropriate cloud resources are auto-provisioned. It is built on CDK internally and offers categories like Data, Auth, Storage, and Functions out of the box. Per-developer cloud sandboxes, shared environments mapped one-to-one to Git branches, and the Amplify Console that bundles hosting and CI/CD are its hallmarks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.amplify.aws/react/how-amplify-works/concepts/" rel="noopener noreferrer"&gt;https://docs.amplify.aws/react/how-amplify-works/concepts/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;App Studio sits on the low-code side, letting non-developers and beginners with little coding experience design and build apps. Where Amplify Gen2 and Blocks target developers, App Studio aims at a fundamentally different audience. I have organized the product details in the write-up I mentioned at the top.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zenn.dev/ikenyal/articles/c2c3ccf9fdd0cf" rel="noopener noreferrer"&gt;https://zenn.dev/ikenyal/articles/c2c3ccf9fdd0cf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lined up by aspect:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;App Studio&lt;/th&gt;
&lt;th&gt;Amplify Gen2&lt;/th&gt;
&lt;th&gt;AWS Blocks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primary audience&lt;/td&gt;
&lt;td&gt;non-developers&lt;/td&gt;
&lt;td&gt;frontend developers&lt;/td&gt;
&lt;td&gt;developers who also write backends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How infrastructure is handled&lt;/td&gt;
&lt;td&gt;fully hidden&lt;/td&gt;
&lt;td&gt;abstracted by category&lt;/td&gt;
&lt;td&gt;derived from code (IFC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local development&lt;/td&gt;
&lt;td&gt;cloud-first&lt;/td&gt;
&lt;td&gt;per-developer cloud environment&lt;/td&gt;
&lt;td&gt;fully local, no AWS account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting / CI-CD&lt;/td&gt;
&lt;td&gt;built in&lt;/td&gt;
&lt;td&gt;bundled in the Amplify Console&lt;/td&gt;
&lt;td&gt;Hosting is one Block, CI/CD is your own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How types flow&lt;/td&gt;
&lt;td&gt;types aren't a concern&lt;/td&gt;
&lt;td&gt;schema-driven Data types&lt;/td&gt;
&lt;td&gt;type-safe RPC, no generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distance from CDK&lt;/td&gt;
&lt;td&gt;far&lt;/td&gt;
&lt;td&gt;used for extensions when needed&lt;/td&gt;
&lt;td&gt;a CDK app from the start, write CDK directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI coding agents&lt;/td&gt;
&lt;td&gt;out of scope&lt;/td&gt;
&lt;td&gt;little explicit support&lt;/td&gt;
&lt;td&gt;bundled AGENTS.md as a premise&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A shared obsession with iteration speed shows up too. Amplify Gen2 advertises up to 8x faster iteration than Gen1, speeding up cloud-side reflection via hot-swap to per-developer sandboxes. Blocks, on the other hand, completes locally, so in many cases there is no round trip to the cloud at all. The direction of "faster" differs, but the aim of shortening the write-then-check loop is shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each one is aiming for
&lt;/h2&gt;

&lt;p&gt;Rather than feature diffs, let me put into words what the three are trying to achieve.&lt;/p&gt;

&lt;p&gt;What App Studio aims for is delivering apps to people who don't write code. Its abstraction is the highest, and it minimizes the developer's involvement the most.&lt;/p&gt;

&lt;p&gt;What Amplify aims for is freeing frontend developers from infrastructure. Even though Gen2 was rebuilt on CDK, what developers face day to day are categories like Data, Auth, and Storage, and that managed mass covers up the infrastructure underneath. It takes care of hosting and CI/CD together, freeing developers from stitching individual AWS services by hand. The key is to hide.&lt;/p&gt;

&lt;p&gt;What Blocks aims for looks a little different. The entry point of making infrastructure tools unnecessary to learn is similar to Amplify, but its means leans toward making things transparent with code and types rather than hiding them. Infrastructure grows from the app's code, the same code runs both locally and in the cloud, and you can write CDK directly when needed. Rather than ease through concealment, it aims for reassurance through transparency and the absence of a ceiling.&lt;/p&gt;

&lt;p&gt;In short, Amplify reaches the shared goal of making frontend developers' lives easier by keeping infrastructure out of mind, while Blocks does it by letting you stay aware of infrastructure without requiring you to. They take two routes to the same place. The former thickens the wall of abstraction; the latter makes it thin and transparent.&lt;/p&gt;

&lt;p&gt;And Blocks carries one more premise that is distinctive of 2026. It takes for granted that AI agents write code, and the framework itself carries the correct way to write from the start. This is a design that lowers not only the cost of humans learning but the cost of agents making mistakes, and its starting point looks different from Amplify's design philosophy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where is Amplify headed
&lt;/h2&gt;

&lt;p&gt;What follows is interpretation, not fact. I write it on the premise that none of it is certain.&lt;/p&gt;

&lt;p&gt;The official docs explicitly call the relationship between Blocks and Amplify complementary. Amplify provides hosting, CI/CD, and a managed backend experience; Blocks focuses on type-safe infrastructure-from-code and local-first development.&lt;/p&gt;

&lt;p&gt;Still, the overlap is not small. Amplify Gen2 also defines backends code-first in TypeScript, on top of CDK. Blocks' IFC layer and Amplify Gen2's backend definition are both a "write your backend in TypeScript" experience standing on CDK, so they sit close in philosophy.&lt;/p&gt;

&lt;p&gt;The closeness shows on the implementation side too. Blocks' project-creation CLI auto-detects an existing Amplify Gen2 project and integrates with it, and there is even a dedicated &lt;code&gt;amplify&lt;/code&gt; template. Adding Blocks to an Amplify Gen2 backend is an entry path assumed from the start. The complementary relationship is not just a line in the docs; it is implemented as tool behavior.&lt;/p&gt;

&lt;p&gt;One possibility I read from this: Amplify shifts its center of gravity toward the hosting and managed-experience layer, while Blocks takes the composable-backend-parts and local-development layer. In fact, Hosting in Blocks is treated as one part bundling CloudFront, S3, and even WAF, and the integrated hosting and CI/CD experience remains a strength on Amplify's side.&lt;/p&gt;

&lt;p&gt;What matters is that this doesn't necessarily mean Amplify is shrinking. It reads more naturally as AWS deliberately keeping multiple entry points into full-stack development. App Studio for non-developers, Amplify for those who want a managed experience, Blocks for those who want to command infrastructure with code and types. Rather than converging on a single right answer, it looks like a strategy of preparing a different door for each developer's stance.&lt;/p&gt;

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

&lt;p&gt;AWS Blocks turned out to be not a replacement for Amplify but one more entry point into full-stack development. Amplify, which makes things easy by hiding; Blocks, which makes things transparent and hands you the controls. Which you choose is also a statement of how you want to relate to infrastructure.&lt;/p&gt;

&lt;p&gt;AWS is not converging on a single answer. When the doors multiply, the question isn't which tool is better, but which kind of developer you want to be.&lt;/p&gt;

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

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




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




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

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

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

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

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

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

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

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

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


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

&lt;p&gt;&lt;strong&gt;Monthly AWS bill review. Something doesn't add up:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2 Instances:
- file-processor (t3.medium)  → $30.37/month
  Actual compute time: 12 minutes/day
  Utilization: 0.8%

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

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

&lt;/div&gt;


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

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

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

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


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

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

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

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


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

&lt;/div&gt;

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

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

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


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

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

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


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


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

&lt;/div&gt;


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

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


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

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


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

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

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

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

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

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

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

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

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





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

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

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

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Navigate to the project directory:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;terraform-by-sarvar/articles/08-lambda-serverless
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

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

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;lambda/hello.py&lt;/code&gt;&lt;/strong&gt; - Simple API handler:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Simple Hello World Lambda function
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Event received: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Get name from query parameters or use default
&lt;/span&gt;    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;World&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;queryStringParameters&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;queryStringParameters&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;World&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aws_request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;function_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;memory_limit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_limit_in_mb&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;lambda/s3_processor.py&lt;/code&gt;&lt;/strong&gt; - Processes uploaded files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Process files uploaded to S3
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Event received: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Records&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unquote_plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;event_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;eventName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Processing file: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bucket: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Size: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Event: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;event_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;processed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Result: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;File processed successfully&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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




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

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0"&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;archive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/archive"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

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

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"archive_file"&lt;/span&gt; &lt;span class="s2"&gt;"s3_processor_lambda"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"zip"&lt;/span&gt;
  &lt;span class="nx"&gt;source_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/lambda/s3_processor.py"&lt;/span&gt;
  &lt;span class="nx"&gt;output_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/lambda/s3_processor.zip"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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




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



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

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

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

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

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

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

&lt;/div&gt;



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

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




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



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

  &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
      &lt;span class="nx"&gt;PROJECT&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

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

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

  &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
      &lt;span class="nx"&gt;PROJECT&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



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

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




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



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

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

&lt;/div&gt;



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

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




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



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

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

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

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

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

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_lambda_permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_invoke&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical detail:&lt;/strong&gt; The &lt;code&gt;aws_lambda_permission&lt;/code&gt; must come before the bucket notification. The &lt;code&gt;depends_on&lt;/code&gt; ensures this. Without it, S3 can't invoke your Lambda because the permission doesn't exist yet.&lt;/p&gt;

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




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



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

  &lt;span class="nx"&gt;endpoint_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;types&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"REGIONAL"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_resource"&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rest_api_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_rest_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;parent_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_rest_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root_resource_id&lt;/span&gt;
  &lt;span class="nx"&gt;path_part&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_api_gateway_integration"&lt;/span&gt; &lt;span class="s2"&gt;"hello_lambda"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rest_api_id&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_rest_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;resource_id&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;http_method&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_api_gateway_method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello_get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http_method&lt;/span&gt;
  &lt;span class="nx"&gt;integration_http_method&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"POST"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS_PROXY"&lt;/span&gt;
  &lt;span class="nx"&gt;uri&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoke_arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

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

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

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

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

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_permission"&lt;/span&gt; &lt;span class="s2"&gt;"api_gateway"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;statement_id&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AllowAPIGatewayInvoke"&lt;/span&gt;
  &lt;span class="nx"&gt;action&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda:InvokeFunction"&lt;/span&gt;
  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_name&lt;/span&gt;
  &lt;span class="nx"&gt;principal&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apigateway.amazonaws.com"&lt;/span&gt;
  &lt;span class="nx"&gt;source_arn&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${aws_api_gateway_rest_api.main.execution_arn}/*/*"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;API Gateway is verbose in Terraform.&lt;/strong&gt; That's 7 resources for one endpoint. Here's what each does:&lt;/p&gt;

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

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




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

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

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

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

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

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"s3_bucket_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"S3 bucket name for Lambda triggers"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-lambda-demo-2026"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

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

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  🚀 Deploy and Test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Initialize (downloads aws + archive providers)&lt;/span&gt;
terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Preview&lt;/span&gt;
terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deploy (creates ~18 resources)&lt;/span&gt;
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

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




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

&lt;h3&gt;
  
  
  Test 1: API Gateway → Hello Lambda
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get the API URL&lt;/span&gt;
&lt;span class="nv"&gt;API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; api_gateway_url&lt;span class="si"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123-def456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform-lambda-hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory_limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# With query parameter&lt;/span&gt;
curl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$API_URL&lt;/span&gt;&lt;span class="s2"&gt;?name=Terraform"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, Terraform!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xyz789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform-lambda-hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory_limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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



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

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


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


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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected log output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Processing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;test.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Bucket:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;terraform-lambda-demo&lt;/span&gt;&lt;span class="mi"&gt;-2026&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Size:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Event:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ObjectCreated:Put&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Result:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"processed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Test 3: Verify Non-.txt Files Don't Trigger
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Upload a .jpg - should NOT trigger Lambda&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"not a text file"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; test.jpg
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;test.jpg s3://&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; s3_bucket_name&lt;span class="si"&gt;)&lt;/span&gt;/test.jpg

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


&lt;span class="c"&gt;# Check logs - no new entries&lt;/span&gt;
aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /aws/lambda/terraform-lambda-s3-processor &lt;span class="nt"&gt;--since&lt;/span&gt; 1m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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




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

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

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

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

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

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

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

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




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

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

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Runtime.ImportModuleError: Unable to import module 'hello': No module named 'hello'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Handler doesn't match filename. If your file is &lt;code&gt;hello.py&lt;/code&gt;, handler must be &lt;code&gt;hello.lambda_handler&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Verify &lt;code&gt;handler&lt;/code&gt; in Terraform matches &lt;code&gt;filename.function_name&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Issue 2: S3 Trigger Not Firing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptoms:&lt;/strong&gt; Upload file, nothing in logs.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Is the file suffix &lt;code&gt;.txt&lt;/code&gt;? Other files are filtered out.&lt;/li&gt;
&lt;li&gt;Did the permission deploy before the notification? Check &lt;code&gt;depends_on&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check Lambda permission: &lt;code&gt;aws lambda get-policy --function-name terraform-lambda-s3-processor&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Issue 3: API Gateway Returns 500
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Usually a Lambda execution error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debug:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check Lambda logs directly&lt;/span&gt;
aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /aws/lambda/terraform-lambda-hello &lt;span class="nt"&gt;--since&lt;/span&gt; 5m

&lt;span class="c"&gt;# Test Lambda directly (bypass API Gateway)&lt;/span&gt;
aws lambda invoke &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--function-name&lt;/span&gt; terraform-lambda-hello &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'{"queryStringParameters":{"name":"Test"}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  response.json

&lt;span class="nb"&gt;cat &lt;/span&gt;response.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Issue 4: "AccessDeniedException" on S3
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; IAM policy doesn't include the bucket or action needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Verify the custom policy references the correct bucket ARN with &lt;code&gt;/*&lt;/code&gt; suffix for object-level actions.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set &lt;code&gt;source_code_hash&lt;/code&gt;&lt;/strong&gt; - Without it, Terraform won't redeploy when code changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;archive_file&lt;/code&gt; data source&lt;/strong&gt; - Let Terraform handle zipping. Manual zip files drift.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set retention on log groups&lt;/strong&gt; - Default is infinite. At $0.03/GB stored, this adds up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Timeout appropriately&lt;/strong&gt; - API handlers: 10-30s. Processing: 30-300s. Never use the 900s max unless you know why.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory = CPU&lt;/strong&gt; - Lambda allocates CPU proportionally to memory. 128MB gets minimal CPU. 1024MB gets significantly more. If your function is slow, increase memory before optimizing code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment variables for config&lt;/strong&gt; - Never hardcode bucket names, table names, or URLs in your Lambda code. Pass them through environment variables in Terraform.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove S3 objects first (bucket must be empty)&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;rm &lt;/span&gt;s3://&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; s3_bucket_name&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt;

&lt;span class="c"&gt;# Destroy everything&lt;/span&gt;
terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;S3 buckets can't be deleted if they contain objects. Empty it first, then Terraform handles the rest.&lt;/p&gt;




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

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

&lt;ul&gt;
&lt;li&gt;✅ Deploy Lambda functions with Terraform&lt;/li&gt;
&lt;li&gt;✅ Package Python code with &lt;code&gt;archive_file&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✅ Create API Gateway endpoints&lt;/li&gt;
&lt;li&gt;✅ Trigger Lambda from S3 uploads&lt;/li&gt;
&lt;li&gt;✅ Apply least-privilege IAM (Article 9 skills!)&lt;/li&gt;
&lt;li&gt;✅ Manage CloudWatch logs with retention&lt;/li&gt;
&lt;li&gt;✅ Test functions via API and S3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The serverless mindset:&lt;/strong&gt; Stop paying for idle servers. Lambda runs your code only when needed, scales automatically, and costs nearly nothing for typical workloads.&lt;/p&gt;




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

&lt;p&gt;In the next article, we'll add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform modules for reusability&lt;/li&gt;
&lt;li&gt;Package your Lambda + API Gateway as a reusable module&lt;/li&gt;
&lt;li&gt;Share infrastructure patterns across projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Coming Up:&lt;/strong&gt; &lt;a href="https://dev.tolink"&gt;Article 9: Secure Database Deployment: RDS + Secrets Manager with Terraform&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;Thank you for reading. I hope this article provided practical insights and a clearer understanding of the topic.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;❤️ Like if it added value
&lt;/li&gt;
&lt;li&gt;🦄 Unicorn if you’re applying it today
&lt;/li&gt;
&lt;li&gt;💾 Save it for your next optimization session
&lt;/li&gt;
&lt;li&gt;🔄 Share it with your team
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 What’s Next
&lt;/h2&gt;

&lt;p&gt;More deep dives are coming soon on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Operations&lt;/li&gt;
&lt;li&gt;GenAI &amp;amp; Agentic AI&lt;/li&gt;
&lt;li&gt;DevOps Automation&lt;/li&gt;
&lt;li&gt;Data &amp;amp; Platform Engineering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow along for weekly insights and hands-on guides.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 Portfolio &amp;amp; Work
&lt;/h2&gt;

&lt;p&gt;You can explore my full body of work, certifications, architecture projects, and technical articles here:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;Visit My Website&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Services I Offer
&lt;/h2&gt;

&lt;p&gt;If you're looking for hands-on guidance or collaboration, I provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Architecture Consulting (AWS / Azure)&lt;/li&gt;
&lt;li&gt;DevSecOps &amp;amp; Automation Design&lt;/li&gt;
&lt;li&gt;FinOps Optimization Reviews&lt;/li&gt;
&lt;li&gt;Technical Writing (Cloud, DevOps, GenAI)&lt;/li&gt;
&lt;li&gt;Product &amp;amp; Architecture Reviews&lt;/li&gt;
&lt;li&gt;Mentorship &amp;amp; 1:1 Technical Guidance&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🤝 Let’s Connect
&lt;/h2&gt;

&lt;p&gt;I’d love to hear your thoughts. Feel free to drop a comment or connect with me on:&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://www.linkedin.com/in/sarvar04/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For collaborations, consulting, or technical discussions, reach out at:&lt;/p&gt;

&lt;p&gt;📧 &lt;a href="mailto:simplynadaf@gmail.com"&gt;simplynadaf@gmail.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Found this helpful? Share it with your team.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;⭐ Star the repo • 📖 Follow the series • 💬 Ask questions  &lt;/p&gt;

&lt;p&gt;Made by &lt;strong&gt;Sarvar Nadaf&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
🌐 &lt;a href="https://sarvarnadaf.com" rel="noopener noreferrer"&gt;https://sarvarnadaf.com&lt;/a&gt;&lt;/p&gt;




</description>
      <category>aws</category>
      <category>terraform</category>
      <category>devops</category>
      <category>ai</category>
    </item>
    <item>
      <title>Investigate and Clean Up Unused Cloud Resources</title>
      <dc:creator>Noureldin ehab</dc:creator>
      <pubDate>Fri, 19 Jun 2026 12:05:58 +0000</pubDate>
      <link>https://dev.to/aws-builders/investigate-and-clean-up-unused-cloud-resources-36oe</link>
      <guid>https://dev.to/aws-builders/investigate-and-clean-up-unused-cloud-resources-36oe</guid>
      <description>&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;By the end of this tutorial, you'll learn how to use Stakpak to investigate zombie resources in a live AWS production account, identify every detached volume, idle load balancer, orphaned snapshot, and forgotten instance silently accruing charges, apply the right cleanups safely, validate that production stays healthy throughout, and configure &lt;a href="https://stakpak.gitbook.io/docs/how-it-works/autopilot" rel="noopener noreferrer"&gt;Stakpak Autopilot&lt;/a&gt; to help detect similar resource sprawl automatically in the future.&lt;/p&gt;

&lt;p&gt;Note: Stakpak is open source, vendor neutral, and works with any model you choose.&lt;/p&gt;

&lt;h1&gt;
  
  
  Problem
&lt;/h1&gt;

&lt;p&gt;AWS environments naturally accumulate unused resources over time: detached volumes, old snapshots, idle load balancers, unassociated Elastic IPs, and forgotten S3 buckets.&lt;/p&gt;

&lt;p&gt;Finding them is easy. Determining whether they're safe to delete is not.&lt;/p&gt;

&lt;p&gt;A resource may look unused, but it could still support a production workload, backup process, or undocumented dependency. Safely cleaning up cloud waste requires connecting usage, ownership, and activity data across your environment before taking action.&lt;/p&gt;

&lt;h1&gt;
  
  
  Application
&lt;/h1&gt;

&lt;p&gt;Northstar Commerce is a B2B ecommerce platform running on AWS, with workloads spread across EKS, ECS Fargate, Lambda, and&lt;br&gt;
Vercel. The main components are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;storefront: Customer facing Next.js app on Vercel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;api-gateway: Public REST and GraphQL edge on EKS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;orders-service: Order lifecycle, Go on EKS, backed by Aurora PostgreSQL.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;payments-service: Java on ECS Fargate, integrates with Stripe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inventory-worker: Celery workers on EKS draining an SQS queue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;search-indexer: Rust Lambda keeping OpenSearch in sync.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;admin-console: React SPA on S3 behind CloudFront.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shared infrastructure includes an EKS cluster, an Aurora cluster, an ElastiCache Redis, an MSK cluster, an OpenSearch domain, ECR, Route 53, ACM, and Secrets Manager.&lt;/p&gt;

&lt;p&gt;Primary region is us-east-1, with us-west-2 as a disaster recovery region.&lt;/p&gt;

&lt;p&gt;Every workload in the catalog is healthy and serving traffic. None of the recent deploys touched infrastructure. &lt;/p&gt;

&lt;p&gt;The application itself is well understood and accounted for, but the AWS account it runs in has accumulated years of side projects, migrations, and experiments that nobody has audited. Anything we find outside of this catalog is a candidate for cleanup, as long as we can prove it isn't quietly supporting one of these workloads.&lt;/p&gt;

&lt;p&gt;Now that we understand the app and architecture, we can start investigating the account.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step-by-Step Guide
&lt;/h1&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.dev/" rel="noopener noreferrer"&gt;Install Stakpak&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html" rel="noopener noreferrer"&gt;AWS credentials configured locally&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Open Stakpak and ask it to &lt;code&gt;audit our AWS account for unused and zombie resources.&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now lets let it do its magic&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%2F6d7972u7t3c9lo0sdrld.png" class="article-body-image-wrapper"&gt;&lt;img 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%2F6d7972u7t3c9lo0sdrld.png" alt=" " width="620" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stakpak audited the AWS account for unused and zombie resources across compute, network, storage, IAM, data, and operational categories and found a small but real pool of recurring waste with no business value attached to any of it.&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%2Fpmaau29538oc2x8w6kyq.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fpmaau29538oc2x8w6kyq.png" alt=" " width="799" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It identified ~$97/month of avoidable spend spread across 15 zombie resources in us-east-1, none tied to any active application. The signals came from EC2 state checks, EBS volume status, ELB target health, CloudWatch metrics for S3 and Lambda, IAM credential reports, and tag/name pattern analysis (*-OLD, -DEPRECATED, rakesh-test-, marketing-campaign-2022, loadtest-runner-2024-q1).&lt;/p&gt;

&lt;p&gt;Then it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Terminated the stopped loadtest-runner-2024-q1 EC2 instance, abandoned since the Q1 2024 load test campaign&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deleted five unattached EBS volumes totaling 371 GB, including a 200 GB elasticsearch-data-node-3 orphan from the search-v1 deprecation and a 100 GB northstar-mysql-data-OLD volume&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deregistered the northstar-golden-image-v2-DEPRECATED AMI and removed its backing snapshot&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Released four unassociated Elastic IPs, including old-nat-gateway-eip from a decommissioned NAT and jenkins-static-ip from the Jenkins-to-GHA migration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deleted two abandoned ALBs (northstar-internal-tools with an empty target group, and a canary ALB with all targets unhealthy) and the northstar-legacy-clb Classic ELB tied to the deprecated checkout-v1 project&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Removed the unused openclaw-sg security group and its orphan openclaw-key key pair&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Emptied and deleted six zombie S3 buckets including northstar-marketing-campaign-2022, rakesh-test-bucket (employee left), tempdata-export, and northstar-checkout-v1-logs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cleaned up two empty CloudWatch log groups (/aws/lambda/feedbackboard-server, /aws/lambda/feedbackboard-warmer) left behind by deleted Lambda functions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the changes were applied, Stakpak verified that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;All 15 zombie resources are gone&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No surviving production resources (bastion, api-canary, staging-app, prod-invoices bucket) were impacted&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;us-west-2 remained clean (only the default VPC, no workloads)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Projected monthly waste dropped from ~$97/month to $0, a 100% reduction on identified zombies&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now everything is cleaned up 🥳&lt;/p&gt;

&lt;p&gt;Now its asking us if we want to sit up &lt;a href="https://stakpak.gitbook.io/docs/how-it-works/autopilot" rel="noopener noreferrer"&gt;Stakpak Autopilot&lt;/a&gt; to avoid having zombie resources&lt;/p&gt;

&lt;p&gt;Note: Stakpak Autopilot monitors your apps 24/7, detects unexpected changes, fixes what’s safe, and only alerts you when it actually matters.&lt;/p&gt;

&lt;h1&gt;
  
  
  Monitoring
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;First, it asks us about how often we want to run the checks&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Then it asks if we want Stakpak to take action&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Then it asks about where we want to get alerted&lt;/li&gt;
&lt;/ol&gt;

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

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

&lt;ol&gt;
&lt;li&gt;And that's it&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h1&gt;
  
  
  Extra Resources:
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Related Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/tutorial/deploy-coolify-on-aws-and-deploy-your-app" rel="noopener noreferrer"&gt;Deploy Coolify on AWS &amp;amp; Deploy Your App&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/tutorial/load-test-to-optimize-cloud-costs" rel="noopener noreferrer"&gt;Load Test to Optimize Cloud Costs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/tutorial/investigate-why-aws-costs-suddenly-increased" rel="noopener noreferrer"&gt;Investigate Why AWS Costs Suddenly Increased&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and more ...&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/get-started/install-stakpak" rel="noopener noreferrer"&gt;Install Stakpak&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/get-started/configure-stakpak" rel="noopener noreferrer"&gt;Configure Stakpak&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html" rel="noopener noreferrer"&gt;Configuration and credential file settings in the AWS CLI&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/how-it-works/autopilot" rel="noopener noreferrer"&gt;Autopilot&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/how-it-works/handling-secrets" rel="noopener noreferrer"&gt;Handling Secrets&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/how-it-works/warden-guardrails" rel="noopener noreferrer"&gt;Warden Guardrails&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>aws</category>
      <category>finops</category>
      <category>devops</category>
    </item>
    <item>
      <title>De cero a la nube: cómo dockericé mi feed de AWS y lo desplegué en ECS</title>
      <dc:creator>Carolina Herrera</dc:creator>
      <pubDate>Fri, 19 Jun 2026 02:08:33 +0000</pubDate>
      <link>https://dev.to/aws-builders/de-cero-a-la-nube-como-dockerice-mi-feed-de-aws-y-lo-desplegue-en-ecs-24f1</link>
      <guid>https://dev.to/aws-builders/de-cero-a-la-nube-como-dockerice-mi-feed-de-aws-y-lo-desplegue-en-ecs-24f1</guid>
      <description>&lt;p&gt;Hace unas semanas me cansé de entrar manualmente a la página de &lt;em&gt;AWS What's New&lt;/em&gt; para ver qué habían lanzado. Así que hice lo que cualquier persona razonable haría: construí una app, la metí en Docker y la subí a ECS. En este artículo te cuento exactamente cómo, con todos los comandos y sin saltarme pasos.&lt;/p&gt;




&lt;h2&gt;
  
  
  ¿Qué vamos a construir?
&lt;/h2&gt;

&lt;p&gt;Una pequeña app web en Python (Flask) que consume el RSS público de AWS y muestra las últimas novedades clasificadas por categoría (AI/ML, Seguridad, Data, FinOps, CloudOps). Tiene modo oscuro y claro, animaciones de entrada y muestra hace cuánto tiempo se publicó cada novedad.&lt;/p&gt;

&lt;p&gt;El flujo completo es:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Código local  →  Docker image  →  Amazon ECR  →  Amazon ECS (Fargate)  →  URL pública
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Repositorio:&lt;/strong&gt; github.com/carotechie/docker-ecs-feedaws &lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Lo que necesitas antes de empezar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Docker Desktop instalado y corriendo&lt;/li&gt;
&lt;li&gt;AWS CLI configurada (&lt;code&gt;aws configure&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Una cuenta de AWS con permisos para ECR y ECS&lt;/li&gt;
&lt;li&gt;Python 3.12+ (solo para desarrollo local)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. La app: Flask + feedparser
&lt;/h2&gt;

&lt;p&gt;El código principal es minimalista. &lt;code&gt;app.py&lt;/code&gt; hace tres cosas: consume el RSS de AWS, limpia el HTML que viene en los summaries y detecta la categoría de cada novedad según sus palabras clave.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.py (fragmento)
&lt;/span&gt;&lt;span class="n"&gt;AWS_RSS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://aws.amazon.com/about-aws/whats-new/recent/feed/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;feedparser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWS_RSS_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...procesa y clasifica cada entrada
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetched_at&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fetched_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No hay base de datos, no hay caché. Cada vez que alguien entra a la URL, la app hace un request al RSS de AWS en ese instante. Simple y siempre actualizado.&lt;/p&gt;

&lt;p&gt;Las dependencias van en &lt;code&gt;requirements.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;flask&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=3.1.1&lt;/span&gt;
&lt;span class="py"&gt;feedparser&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=6.0.11&lt;/span&gt;
&lt;span class="py"&gt;gunicorn&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=23.0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. El Dockerfile
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["gunicorn", "--bind", "0.0.0.0:8080", "app:app"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nada del otro mundo. Usamos &lt;code&gt;python:3.12-slim&lt;/code&gt; para mantener la imagen liviana y &lt;code&gt;gunicorn&lt;/code&gt; como servidor WSGI (más robusto que el servidor de desarrollo de Flask para producción).&lt;/p&gt;

&lt;p&gt;Un detalle importante: copiamos &lt;code&gt;requirements.txt&lt;/code&gt; &lt;strong&gt;antes&lt;/strong&gt; que el resto del código. Esto aprovecha el caché de capas de Docker — si no cambias las dependencias, Docker reutiliza esa capa y el build es mucho más rápido.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Probar localmente
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Construir la imagen&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; mifeed-aws &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Correr el contenedor&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;--name&lt;/span&gt; mifeed mifeed-aws

&lt;span class="c"&gt;# Verificar que levantó&lt;/span&gt;
curl http://localhost:8080/health
&lt;span class="c"&gt;# → {"status": "ok"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Abre &lt;code&gt;http://localhost:8080&lt;/code&gt; en tu navegador y deberías ver algo así:&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%2Fdkxvop0bduk6j2nigjnx.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fdkxvop0bduk6j2nigjnx.png" alt="Al entrar a http://localhost:8080" width="800" height="692"&gt;&lt;/a&gt;&lt;/p&gt;







&lt;p&gt;Para detenerlo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop mifeed &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker &lt;span class="nb"&gt;rm &lt;/span&gt;mifeed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Subir la imagen a Amazon ECR
&lt;/h2&gt;

&lt;p&gt;Amazon ECR (Elastic Container Registry) es el registro privado de imágenes Docker de AWS. Piénsalo como un Docker Hub pero dentro de tu cuenta de AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Crear el repositorio en ECR
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr create-repository &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository-name&lt;/span&gt; demos/mifeed-aws &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El comando devuelve un JSON. Copia el valor de &lt;code&gt;repositoryUri&lt;/code&gt;, lo vas a necesitar. Se ve así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;url.dkr.ecr.us-east-1.amazonaws.com/demos/mifeed-aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbhs4nwpqo56ea8cjescl.png" alt="Mi repositorio en AWS ECR" width="800" height="206"&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.2 Autenticarte con ECR
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
  | docker login &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    url.dkr.ecr.us-east-1.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si ves &lt;code&gt;Login Succeeded&lt;/code&gt;, estás lista para el siguiente paso.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Etiquetar y subir la imagen
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ECR_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;url.dkr.ecr.us-east-1.amazonaws.com/demos/mifeed-aws

&lt;span class="c"&gt;# Etiquetar la imagen local con el URI de ECR&lt;/span&gt;
docker tag mifeed-aws:latest &lt;span class="nv"&gt;$ECR_URI&lt;/span&gt;:latest

&lt;span class="c"&gt;# Subir al registro&lt;/span&gt;
docker push &lt;span class="nv"&gt;$ECR_URI&lt;/span&gt;:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El push puede tardar unos minutos la primera vez. Verás las capas subiendo una por una.&lt;/p&gt;







&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi6v0kv6aayv340zmz60z.png" alt="docker push en progreso" width="800" height="339"&gt;
&lt;/h2&gt;




&lt;p&gt;&lt;a 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%2Fkd5tvmhsyzrafb676z4h.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fkd5tvmhsyzrafb676z4h.png" alt="La imagen subida a AWS ECR" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Configurar y desplegar en Amazon ECS
&lt;/h2&gt;

&lt;p&gt;ECS (Elastic Container Service) es el servicio administrado de AWS para correr contenedores. Usaremos &lt;strong&gt;Fargate&lt;/strong&gt;, que es el modo serverless: no tienes que gestionar servidores ni instancias EC2.&lt;/p&gt;

&lt;p&gt;El flujo en ECS tiene tres piezas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task Definition  →  Cluster  →  Service
(qué correr)        (dónde)     (cuántas copias y cómo)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.1 Crear el Cluster
&lt;/h3&gt;

&lt;p&gt;Ve a la consola de AWS → ECS → &lt;strong&gt;Clusters&lt;/strong&gt; → &lt;strong&gt;Create Cluster&lt;/strong&gt;.&lt;/p&gt;




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




&lt;p&gt;Configuración:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cluster name:&lt;/strong&gt; &lt;code&gt;mifeed-aws&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure:&lt;/strong&gt; AWS Fargate (serverless)&lt;/li&gt;
&lt;li&gt;Deja el resto por defecto y haz clic en &lt;strong&gt;Create&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.2 Crear la Task Definition
&lt;/h3&gt;

&lt;p&gt;La Task Definition le dice a ECS qué imagen correr, cuánta CPU/memoria usar y en qué puerto escucha.&lt;/p&gt;

&lt;p&gt;Ve a &lt;strong&gt;Task Definitions&lt;/strong&gt; → &lt;strong&gt;Create new task definition&lt;/strong&gt;.&lt;/p&gt;




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




&lt;p&gt;Configuración:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Task definition family:&lt;/strong&gt; &lt;code&gt;mifeed-aws&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Launch type:&lt;/strong&gt; AWS Fargate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU:&lt;/strong&gt; 0.25 vCPU&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory:&lt;/strong&gt; 0.5 GB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container name:&lt;/strong&gt; &lt;code&gt;mifeed-aws&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image URI:&lt;/strong&gt; &lt;code&gt;url.dkr.ecr.us-east-1.amazonaws.com/demos/mifeed-aws:latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container port:&lt;/strong&gt; &lt;code&gt;8080&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol:&lt;/strong&gt; TCP&lt;/li&gt;
&lt;/ul&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%2Foc8tq45cblaw3bdebqwu.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Foc8tq45cblaw3bdebqwu.png" alt="**Configuración del contenedor en la Task Definition**" width="800" height="720"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Haz clic en &lt;strong&gt;Create&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Crear el Service
&lt;/h3&gt;

&lt;p&gt;El Service mantiene corriendo el número de Tasks que definas y las reinicia si fallan.&lt;/p&gt;

&lt;p&gt;En tu cluster &lt;code&gt;mifeed-aws&lt;/code&gt; → pestaña &lt;strong&gt;Services&lt;/strong&gt; → &lt;strong&gt;Create&lt;/strong&gt;.&lt;/p&gt;




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




&lt;p&gt;Configuración:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Family:&lt;/strong&gt; &lt;code&gt;mifeed-aws&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service name:&lt;/strong&gt; &lt;code&gt;mifeed-service&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desired tasks:&lt;/strong&gt; &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Launch type:&lt;/strong&gt; Fargate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC:&lt;/strong&gt; la VPC por defecto está bien para empezar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subnets:&lt;/strong&gt; selecciona al menos 2 subnets públicas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security group:&lt;/strong&gt; crea uno nuevo (o usa uno existente) con la siguiente regla de entrada:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tipo&lt;/th&gt;
&lt;th&gt;Puerto&lt;/th&gt;
&lt;th&gt;Origen&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Custom TCP&lt;/td&gt;
&lt;td&gt;8080&lt;/td&gt;
&lt;td&gt;0.0.0.0/0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public IP:&lt;/strong&gt; Enabled ← &lt;strong&gt;importante&lt;/strong&gt;, sin esto no tendrás IP pública&lt;/li&gt;
&lt;/ul&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%2Fuphwr7n5a78y9jm3bjks.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fuphwr7n5a78y9jm3bjks.png" alt="**Configuración de red del Service**" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Haz clic en &lt;strong&gt;Create Service&lt;/strong&gt; y espera unos minutos a que el Task pase a estado &lt;strong&gt;RUNNING&lt;/strong&gt;.&lt;/p&gt;




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




&lt;h2&gt;
  
  
  6. Conectarme a mi aplicación
&lt;/h2&gt;

&lt;p&gt;Una vez que el Task está en RUNNING, la app ya está viva en internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obtener la IP pública
&lt;/h3&gt;

&lt;p&gt;En la consola: &lt;strong&gt;Clusters&lt;/strong&gt; → &lt;code&gt;mifeed-aws&lt;/code&gt; → &lt;strong&gt;Tasks&lt;/strong&gt; → clic en el Task ID → busca la sección &lt;strong&gt;Network&lt;/strong&gt; → copia la &lt;strong&gt;Public IP&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft3gza3ulzf63wx5kp3a2.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Ft3gza3ulzf63wx5kp3a2.png" alt="IP pública del Task" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Abre en tu navegador:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;PUBLIC_IP&amp;gt;:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y deberías ver tu feed funcionando en la nube:&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%2Fts2a68ltjaajw953v1m1.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fts2a68ltjaajw953v1m1.png" alt="La app en producción" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Verificar el health check
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;PUBLIC_IP&amp;gt;:8080/health
&lt;span class="c"&gt;# → {"status": "ok"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ¿Y las actualizaciones automáticas?
&lt;/h2&gt;

&lt;p&gt;La app no necesita ninguna configuración especial. Como el feed se parsea en cada request, cada vez que alguien carga la página obtiene los datos más recientes de AWS. El contenedor puede estar corriendo semanas sin ningún reinicio y el feed siempre estará actualizado.&lt;/p&gt;

&lt;p&gt;Si quieres que ECS descargue automáticamente una nueva versión de tu imagen cuando hagas cambios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sube la nueva imagen a ECR con el mismo tag &lt;code&gt;latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;En ECS → tu Service → &lt;strong&gt;Update service&lt;/strong&gt; → activa &lt;strong&gt;Force new deployment&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;ECS descargará la nueva imagen y reemplazará el Task sin downtime&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Reflexión final
&lt;/h2&gt;

&lt;p&gt;Lo que me gustó de este ejercicio fue lo poco que tuve que tocar para pasar de "corre en mi máquina" a "corre en la nube":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El &lt;code&gt;Dockerfile&lt;/code&gt; no cambió&lt;/li&gt;
&lt;li&gt;La app no sabe si está en local o en ECS&lt;/li&gt;
&lt;li&gt;La única diferencia real es de dónde viene la imagen (local vs ECR)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eso es exactamente lo que hace valioso a Docker en un contexto de nube. El artefacto que pruebas localmente es el mismo artefacto que corre en producción.&lt;/p&gt;




&lt;h2&gt;
  
  
  Próximos pasos
&lt;/h2&gt;

&lt;p&gt;Si quisieras llevar esto a producción real, los siguientes pasos serían:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agregar un Load Balancer&lt;/strong&gt; (ALB) para tener una URL fija en vez de una IP efímera&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dominio propio&lt;/strong&gt; con Route 53 y un certificado SSL en ACM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD&lt;/strong&gt; con GitHub Actions para que cada push a &lt;code&gt;main&lt;/code&gt; actualice la imagen en ECR y fuerce un nuevo deploy en ECS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto Scaling&lt;/strong&gt; para que el servicio escale según la carga&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>aws</category>
      <category>ecs</category>
      <category>containerapps</category>
    </item>
    <item>
      <title>S3 annotations and the question of where object metadata should live</title>
      <dc:creator>Kento IKEDA</dc:creator>
      <pubDate>Thu, 18 Jun 2026 23:59:16 +0000</pubDate>
      <link>https://dev.to/aws-builders/s3-annotations-and-the-question-of-where-object-metadata-should-live-44h3</link>
      <guid>https://dev.to/aws-builders/s3-annotations-and-the-question-of-where-object-metadata-should-live-44h3</guid>
      <description>&lt;p&gt;On June 17, 2026, AWS Summit New York ran a long line of announcements that filled in the infrastructure for running agents in production. The official blog has the full roundup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/aws/top-announcements-of-the-aws-summit-in-new-york-2026/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/aws/top-announcements-of-the-aws-summit-in-new-york-2026/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of them reads as plain from the headline alone: S3 annotations. You can attach up to 1 GB of information directly to an object, so the first impression is "tags just got bigger."&lt;/p&gt;

&lt;p&gt;Reading it as a capacity story misses the point. This changes a decision: where the information attached to an S3 object should live. For a long time, the answer was usually "outside the object." You kept it in an external database or a sidecar file and reconciled the two with a sync process. S3 annotations add another option to that answer: don't move it out. If you have kept data in S3 while managing its metadata off to the side, you know the cost of keeping the two in sync.&lt;/p&gt;

&lt;p&gt;The official announcement is here:&lt;br&gt;
&lt;a href="https://aws.amazon.com/blogs/aws/amazon-s3-annotations-attach-rich-queryable-context-directly-to-your-objects/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/aws/amazon-s3-annotations-attach-rich-queryable-context-directly-to-your-objects/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The difference is character, not capacity
&lt;/h2&gt;

&lt;p&gt;Line up annotations against the existing ways to describe an object, and what matters is the difference in character, not the numbers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;How much&lt;/th&gt;
&lt;th&gt;Mutable&lt;/th&gt;
&lt;th&gt;Character&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;System-defined metadata&lt;/td&gt;
&lt;td&gt;Fixed fields&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Intrinsic properties of the object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User-defined metadata&lt;/td&gt;
&lt;td&gt;2 KB total, set at upload&lt;/td&gt;
&lt;td&gt;Effectively no&lt;/td&gt;
&lt;td&gt;Small incidental notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object tags&lt;/td&gt;
&lt;td&gt;Up to 10&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Labels for access control and lifecycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;annotations&lt;/td&gt;
&lt;td&gt;Up to 1,000, 1 GB total&lt;/td&gt;
&lt;td&gt;Yes, without rewriting&lt;/td&gt;
&lt;td&gt;Structured knowledge that grows over time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The counts and sizes come from the official blog. The clear gap is in the bottom two rows. Tags are key-and-value labels, meant for access control and cost allocation. annotations carry structure in JSON or YAML, and you can rewrite them as often as you like without rewriting the object. They travel with the object on copy and replication, and they are removed when the object is deleted.&lt;/p&gt;

&lt;p&gt;A different character means a different job. Tags describe how to handle an object. annotations hold what the object is and what is known about it, the kind of knowledge that accumulates after the fact: an AI-generated summary, an inference confidence score, processing history. That information does not fit in 2 KB, and you want to update it as the data changes. Until now, meeting that requirement meant moving the context outside the object.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AWS says, and one step past it
&lt;/h2&gt;

&lt;p&gt;The official blog describes annotations as removing the need for a separate metadata system. It is true that the pattern of double-writing to DynamoDB for cross-object search, syncing with Lambda, and watching for drift can be retired for some use cases. The annotations you attach flow through S3 Metadata into Apache Iceberg tables and become queryable from Amazon Athena.&lt;/p&gt;

&lt;p&gt;But stopping at "you no longer need an external database" only repeats what AWS already said. The part worth pressing on is what else moves along with the location. When the context lived outside the object, who could touch it was decided directly by the access control on that external database. Once it sits on the object, you have to redesign who is allowed to change which context. Adding and reading annotations requires the IAM actions &lt;code&gt;s3:PutObjectAnnotation&lt;/code&gt; and &lt;code&gt;s3:GetObjectAnnotation&lt;/code&gt;. Coupling context tightly to data is the benefit. It also means tampering with the context turns directly into a misreading of the data.&lt;/p&gt;

&lt;p&gt;The other thing that moves is responsibility for structure. Key-and-value tags left almost no room for design, but being able to hold structure means you have to decide which key represents what and at what granularity to split things. If an agent is meant to read it, that decision drives search quality. Dump everything in, and you end up with annotations that are useless in a later cross-object query. The freedom of capacity arrives bundled with the responsibility of design.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much of your sync layer can you actually retire
&lt;/h2&gt;

&lt;p&gt;"Then move everything to annotations" does not follow. There is a line on what you can move over.&lt;/p&gt;

&lt;p&gt;In a verification by Classmethod, the Annotation Table took about 25 minutes to become active even in a small environment. That is a third-party measurement, but it lines up with the spec: reflection into the table is asynchronous. What you attach is not reflected into cross-object search the instant you write it.&lt;br&gt;
&lt;a href="https://dev.classmethod.jp/articles/s3-annotations-crud-athena-search/" rel="noopener noreferrer"&gt;https://dev.classmethod.jp/articles/s3-annotations-crud-athena-search/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The plain conclusion is about fit with low-latency reads. Information you want to pull in milliseconds on every screen render, or that needs a secondary-index lookup, still fits the older DynamoDB design better. Context that gets updated later but does not need immediacy, such as compliance status, history, or summaries, leans toward annotations. What you retire is part of the sync layer, not all of it.&lt;/p&gt;

&lt;p&gt;Cost does not move in one direction either. You can let go of the sync layer that watches for drift, but storing and reading annotations is billed at the same rates as S3 Standard storage and requests, and the S3 Metadata and Annotation Tables behind cross-object search carry their own processing and storage charges. You weigh the cost you remove against the cost you add. The pricing page has the breakdown.&lt;br&gt;
&lt;a href="https://aws.amazon.com/s3/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/s3/pricing/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Region coverage differs too. Per the official blog, attaching an annotation works in nearly all Regions, while the Annotation Table used for cross-object search is limited to Regions where S3 Metadata is available. S3 Metadata coverage has been expanding in waves.&lt;br&gt;
&lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/11/amazon-s3-metadata-expands-22-regions/" rel="noopener noreferrer"&gt;https://aws.amazon.com/about-aws/whats-new/2025/11/amazon-s3-metadata-expands-22-regions/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Coverage and per-feature availability change, so the documentation is the reliable place to check the current state.&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/annotations-overview.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/annotations-overview.html&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Is an agent a precondition
&lt;/h2&gt;

&lt;p&gt;annotations are designed for agents, and the official explanation is written with that in mind. It is tempting to read "I don't run AI agents, so this isn't for me" and skip the rest, but hold off for a moment.&lt;/p&gt;

&lt;p&gt;Agents or not, if you operate sidecar files or an external metadata database today, the shift in location is where the benefit shows up. What annotations really are is an improvement to metadata management itself: structured context attached close to the object, queryable across objects. Natural-language search can be added later through the S3 Tables MCP server, so retiring the sync layer first is a fine way in. The agent is a possible first reader of the context you place, not a precondition.&lt;/p&gt;

&lt;h2&gt;
  
  
  From object-level to organization-level context
&lt;/h2&gt;

&lt;p&gt;Widen the view, and annotations look less like a standalone feature and more like part of a movement. At the same Summit, AWS previewed AWS Context, which maps the data relationships across an organization into a knowledge graph. Its availability is stated as forthcoming.&lt;br&gt;
&lt;a href="https://aws.amazon.com/blogs/machine-learning/context-intelligence-for-your-data-and-ai-agents-at-scale/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/machine-learning/context-intelligence-for-your-data-and-ai-agents-at-scale/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If S3 annotations are context at the object level, AWS Context is context at the organization level. Both are designed to surface in Apache Iceberg format in S3, so they read as continuous. The movement is from each team holding its own context for RAG toward a shared context layer with managed access for the whole organization. annotations cover the lowest layer of that, the part where context is attached close to the object.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;The first step is an inventory of the context you currently hold in external databases or sidecars. Pull out the context described above, the kind that gets updated later but does not need immediacy, and make it a candidate for annotations. For schema, deciding a handful of keys you want an agent to read on a single bucket is enough to begin. As long as you hold the premise that responsibility for structure is now yours, you lower the odds of getting stuck in a later cross-object query.&lt;/p&gt;

&lt;p&gt;For a long time, keeping context outside the object was simply the premise. There is meaning in being able to question that premise at all. This is not a story about more capacity. It is a story about who is responsible for the context, and that answer has come back to the side of the object.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>ai</category>
      <category>metadata</category>
    </item>
    <item>
      <title>Investigate Why AWS Costs Suddenly Increased</title>
      <dc:creator>Noureldin ehab</dc:creator>
      <pubDate>Thu, 18 Jun 2026 12:56:43 +0000</pubDate>
      <link>https://dev.to/aws-builders/investigate-why-aws-costs-suddenly-increased-5clk</link>
      <guid>https://dev.to/aws-builders/investigate-why-aws-costs-suddenly-increased-5clk</guid>
      <description>&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;By the end of this tutorial, you'll learn how to use Stakpak to investigate zombie resources in a live AWS production account, identify every detached volume, idle load balancer, orphaned snapshot, and forgotten instance silently accruing&lt;br&gt;
charges, apply the right cleanups safely, validate that production stays healthy throughout, and configure &lt;a href="https://stakpak.gitbook.io/docs/how-it-works/autopilot" rel="noopener noreferrer"&gt;Stakpak Autopilot&lt;/a&gt; to help detect similar resource sprawl automatically in the future.&lt;/p&gt;

&lt;p&gt;Note: Stakpak is open source and works with any model you choose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Your AWS production application is healthy. The pipeline is green, the SLOs are green, the on call channel is quiet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But your FinOps lead just pinged the team:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We're $4,300 over budget this month and trending 35% above last. Nothing in the apps catalog has changed. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You start the usual cost investigation loop, Cost Explorer by service and by tag, VPCs and NAT Gateways, unattached EBS volumes, stale snapshots, idle Elastic IPs, VPC endpoints, RDS instances, CloudWatch log retention, S3 lifecycle policies, CloudTrail events.&lt;/p&gt;

&lt;p&gt;Cost Explorer shows the highest cost is from EC2, Other, EKS, and CloudWatch. The rest is scattered across eight services in chunks too small to feel urgent on their own. Tag breakdowns are messy because half the spend rolls up under (no tag) or Owner=unknown, and the biggest single CUR line item is a Fargate workload nobody on the current team recognizes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Is the $890 NAT Gateway data line the orphaned VPC nobody decommissioned, or production traffic that should be flowing through a VPC endpoint?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are the 1,400+ EBS snapshots load-bearing, or from a Lambda deprecated 18 months ago and never disabled?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is the RDS instance tagged Environment=staging-old truly idle, or does some nightly job still touch it?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which of the 12 likely cost drivers, if any, would be the wrong thing to delete?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cost Explorer gives you part of the picture. AWS resource APIs give you the rest. But you still have to connect them, attribute them to owners, correlate them with utilization, and decide what is safe to remediate.&lt;/p&gt;

&lt;h1&gt;
  
  
  Application
&lt;/h1&gt;

&lt;p&gt;Northstar Commerce is a B2B ecommerce platform running on AWS, with workloads spread across EKS, ECS Fargate, Lambda, and Vercel. The main components are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;storefront: Customer facing Next.js app on Vercel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;api-gateway: Public REST and GraphQL edge on EKS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;orders-service: Order lifecycle, Go on EKS, backed by Aurora PostgreSQL.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;payments-service: Java on ECS Fargate, integrates with Stripe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;inventory-worker: Celery workers on EKS draining an SQS queue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;search-indexer: Rust Lambda keeping OpenSearch in sync.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;admin-console: React SPA on S3 behind CloudFront.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shared infrastructure includes an EKS cluster, an Aurora cluster, an ElastiCache Redis, an MSK cluster, an OpenSearch domain, ECR, Route 53, ACM, and Secrets Manager. &lt;/p&gt;

&lt;p&gt;Primary region is us-east-1, with us-west-2 as a disaster recovery region.&lt;/p&gt;

&lt;p&gt;Every workload in the catalog is healthy and serving traffic. None of the recent deploys touched infrastructure. Which is what makes a 35% cost jump suspicious: the bill is growing faster than the application is.&lt;/p&gt;

&lt;p&gt;Now that we understand the app and architecture, we can start investigating the cost spike.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step-by-Step Guide
&lt;/h1&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.dev/" rel="noopener noreferrer"&gt;Install Stakpak&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html" rel="noopener noreferrer"&gt;AWS credentials configured locally&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Open Stakpak and ask it to &lt;code&gt;investigate the cloud cost spike&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now lets let it do its magic&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%2Fpr31v2mvf2aatv3n0e1s.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fpr31v2mvf2aatv3n0e1s.png" alt=" " width="620" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stakpak traced the cost spike across billing, utilization, and infrastructure signals and identified multiple sources of unnecessary spend driving the 35% increase.&lt;/p&gt;

&lt;p&gt;It found that the $4,270 June overage came from 12 distinct cost drivers totaling ~$6,800/month of avoidable spend, none caused by application changes. The signals were spread across Cost Explorer deltas, tag anomalies (staging-old +5,854%, intern-summer-2025 +9,677%), CUR line items, CloudWatch utilization, and CloudTrail provenance.&lt;/p&gt;

&lt;p&gt;Then it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Deleted the orphaned legacy VPC and its NAT Gateway, abandoned since the 2024 EKS migration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Terminated three m5.2xlarge legacy batch workers idling at 2% CPU&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deleted the forgotten eks-dev-intern cluster and its Fargate Spot profile, running since July 2025&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deleted the staging-old RDS instance after 30 days of zero connections&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Removed five unattached EBS volumes, three idle Elastic IPs, and 1,400+ stale snapshots from a deprecated 2023 backup Lambda&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Disabled GuardDuty in eu-west-1 and ap-southeast-1 where no workloads exist&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added an S3 Gateway VPC endpoint to the production VPC, eliminating $890/month of NAT data processing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Applied lifecycle rules to northstar-prod-edge-logs and 30-day retention to three "Never expire" log groups&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fixed cross-AZ traffic on orders-service&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deployed AWS Budgets with anomaly detection, tag-enforcement SCPs, and Config rules&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the changes were applied, Stakpak verified that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;All 12 driver resources are gone or reconfigured&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every production workload remained healthy with no SLO regressions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Projected run-rate dropped to ~$9,600/month, below the January baseline&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now everything is cleaned up 🥳&lt;/p&gt;

&lt;p&gt;Now its asking us if we want to sit up &lt;a href="https://stakpak.gitbook.io/docs/how-it-works/autopilot" rel="noopener noreferrer"&gt;stakpak Autopilot&lt;/a&gt; to avoid future cost spikes&lt;/p&gt;

&lt;p&gt;Note: Stakpak Autopilot monitors your apps 24/7, detects unexpected changes, fixes what’s safe, and only alerts you when it actually matters.&lt;/p&gt;

&lt;h1&gt;
  
  
  Monitoring
&lt;/h1&gt;

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

&lt;h1&gt;
  
  
  Extra Resources:
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Related Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/tutorial/investigate-why-aws-costs-suddenly-increased" rel="noopener noreferrer"&gt;Investigate and Clean Up Unused Cloud Resources&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/tutorial/deploy-coolify-on-aws-and-deploy-your-app" rel="noopener noreferrer"&gt;Deploy Coolify on AWS &amp;amp; Deploy Your App&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/tutorial/load-test-to-optimize-cloud-costs" rel="noopener noreferrer"&gt;Load Test to Optimize Cloud Costs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/get-started/install-stakpak" rel="noopener noreferrer"&gt;Install Stakpak&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/get-started/configure-stakpak" rel="noopener noreferrer"&gt;Configure Stakpak&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html" rel="noopener noreferrer"&gt;Configuration and credential file settings in the AWS CLI&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/how-it-works/autopilot" rel="noopener noreferrer"&gt;Autopilot&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/how-it-works/handling-secrets" rel="noopener noreferrer"&gt;Handling Secrets&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stakpak.gitbook.io/docs/how-it-works/warden-guardrails" rel="noopener noreferrer"&gt;Warden Guardrails&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>aws</category>
      <category>cloudnative</category>
      <category>agents</category>
    </item>
    <item>
      <title>Kiro V1.0.0 is out: migrate your hooks!</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Thu, 18 Jun 2026 09:44:45 +0000</pubDate>
      <link>https://dev.to/aws-builders/kiro-v100-is-out-migrate-your-hooks-59oc</link>
      <guid>https://dev.to/aws-builders/kiro-v100-is-out-migrate-your-hooks-59oc</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%2Fwps55uc1qnn5q0frr4uu.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fwps55uc1qnn5q0frr4uu.png" alt=" " width="800" height="783"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kiro 1.0 is now publicly available and ready to use!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fortunately, KiroGraph, my code intelligence graph for Kiro, relies heavily on Kiro's lifecycle components, making it a great real-world test case to verify whether all the features we loved during the beta are still working as expected.&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%2F1znqmlare6zw9wtu0fj9.png" class="article-body-image-wrapper"&gt;&lt;img 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%2F1znqmlare6zw9wtu0fj9.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/davide-desio-eleva" rel="noopener noreferrer"&gt;
        davide-desio-eleva
      &lt;/a&gt; / &lt;a href="https://github.com/davide-desio-eleva/kirograph" rel="noopener noreferrer"&gt;
        kirograph
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Semantic code knowledge graph for Kiro: fewer tool calls, instant symbol lookups, 100% local.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/davide-desio-eleva/kirograph/main/assets/logo.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdavide-desio-eleva%2Fkirograph%2Fmain%2Fassets%2Flogo.png" alt="KiroGraph terminal"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;KiroGraph&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/davide-desio-eleva/kirograph/main/assets/terminal.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdavide-desio-eleva%2Fkirograph%2Fmain%2Fassets%2Fterminal.png" alt="KiroGraph terminal"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Semantic code knowledge graph for &lt;a href="https://kiro.dev" rel="nofollow noopener noreferrer"&gt;Kiro&lt;/a&gt;: fewer tool calls, instant symbol lookups, 100% local.&lt;/p&gt;
&lt;p&gt;Inspired by &lt;a href="https://github.com/colbymchenry/codegraph" rel="noopener noreferrer"&gt;CodeGraph&lt;/a&gt; by &lt;a href="https://github.com/colbymchenry" rel="noopener noreferrer"&gt;colbymchenry&lt;/a&gt; for Claude Code, rebuilt natively for Kiro's MCP and hooks system.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Full support is for Kiro only.&lt;/strong&gt; Experimental integrations for 34 other MCP-capable tools (Cursor, Copilot, Claude Code, Windsurf, Cline, and more) are available with auto-detection. See &lt;a href="https://github.com/davide-desio-eleva/kirograph/docs/guide/integrations.md" rel="noopener noreferrer"&gt;Integrations&lt;/a&gt; for the full list.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why KiroGraph?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;When you ask Kiro to work on a complex task, it explores your codebase using file reads, grep, and glob searches. Every one of those is a tool call, and tool calls consume context and slow things down.&lt;/p&gt;
&lt;p&gt;KiroGraph gives Kiro a semantic knowledge graph that's pre-indexed and always up to date. Instead of scanning files to understand your code, Kiro queries the graph instantly: symbol relationships, call graphs, type hierarchies, impact radius, all in a single MCP tool call.&lt;/p&gt;
&lt;p&gt;The result is fewer tool…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/davide-desio-eleva/kirograph" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Good News
&lt;/h2&gt;

&lt;p&gt;Let's start with what didn't change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Steering and Skills&lt;/strong&gt; (using the SKILL.md format) continue to work exactly as before, with the same structure and frontmatter.&lt;/li&gt;
&lt;li&gt;All &lt;strong&gt;MCP&lt;/strong&gt; servers, both project-level and global, are automatically connected and immediately available after the upgrade.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;So far, so good.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Now for the interesting part.&lt;/p&gt;

&lt;p&gt;Many of us have learned to love &lt;strong&gt;Kiro Hooks&lt;/strong&gt; because they allow us to customize development workflows and lifecycle events.&lt;/p&gt;

&lt;p&gt;For example, &lt;strong&gt;KiroGraph uses a hook to automatically synchronize the code graph at agent stop whenever files are modified&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you open an existing project that still uses legacy hooks, &lt;strong&gt;you'll immediately notice that they're marked as V1 and inactive.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At first glance, this looks alarming.&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%2Fjtz94y5s57ct8cpc4m9f.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fjtz94y5s57ct8cpc4m9f.png" alt=" " width="768" height="1408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Kiro migration pop up
&lt;/h2&gt;

&lt;p&gt;The good news is that &lt;strong&gt;Kiro can actually help you migrate your hooks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you restart Kiro you'll probably see this migration popup which will assist you in a complete migration&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%2Fez5vhnpbnjv1oumh4j12.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fez5vhnpbnjv1oumh4j12.png" alt=" " width="800" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kiro will confirm the migration.&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%2Ffj2cj6w9oz12pa6t7yu5.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Ffj2cj6w9oz12pa6t7yu5.png" alt=" " width="799" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Be aware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you'll still have older version, delete them&lt;/li&gt;
&lt;li&gt;new version won't be active, you should activate them one by one&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If the popup doesn't appear
&lt;/h2&gt;

&lt;p&gt;&lt;a 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%2F6g4os1rznxx0bws2n25j.png" class="article-body-image-wrapper"&gt;&lt;img 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%2F6g4os1rznxx0bws2n25j.png" alt=" " width="712" height="1544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This has happened to me in multiple projects, and I’ve also seen it affect other users working with KiroGraph or older hook formats: the expected popup doesn’t appear.&lt;/p&gt;

&lt;p&gt;An IDE restart might be sufficient, but in some cases a fresh installation can help as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However, if you’re using a tool like KiroGraph or a CLI that generates hooks in the older format, you won’t see this popup.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don’t worry: although the hook schema has changed and older hooks are no longer active, Kiro still understands both formats and can automatically generate the updated configuration for you.&lt;/p&gt;

&lt;p&gt;In my case, the entire migration was completed with a single prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Prompt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyze the `.kiro/hooks` directory and identify all existing Kiro Hooks. These hooks use V1 format and are no longer active in the latest Kiro IDE V1.0.0.

Your tasks is:

1. Determine the differences between the legacy V1 hook format and the current hook format supported by Kiro IDE.
2. Migrate all existing V1 hooks to the new format.
3. Verify that the generated hooks are recognized and executed by the current Kiro IDE.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Kiro's Response
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Now let me find where these hooks are. 
Now I have full understanding of the v1 format. 
Now I need to map the v1 triggers/actions to v2 format:

| v1 `when.type` | v2 `trigger` |
|---|---|
| `agentStop` | `Stop` |
| `preToolUse` | `PreToolUse` |

| v1 `then.type` | v2 `action.type` |
|---|---|
| `runCommand` | `command` |
| `askAgent` | `agent` |

| v1 `when.toolTypes: ["shell"]` | v2 `matcher` regex for shell tools |

Now write the new v2 hook files. 
All done. 
Here's what changed:

**`.kiro/hooks/`** — Replaced all 6 `.kiro.hook` files with their `.json` v2 equivalents. 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After analyzing the existing hooks, Kiro identified the required mappings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Legacy Format&lt;/th&gt;
&lt;th&gt;New Format&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;agentStop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Stop&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preToolUse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PreToolUse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;runCommand&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;command&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;askAgent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;agent&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Migrated all existing &lt;code&gt;.kiro.hook&lt;/code&gt; files to the new JSON format.&lt;/li&gt;
&lt;li&gt;Verified that all migrated hooks were correctly recognized by Kiro IDE.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before: old hooks format output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"KiroGraph Sync on Agent Stop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sync the KiroGraph index when the agent stops, picking up any file edits, creates, or deletes from the session."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agentStop"&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;"then"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"runCommand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kirograph sync --quiet 2&amp;gt;&amp;amp;1 &amp;gt; /dev/null"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  After: new hooks format output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"KiroGraph Sync on Agent Stop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"trigger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kirograph sync --quiet 2&amp;gt;&amp;amp;1 &amp;gt; /dev/null"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  KiroGraph
&lt;/h2&gt;

&lt;p&gt;Maintainers of tools like &lt;strong&gt;KiroGraph&lt;/strong&gt;, which automatically configure hooks for you, will likely update their installers and scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KiroGraph&lt;/strong&gt; is ready for Kiro &lt;code&gt;v1.0.0&lt;/code&gt; and ensures backward compatibility for users still on the beta version.&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%2Fhaox0tzftpxcjeto9k1t.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fhaox0tzftpxcjeto9k1t.png" alt=" " width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just update it to &lt;code&gt;v0.26.0&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;If you've been holding off on upgrading because you're worried about breaking your custom workflows, don't panic.&lt;/p&gt;

&lt;p&gt;Steering, Skills, and MCP integrations continue to work seamlessly, and even hook migrations can be largely automated by Kiro itself.&lt;/p&gt;

&lt;p&gt;Sometimes the best tool to fix a breaking change is the tool that introduced it.&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
As of June 2026, I’m an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

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

</description>
      <category>kiro</category>
      <category>ai</category>
      <category>aws</category>
    </item>
    <item>
      <title>Google Open Knowledge Format: Why Enterprise Agents Need a Knowledge Layer, Not Just More Tools</title>
      <dc:creator>Amit Kayal</dc:creator>
      <pubDate>Thu, 18 Jun 2026 06:41:06 +0000</pubDate>
      <link>https://dev.to/aws-builders/google-open-knowledge-format-why-enterprise-agents-need-a-knowledge-layer-not-just-more-tools-je1</link>
      <guid>https://dev.to/aws-builders/google-open-knowledge-format-why-enterprise-agents-need-a-knowledge-layer-not-just-more-tools-je1</guid>
      <description>&lt;h1&gt;
  
  
  Google Open Knowledge Format: Why Enterprise Agents Need a Knowledge Layer, Not Just More Tools
&lt;/h1&gt;

&lt;p&gt;Most enterprise AI conversations still start in the wrong place.&lt;/p&gt;

&lt;p&gt;They start with the model.&lt;/p&gt;

&lt;p&gt;Which model should we use? Which framework should we adopt? Which vendor has the best agent platform? Which tools should we connect next?&lt;/p&gt;

&lt;p&gt;These are fair questions. But in real enterprise architecture, they are not the hardest questions.&lt;/p&gt;

&lt;p&gt;The harder question is this:&lt;/p&gt;

&lt;p&gt;Can our AI systems actually understand how our business works?&lt;/p&gt;

&lt;p&gt;That is why Google Cloud’s article on Open Knowledge Format caught my attention. The article talks about a simple but important idea: representing knowledge in a way that humans can read and machines can use. In OKF, that means markdown for the content and structured metadata for context.&lt;/p&gt;

&lt;p&gt;At first glance, that may sound too simple.&lt;/p&gt;

&lt;p&gt;But that simplicity is the point.&lt;/p&gt;

&lt;p&gt;Enterprises do not need another place where knowledge goes to die. We already have enough portals, catalogs, wikis, dashboards, folders, and internal tools. What we need is a practical way to package knowledge so it can be reviewed, versioned, governed, searched, and reused by both people and AI agents.&lt;/p&gt;

&lt;p&gt;That is where this idea becomes very relevant for agentic AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Enterprise AI Problem
&lt;/h2&gt;

&lt;p&gt;Most organizations already have the knowledge their AI agents need.&lt;/p&gt;

&lt;p&gt;They have it in databases, dashboards, tickets, architecture notes, runbooks, Confluence pages, data catalogs, code comments, incident reports, old project documents, and the heads of experienced employees.&lt;/p&gt;

&lt;p&gt;The issue is not that knowledge does not exist.&lt;/p&gt;

&lt;p&gt;The issue is that it is fragmented.&lt;/p&gt;

&lt;p&gt;Some of it is outdated. Some of it is duplicated. Some of it is tribal. Some of it is locked inside tools. Some of it is written for humans but not structured enough for AI systems to use reliably.&lt;/p&gt;

&lt;p&gt;This becomes a serious problem when we move from AI assistants to AI agents.&lt;/p&gt;

&lt;p&gt;An assistant can give a helpful answer. An agent does more. It plans, selects tools, queries systems, executes steps, generates outputs, and sometimes triggers workflows.&lt;/p&gt;

&lt;p&gt;That means the cost of wrong context is much higher.&lt;/p&gt;

&lt;p&gt;A data agent may know how to generate SQL. But does it know which table is the source of truth?&lt;/p&gt;

&lt;p&gt;A finance agent may calculate revenue. But does it know whether the business means booked revenue, invoiced revenue, recognized revenue, or collected cash?&lt;/p&gt;

&lt;p&gt;A support agent may summarize a customer case. But does it know what customer information must be masked before anything is shared externally?&lt;/p&gt;

&lt;p&gt;A delivery agent may review project status. But does it understand governance rules, escalation paths, release gates, and dependency risks?&lt;/p&gt;

&lt;p&gt;A cloud cost agent may recommend savings. But does it know which environments are production-critical and which ones are safe to shut down?&lt;/p&gt;

&lt;p&gt;Without this context, agents do not become enterprise-ready. They become fast, confident, and risky.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Tools Will Not Solve This
&lt;/h2&gt;

&lt;p&gt;One common mistake in agentic AI is assuming that more tool access means better capability.&lt;/p&gt;

&lt;p&gt;Connect the database.&lt;br&gt;
Connect the CRM.&lt;br&gt;
Connect the ticketing system.&lt;br&gt;
Connect the cloud APIs.&lt;br&gt;
Connect the document repository.&lt;br&gt;
Connect the workflow engine.&lt;/p&gt;

&lt;p&gt;This improves reach, but not necessarily judgment.&lt;/p&gt;

&lt;p&gt;An agent with many tools and weak context can still choose the wrong source, apply the wrong rule, query the wrong table, expose the wrong field, or automate the wrong step.&lt;/p&gt;

&lt;p&gt;That is why I believe every serious enterprise agentic AI framework needs three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A reasoning layer&lt;/li&gt;
&lt;li&gt;A tool/action layer&lt;/li&gt;
&lt;li&gt;A governed knowledge layer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most teams are investing heavily in the first two. They are testing models, orchestration frameworks, prompts, tools, APIs, and agent workflows.&lt;/p&gt;

&lt;p&gt;That work is needed.&lt;/p&gt;

&lt;p&gt;But the third layer is where enterprise differentiation will come from.&lt;/p&gt;

&lt;p&gt;The model can be changed.&lt;/p&gt;

&lt;p&gt;The tools can be integrated.&lt;/p&gt;

&lt;p&gt;But the organization’s internal knowledge — its definitions, operating rules, business logic, exceptions, ownership, architecture, and lessons learned — is unique.&lt;/p&gt;

&lt;p&gt;That is the real asset.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Google Open Knowledge Format Gets Right
&lt;/h2&gt;

&lt;p&gt;What I like about the Open Knowledge Format idea is that it does not overcomplicate the problem.&lt;/p&gt;

&lt;p&gt;It treats knowledge as something that should be readable, portable, structured, and maintainable.&lt;/p&gt;

&lt;p&gt;Markdown makes it easy for humans to read and contribute. Structured metadata makes it easier for systems and agents to classify, retrieve, and use the knowledge. Version control makes review and audit possible.&lt;/p&gt;

&lt;p&gt;This matters because traditional documentation is passive.&lt;/p&gt;

&lt;p&gt;Someone writes it. Someone may read it. Eventually, it becomes stale.&lt;/p&gt;

&lt;p&gt;Agentic AI needs active knowledge.&lt;/p&gt;

&lt;p&gt;The knowledge has to be available at runtime. It should help the agent decide what to do, what not to do, which source to trust, which rule to apply, and when to escalate.&lt;/p&gt;

&lt;p&gt;A database schema may say that a table has a column called &lt;code&gt;segment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is useful, but not enough.&lt;/p&gt;

&lt;p&gt;The agent also needs to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What does &lt;code&gt;segment&lt;/code&gt; mean?&lt;/li&gt;
&lt;li&gt;Who owns the definition?&lt;/li&gt;
&lt;li&gt;Which values are valid?&lt;/li&gt;
&lt;li&gt;Is the field reliable?&lt;/li&gt;
&lt;li&gt;Can it be used for reporting?&lt;/li&gt;
&lt;li&gt;Can it be exposed externally?&lt;/li&gt;
&lt;li&gt;Are there legacy exceptions?&lt;/li&gt;
&lt;li&gt;Which workflows are allowed to use it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the gap between data access and enterprise intelligence.&lt;/p&gt;
&lt;h2&gt;
  
  
  How This Fits into an Agentic AI Framework
&lt;/h2&gt;

&lt;p&gt;In our agentic AI framework, I would treat an OKF-like structure as the Enterprise Knowledge Layer.&lt;/p&gt;

&lt;p&gt;This layer should sit between enterprise systems and agent execution.&lt;/p&gt;

&lt;p&gt;The agent should not jump directly from a user request to a tool call. That is where many mistakes happen.&lt;/p&gt;

&lt;p&gt;A better flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User request
   ↓
Agent identifies intent and domain
   ↓
Agent retrieves relevant knowledge
   ↓
Agent checks source of truth, ownership, caveats, access rules, and usage guidance
   ↓
Agent plans the action
   ↓
Agent calls the right tool
   ↓
Agent produces the answer or executes the workflow
   ↓
Agent proposes a knowledge update if reusable learning is found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This changes the quality of execution.&lt;/p&gt;

&lt;p&gt;Take a simple question:&lt;/p&gt;

&lt;p&gt;“Show me revenue by customer segment.”&lt;/p&gt;

&lt;p&gt;A weak agent will search for tables with names like &lt;code&gt;revenue&lt;/code&gt;, &lt;code&gt;customer&lt;/code&gt;, and &lt;code&gt;segment&lt;/code&gt;, then generate SQL.&lt;/p&gt;

&lt;p&gt;A stronger enterprise agent will first check the knowledge layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which revenue table is approved?&lt;/li&gt;
&lt;li&gt;Which revenue definition applies?&lt;/li&gt;
&lt;li&gt;Which customer segment field is trusted?&lt;/li&gt;
&lt;li&gt;Which join is valid?&lt;/li&gt;
&lt;li&gt;Which date logic should be used?&lt;/li&gt;
&lt;li&gt;Are there caveats for legacy accounts?&lt;/li&gt;
&lt;li&gt;Is the user allowed to see this output?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only after that should it query the database.&lt;/p&gt;

&lt;p&gt;That is the difference between automation and governed intelligence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating Open Knowledge Format from AWS SQL Databases
&lt;/h2&gt;

&lt;p&gt;For AWS SQL environments such as Amazon RDS, Aurora, and Redshift, the starting point is metadata extraction.&lt;/p&gt;

&lt;p&gt;We can automatically extract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database names&lt;/li&gt;
&lt;li&gt;Schema names&lt;/li&gt;
&lt;li&gt;Table names&lt;/li&gt;
&lt;li&gt;Column names&lt;/li&gt;
&lt;li&gt;Data types&lt;/li&gt;
&lt;li&gt;Primary keys&lt;/li&gt;
&lt;li&gt;Foreign keys&lt;/li&gt;
&lt;li&gt;Indexes&lt;/li&gt;
&lt;li&gt;Nullability&lt;/li&gt;
&lt;li&gt;Row counts&lt;/li&gt;
&lt;li&gt;Table comments&lt;/li&gt;
&lt;li&gt;Column comments&lt;/li&gt;
&lt;li&gt;AWS tags&lt;/li&gt;
&lt;li&gt;Freshness indicators&lt;/li&gt;
&lt;li&gt;Query usage patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS Glue Crawlers and the Glue Data Catalog can help discover and centralize metadata. Database-native sources like &lt;code&gt;information_schema&lt;/code&gt; can provide table and column-level structure.&lt;/p&gt;

&lt;p&gt;But metadata is not knowledge.&lt;/p&gt;

&lt;p&gt;A pipeline can discover that a table has a column called &lt;code&gt;revenue_amount&lt;/code&gt;. It cannot automatically know whether that means booked revenue, recognized revenue, invoiced revenue, or pipeline value. That meaning has to come from finance, sales operations, data owners, or approved documentation.&lt;/p&gt;

&lt;p&gt;So OKF generation should be semi-automated.&lt;/p&gt;

&lt;p&gt;Technical metadata should be generated automatically. Business meaning should be reviewed and approved by the right domain owners.&lt;/p&gt;

&lt;p&gt;For every critical SQL table, the knowledge file should capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business purpose&lt;/li&gt;
&lt;li&gt;Source system&lt;/li&gt;
&lt;li&gt;Source-of-truth status&lt;/li&gt;
&lt;li&gt;Data owner&lt;/li&gt;
&lt;li&gt;Data steward&lt;/li&gt;
&lt;li&gt;Refresh frequency&lt;/li&gt;
&lt;li&gt;Key columns&lt;/li&gt;
&lt;li&gt;Approved joins&lt;/li&gt;
&lt;li&gt;Common usage patterns&lt;/li&gt;
&lt;li&gt;Prohibited usage&lt;/li&gt;
&lt;li&gt;Data quality rules&lt;/li&gt;
&lt;li&gt;Sensitivity classification&lt;/li&gt;
&lt;li&gt;Agent usage guidance&lt;/li&gt;
&lt;li&gt;Known caveats&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A table-level knowledge file should not simply describe the table. It should tell an agent how to use that table safely.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sales.crm.customer_account&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;table&lt;/span&gt;
&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aurora-postgresql&lt;/span&gt;
&lt;span class="na"&gt;cloud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
&lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crm_prod&lt;/span&gt;
&lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sales&lt;/span&gt;
&lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customer_account&lt;/span&gt;
&lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sales&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;revenue-operations&lt;/span&gt;
&lt;span class="na"&gt;steward&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data-platform-team&lt;/span&gt;
&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;classification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;confidential&lt;/span&gt;
&lt;span class="na"&gt;pii&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;freshness_sla&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;15&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;minutes"&lt;/span&gt;
&lt;span class="na"&gt;source_system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;salesforce&lt;/span&gt;
&lt;span class="na"&gt;agent_usage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allowed_with_row_level_controls&lt;/span&gt;
&lt;span class="na"&gt;approval_status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;approved&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# customer_account&lt;/span&gt;

&lt;span class="gu"&gt;## Business Meaning&lt;/span&gt;

This table represents customer account records synchronized from Salesforce into the CRM production database.

It is the approved source for account ownership, account segment, customer lifecycle stage, and sales territory mapping.

&lt;span class="gu"&gt;## Agent Usage Guidance&lt;/span&gt;

Use this table for customer account analysis, sales ownership, account segmentation, and lifecycle stage reporting.

Do not use this table for audited revenue reporting, invoice reconciliation, or financial close reporting.

For revenue reporting, use the approved finance revenue table.

&lt;span class="gu"&gt;## Important Caveats&lt;/span&gt;

Some legacy accounts may have missing segment values. Agents must not infer missing segment values without confirmation.

This table contains confidential customer information. Agents must apply row-level access controls and masking rules where applicable.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much more useful than a raw schema.&lt;/p&gt;

&lt;p&gt;The schema tells the agent what exists.&lt;/p&gt;

&lt;p&gt;The knowledge file tells the agent how to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating Open Knowledge Format from AWS NoSQL Databases
&lt;/h2&gt;

&lt;p&gt;NoSQL systems need even more care.&lt;/p&gt;

&lt;p&gt;In DynamoDB, the table name and attributes rarely tell the full story. The real design is usually in the access patterns.&lt;/p&gt;

&lt;p&gt;A DynamoDB table may store multiple entity types. It may use composite keys. It may depend on global secondary indexes. It may be optimized for specific queries and unsuitable for others.&lt;/p&gt;

&lt;p&gt;If an agent does not understand this, it can misuse the table, trigger inefficient scans, produce incomplete answers, or misunderstand the business process.&lt;/p&gt;

&lt;p&gt;For DynamoDB, the knowledge file should capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Table purpose&lt;/li&gt;
&lt;li&gt;Partition key&lt;/li&gt;
&lt;li&gt;Sort key&lt;/li&gt;
&lt;li&gt;Item types&lt;/li&gt;
&lt;li&gt;Common item shapes&lt;/li&gt;
&lt;li&gt;Global secondary indexes&lt;/li&gt;
&lt;li&gt;Local secondary indexes&lt;/li&gt;
&lt;li&gt;Streams&lt;/li&gt;
&lt;li&gt;TTL rules&lt;/li&gt;
&lt;li&gt;Primary access patterns&lt;/li&gt;
&lt;li&gt;Anti-patterns&lt;/li&gt;
&lt;li&gt;Sensitive attributes&lt;/li&gt;
&lt;li&gt;Agent permissions&lt;/li&gt;
&lt;li&gt;Operational caveats&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important part is access pattern documentation.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access pattern 1:
Get all events for an order
PK = order_id
SK = event_timestamp

Access pattern 2:
Get latest order status
PK = order_id
Sort descending by event_timestamp
Limit = 1

Access pattern 3:
Investigate failed payment events
Use event_type index if available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the agent how the table is actually meant to be used.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;commerce.dynamodb.order_events&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nosql_table&lt;/span&gt;
&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dynamodb&lt;/span&gt;
&lt;span class="na"&gt;cloud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
&lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;order_events&lt;/span&gt;
&lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;commerce&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;order-platform-team&lt;/span&gt;
&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;partition_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;order_id&lt;/span&gt;
&lt;span class="na"&gt;sort_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;event_timestamp&lt;/span&gt;
&lt;span class="na"&gt;billing_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PAY_PER_REQUEST&lt;/span&gt;
&lt;span class="na"&gt;stream_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;classification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;confidential&lt;/span&gt;
&lt;span class="na"&gt;pii&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;agent_usage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allowed_read_only&lt;/span&gt;
&lt;span class="na"&gt;approval_status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;approved&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# order_events&lt;/span&gt;

&lt;span class="gu"&gt;## Business Meaning&lt;/span&gt;

This table stores the event history of customer orders.

Each item represents an event in the order lifecycle, such as order created, payment completed, shipment initiated, shipment delivered, cancellation requested, or refund completed.

&lt;span class="gu"&gt;## Primary Access Pattern&lt;/span&gt;

Retrieve the event timeline for a specific order.

PK = order_id  
SK = event_timestamp

&lt;span class="gu"&gt;## Agent Usage Guidance&lt;/span&gt;

Agents should use this table to reconstruct order history, check operational status, and investigate order workflow issues.

Agents should not use this table as the financial source of truth for revenue, refunds, or payment settlement.

&lt;span class="gu"&gt;## Caveats&lt;/span&gt;

This table is append-only.

The latest operational event should not be treated as financial completion. For accounting status, agents must check the finance ledger.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents an agent from treating NoSQL like a normal relational model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating Knowledge from Document Databases
&lt;/h2&gt;

&lt;p&gt;For document databases such as Amazon DocumentDB or MongoDB-compatible systems, the main challenge is flexible structure and nested sensitive data.&lt;/p&gt;

&lt;p&gt;A support case document may contain customer messages. A customer profile may contain personal data. A workflow document may include internal comments, commercial terms, or escalation notes.&lt;/p&gt;

&lt;p&gt;Agents need clear rules before reading, summarizing, or exposing this type of content.&lt;/p&gt;

&lt;p&gt;For document collections, the knowledge file should capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collection purpose&lt;/li&gt;
&lt;li&gt;Common document structure&lt;/li&gt;
&lt;li&gt;Required fields&lt;/li&gt;
&lt;li&gt;Optional fields&lt;/li&gt;
&lt;li&gt;Nested arrays&lt;/li&gt;
&lt;li&gt;Indexes&lt;/li&gt;
&lt;li&gt;Query patterns&lt;/li&gt;
&lt;li&gt;Sensitive fields&lt;/li&gt;
&lt;li&gt;Masking rules&lt;/li&gt;
&lt;li&gt;Retention rules&lt;/li&gt;
&lt;li&gt;Allowed agent use cases&lt;/li&gt;
&lt;li&gt;Prohibited use cases&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;support.documentdb.customer_cases&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;document_collection&lt;/span&gt;
&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;documentdb&lt;/span&gt;
&lt;span class="na"&gt;cloud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
&lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;support_prod&lt;/span&gt;
&lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customer_cases&lt;/span&gt;
&lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customer-support&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;support-platform-team&lt;/span&gt;
&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;classification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restricted&lt;/span&gt;
&lt;span class="na"&gt;pii&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;agent_usage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allowed_with_masking&lt;/span&gt;
&lt;span class="na"&gt;approval_status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;approved&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# customer_cases&lt;/span&gt;

&lt;span class="gu"&gt;## Business Meaning&lt;/span&gt;

This collection stores customer support cases raised through web, email, account manager, and internal escalation channels.

It is used to view case history, identify recurring issues, and prepare escalation summaries.

&lt;span class="gu"&gt;## Agent Usage Guidance&lt;/span&gt;

Agents can use this collection for internal case summaries, issue classification, support briefings, and next-action recommendations.

Agents must not expose raw customer messages externally without masking sensitive information.

&lt;span class="gu"&gt;## Sensitive Fields&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; customer_email
&lt;span class="p"&gt;-&lt;/span&gt; phone_number
&lt;span class="p"&gt;-&lt;/span&gt; messages.message
&lt;span class="p"&gt;-&lt;/span&gt; account_id
&lt;span class="p"&gt;-&lt;/span&gt; internal_notes

&lt;span class="gu"&gt;## Caveats&lt;/span&gt;

Free-text messages may contain sensitive personal or commercial information. Agents should summarize the issue and intent rather than copying raw text.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not just documentation. It is a guardrail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AWS Pipeline I Would Build
&lt;/h2&gt;

&lt;p&gt;I would not make this a manual documentation exercise.&lt;/p&gt;

&lt;p&gt;That will not scale.&lt;/p&gt;

&lt;p&gt;I would build a pipeline that generates draft knowledge files automatically, then routes critical content for human review.&lt;/p&gt;

&lt;p&gt;A practical AWS-based pipeline would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS data sources
   ↓
Metadata extraction
   ↓
Profiling and classification
   ↓
Business enrichment
   ↓
OKF draft generation
   ↓
Human review and approval
   ↓
Git-based version control
   ↓
Indexing into agent knowledge retrieval
   ↓
Agent execution
   ↓
Feedback loop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extraction layer would pull from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Glue Data Catalog&lt;/li&gt;
&lt;li&gt;RDS and Aurora metadata&lt;/li&gt;
&lt;li&gt;Redshift catalog tables&lt;/li&gt;
&lt;li&gt;DynamoDB DescribeTable&lt;/li&gt;
&lt;li&gt;DynamoDB exports to S3&lt;/li&gt;
&lt;li&gt;DocumentDB collection profiling&lt;/li&gt;
&lt;li&gt;AWS tags&lt;/li&gt;
&lt;li&gt;CloudWatch metrics&lt;/li&gt;
&lt;li&gt;IAM and Lake Formation policies&lt;/li&gt;
&lt;li&gt;Existing documentation and runbooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The enrichment layer would add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business definitions&lt;/li&gt;
&lt;li&gt;Source-of-truth mapping&lt;/li&gt;
&lt;li&gt;Ownership&lt;/li&gt;
&lt;li&gt;Usage guidance&lt;/li&gt;
&lt;li&gt;Sensitivity classification&lt;/li&gt;
&lt;li&gt;Approved joins&lt;/li&gt;
&lt;li&gt;Known caveats&lt;/li&gt;
&lt;li&gt;Agent-specific instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The governance layer would make sure the knowledge is trusted before agents rely on it.&lt;/p&gt;

&lt;p&gt;This is important.&lt;/p&gt;

&lt;p&gt;If we automate everything without review, we risk creating wrong knowledge at scale.&lt;/p&gt;

&lt;p&gt;If we manually write everything, we will never scale.&lt;/p&gt;

&lt;p&gt;The practical answer is auto-generation with human governance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance Is Not Optional
&lt;/h2&gt;

&lt;p&gt;Enterprise agents need trust boundaries.&lt;/p&gt;

&lt;p&gt;Every knowledge file should have ownership and lifecycle metadata.&lt;/p&gt;

&lt;p&gt;At minimum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;finance-operations&lt;/span&gt;
&lt;span class="na"&gt;steward&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data-platform-team&lt;/span&gt;
&lt;span class="na"&gt;classification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;confidential&lt;/span&gt;
&lt;span class="na"&gt;approval_status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;approved&lt;/span&gt;
&lt;span class="na"&gt;agent_usage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allowed_internal_only&lt;/span&gt;
&lt;span class="na"&gt;last_reviewed_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-06-18"&lt;/span&gt;
&lt;span class="na"&gt;next_review_due&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-09-18"&lt;/span&gt;
&lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the framework accountability.&lt;/p&gt;

&lt;p&gt;If an agent uses a revenue definition, we should know who approved it.&lt;/p&gt;

&lt;p&gt;If an agent queries a customer table, we should know whether it contains PII.&lt;/p&gt;

&lt;p&gt;If an agent summarizes a support case, we should know what masking rules apply.&lt;/p&gt;

&lt;p&gt;Governance is not bureaucracy here.&lt;/p&gt;

&lt;p&gt;Governance is what allows agentic AI to move from demo to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Feedback Loop
&lt;/h2&gt;

&lt;p&gt;The best part of this approach is that the knowledge layer can improve over time.&lt;/p&gt;

&lt;p&gt;Agents should not only consume knowledge. They should help identify gaps in it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;A data agent discovers that two tables have conflicting definitions.&lt;/li&gt;
&lt;li&gt;A support agent identifies a recurring customer exception.&lt;/li&gt;
&lt;li&gt;A delivery agent finds that a release checklist is outdated.&lt;/li&gt;
&lt;li&gt;A FinOps agent identifies an untagged resource pattern.&lt;/li&gt;
&lt;li&gt;A sales agent finds that a metric definition is ambiguous.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But agents should not directly overwrite approved knowledge.&lt;/p&gt;

&lt;p&gt;They should propose updates.&lt;/p&gt;

&lt;p&gt;The workflow should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent identifies reusable learning
   ↓
Agent creates OKF update proposal
   ↓
Domain owner reviews
   ↓
Approved change is merged
   ↓
Knowledge index is refreshed
   ↓
Future agents use improved context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns agentic AI into a learning operating model, not just task automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I Would Start
&lt;/h2&gt;

&lt;p&gt;I would not start by documenting the whole enterprise.&lt;/p&gt;

&lt;p&gt;That sounds ambitious, but it is usually a bad execution plan.&lt;/p&gt;

&lt;p&gt;It becomes a documentation program, not an AI acceleration program.&lt;/p&gt;

&lt;p&gt;I would start with one high-value domain where correctness matters.&lt;/p&gt;

&lt;p&gt;Good candidates are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Revenue analytics&lt;/li&gt;
&lt;li&gt;Customer support&lt;/li&gt;
&lt;li&gt;Delivery governance&lt;/li&gt;
&lt;li&gt;Cloud cost optimization&lt;/li&gt;
&lt;li&gt;Sales operations&lt;/li&gt;
&lt;li&gt;Data quality monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the first MVP, I would select 10 to 20 high-value datasets and generate knowledge files around them.&lt;/p&gt;

&lt;p&gt;The MVP should include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AWS metadata extraction&lt;/li&gt;
&lt;li&gt;Draft OKF generation&lt;/li&gt;
&lt;li&gt;Manual business enrichment&lt;/li&gt;
&lt;li&gt;Data owner approval&lt;/li&gt;
&lt;li&gt;Git-based versioning&lt;/li&gt;
&lt;li&gt;Agent retrieval integration&lt;/li&gt;
&lt;li&gt;Measurement of answer quality and tool accuracy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is not to create perfect documentation.&lt;/p&gt;

&lt;p&gt;The goal is to make agents more accurate, more governed, and more useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Would Measure Success
&lt;/h2&gt;

&lt;p&gt;The wrong metric is “number of OKF files created.”&lt;/p&gt;

&lt;p&gt;That only measures documentation volume.&lt;/p&gt;

&lt;p&gt;The right metrics are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduction in incorrect SQL generation&lt;/li&gt;
&lt;li&gt;Reduction in wrong source-of-truth usage&lt;/li&gt;
&lt;li&gt;Increase in agent answer accuracy&lt;/li&gt;
&lt;li&gt;Reduction in human corrections&lt;/li&gt;
&lt;li&gt;Increase in approved knowledge reuse&lt;/li&gt;
&lt;li&gt;Reduction in deprecated table usage&lt;/li&gt;
&lt;li&gt;Improvement in data discovery time&lt;/li&gt;
&lt;li&gt;Fewer governance violations&lt;/li&gt;
&lt;li&gt;Higher user trust in agent outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The question should always be:&lt;/p&gt;

&lt;p&gt;Did the knowledge layer make the agent better?&lt;/p&gt;

&lt;p&gt;If not, we are just creating another documentation repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final View
&lt;/h2&gt;

&lt;p&gt;Google Open Knowledge Format is not interesting because markdown and YAML are new.&lt;/p&gt;

&lt;p&gt;They are not.&lt;/p&gt;

&lt;p&gt;It is interesting because it points to one of the most important problems in enterprise AI: how to make organizational knowledge usable by agents without locking it inside one platform.&lt;/p&gt;

&lt;p&gt;In an AWS environment, we already have many of the raw signals: RDS schemas, Aurora metadata, Redshift catalogs, DynamoDB keys and indexes, Glue Data Catalog, S3 exports, CloudWatch metrics, tags, IAM policies, Lake Formation rules, and existing documentation.&lt;/p&gt;

&lt;p&gt;The opportunity is to convert these scattered signals into a governed Enterprise Knowledge Layer.&lt;/p&gt;

&lt;p&gt;That layer becomes the memory and context foundation for agentic AI.&lt;/p&gt;

&lt;p&gt;My view is simple:&lt;/p&gt;

&lt;p&gt;Models give agents reasoning power.&lt;/p&gt;

&lt;p&gt;Tools give agents execution power.&lt;/p&gt;

&lt;p&gt;Knowledge gives agents enterprise judgment.&lt;/p&gt;

&lt;p&gt;Without that knowledge layer, agentic AI will remain impressive in demos and fragile in production.&lt;/p&gt;

&lt;p&gt;With it, enterprises can build agents that do not just act fast, but act correctly, safely, and in alignment with how the business actually works.&lt;/p&gt;

</description>
      <category>enterpriseai</category>
      <category>agentskills</category>
      <category>aws</category>
      <category>agentknowledgemanagement</category>
    </item>
    <item>
      <title>What happens when curiosity meets your AWS Credit?</title>
      <dc:creator>Nadtakan</dc:creator>
      <pubDate>Wed, 17 Jun 2026 15:20:02 +0000</pubDate>
      <link>https://dev.to/aws-builders/what-happens-when-curiosity-meets-your-aws-credit-3l4b</link>
      <guid>https://dev.to/aws-builders/what-happens-when-curiosity-meets-your-aws-credit-3l4b</guid>
      <description>&lt;p&gt;I recently had my AWS Community Builder membership extended for another year, and I'm excited to continue building alongside so many talented people in the community.&lt;/p&gt;

&lt;p&gt;One of the benefits of the program is AWS credits. As developers, having the freedom to experiment and build without constantly worrying about costs is incredibly valuable.&lt;/p&gt;

&lt;p&gt;When I received my credits, I started thinking about what I wanted to build next.&lt;/p&gt;

&lt;p&gt;My first thought was &lt;a href="https://kiro.dev/" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since AWS credits work with Kiro, &lt;a href="https://builder.aws.com/content/37PewQ5TbE58GHR7Ijajp0pFJP3/how-to-connect-kiro-billing-to-aws-credits" rel="noopener noreferrer"&gt;I set up IAM Identity Center&lt;/a&gt;, connected my account, and was ready to go.&lt;/p&gt;

&lt;p&gt;Done, right?&lt;/p&gt;

&lt;p&gt;Not quite.&lt;/p&gt;

&lt;p&gt;The next question immediately became:&lt;/p&gt;

&lt;p&gt;"Now what?"&lt;/p&gt;

&lt;p&gt;I knew I wanted to build something serverless, but I wasn't sure what.&lt;/p&gt;

&lt;p&gt;I started thinking about writing a monthly article summarizing AWS releases and announcements. The challenge was figuring out how to keep up with everything AWS ships.&lt;/p&gt;

&lt;p&gt;My first idea was simple:&lt;/p&gt;

&lt;p&gt;"Just check the AWS announcements page every day."&lt;/p&gt;

&lt;p&gt;Problem solved.&lt;/p&gt;

&lt;p&gt;Or so I thought.&lt;/p&gt;

&lt;p&gt;Not everyone has time to manually monitor AWS releases every day.&lt;/p&gt;

&lt;p&gt;After some research, I discovered &lt;a href="https://aws.amazon.com/new/feed/" rel="noopener noreferrer"&gt;AWS RSS feeds&lt;/a&gt; that publish release announcements as they happen.&lt;/p&gt;

&lt;p&gt;Now things got interesting.&lt;/p&gt;

&lt;p&gt;The first version of the project was straightforward:&lt;/p&gt;

&lt;p&gt;• Fetch announcements from RSS feeds&lt;br&gt;
• Store them in Amazon S3&lt;br&gt;
• Display them on a webpage&lt;/p&gt;

&lt;p&gt;But I wanted more than a feed reader.&lt;/p&gt;

&lt;p&gt;I wanted AI to help answer a question I personally care about:&lt;/p&gt;

&lt;p&gt;"How does this announcement impact my daily work or personal projects?"&lt;/p&gt;

&lt;p&gt;That changed everything.&lt;/p&gt;

&lt;p&gt;Instead of simply storing content in S3, I moved toward DynamoDB so I could store structured data alongside each release:&lt;/p&gt;

&lt;p&gt;• AI-generated summaries&lt;br&gt;
• Impact analysis&lt;br&gt;
• Categorization&lt;br&gt;
• Additional metadata and insights&lt;/p&gt;

&lt;p&gt;Over the course of a day, Kiro helped me build much of the foundation. My role shifted from writing every line of code to making architectural decisions and guiding the implementation.&lt;/p&gt;

&lt;p&gt;That said, I still jump into the code regularly. Sometimes I tweak features, optimize workflows, or dig into the implementation to understand what’s happening under the hood. I enjoy coding, and I don’t want to lose that muscle. Tools like Kiro help me move faster, but staying hands-on keeps me sharp as an engineer.&lt;/p&gt;

&lt;p&gt;One of the most interesting discussions wasn’t about code at all—it was about cost optimization.&lt;/p&gt;

&lt;p&gt;Should the system write a new record every time it processes a release?&lt;/p&gt;

&lt;p&gt;Or should it only write when a release doesn’t already exist?&lt;/p&gt;

&lt;p&gt;Small decisions like these have a big impact on cost, scalability, and operational efficiency.&lt;/p&gt;

&lt;p&gt;What started as "I have AWS credits to spend" evolved into an AI-powered AWS release analysis platform.&lt;/p&gt;

&lt;p&gt;And honestly, that's one of my favorite parts of building.&lt;/p&gt;

&lt;p&gt;You start with one idea, discover a better one, and keep iterating until the project becomes something you never originally planned.&lt;/p&gt;

&lt;p&gt;Here's the evolution of the project from V1 to V2.&lt;/p&gt;

&lt;p&gt;What started as a simple RSS-to-webpage pipeline evolved into an AI-powered AWS release analysis platform that helps me focus on the AWS services and topics I care about while also understanding how new releases might impact my work and personal projects.&lt;/p&gt;

&lt;p&gt;V1 vs. V2 Architecture&lt;/p&gt;

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

&lt;p&gt;The project is &lt;a href="https://github.com/nadtakanfuthoem/serverless-radar" rel="noopener noreferrer"&gt;open source&lt;/a&gt;, and you can also explore the &lt;a href="https://serverless-radar.nadtakanfuthoem.com" rel="noopener noreferrer"&gt;live version here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Feel free to clone it, deploy it to your own AWS account, and make it your own.&lt;/p&gt;

&lt;p&gt;Whether you want a quick way to stay on top of AWS releases or you're interested in extending the functionality, I hope it provides a useful starting point.&lt;/p&gt;

&lt;p&gt;If you have ideas, suggestions, or improvements, I'd love to hear them. Open an issue, submit a PR, or send me a message.&lt;/p&gt;

&lt;p&gt;I'm especially curious how others are using AI alongside serverless architectures.&lt;/p&gt;

&lt;p&gt;How are you using AI in your side projects today?&lt;/p&gt;

&lt;p&gt;Leave me a comment; until next time!&lt;/p&gt;

&lt;p&gt;Nad &lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>AWS Amplify builds broken after a GitHub rename? Here’s the fix the console can’t give you</title>
      <dc:creator>Suzana Melo</dc:creator>
      <pubDate>Wed, 17 Jun 2026 12:29:23 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-amplify-builds-broken-after-a-github-rename-heres-the-fix-the-console-cant-give-you-mfe</link>
      <guid>https://dev.to/aws-builders/aws-amplify-builds-broken-after-a-github-rename-heres-the-fix-the-console-cant-give-you-mfe</guid>
      <description>&lt;p&gt;My automated builds stopped working, and I had no idea why.&lt;/p&gt;

&lt;p&gt;I had been hosting and managing my blog on AWS Amplify since I launched it last year, and I couldn't be happier with it. Amplify gave me all that it promised and more. Quick to connect to GitHub, effortless to deploy, CI/CD out of the box. Every push to the repository rebuilt and redeployed the site for me. Exactly what I wanted: ship fast, sort the details later. Except "later" arrived sooner than I expected.&lt;/p&gt;

&lt;p&gt;In fact, I soon realised there are scenarios where Amplify doesn't tell you when you're starting, and I hit one of them when I decided to do what I thought was a simple thing. I updated my GitHub username. What followed was not what I expected.&lt;/p&gt;




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

&lt;p&gt;When I updated my GitHub username from &lt;code&gt;suzanamelomoraes&lt;/code&gt; to &lt;code&gt;suzanamelo-m&lt;/code&gt;, I didn't think about the obvious: all repository URLs change with it.&lt;/p&gt;

&lt;p&gt;What I also didn't know is that AWS Amplify stores a hard link to the original repository URL and cannot automatically follow username or repository renames.&lt;br&gt;&lt;br&gt;
For example, from my old repo URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/suzanamelomoraes/suzanamelo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to my new repo URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/suzanamelo-m/suzanamelo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CI/CD stopped, and no updates I made on my application were going live. I was pushing my changes to my remote repository at &lt;code&gt;https://github.com/suzanamelo-m/suzanamelo&lt;/code&gt;, while AWS Amplify was still reading from the previous URL.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Amplify doesn't tell you when you're starting out
&lt;/h2&gt;

&lt;p&gt;I jumped to AWS Amplify to try to learn how to fix the issue. I went to the AWS Amplify console to check item by item where a fix could be applied.&lt;/p&gt;

&lt;p&gt;I consulted the &lt;a href="https://docs.amplify.aws/react/build-a-backend/troubleshooting/" rel="noopener noreferrer"&gt;Amplify documentation for troubleshooting&lt;/a&gt; suggestions. I didn't find anything related.&lt;/p&gt;

&lt;p&gt;I tried to reconnect to the repository by going to the Branch settings dropdown in App settings and clicking the "Reconnect repository" button. When someone renames their GitHub username, GitHub creates a redirect from the old URL to the new one, but only for a while. I thought that Amplify would just follow it. It didn't.&lt;/p&gt;

&lt;p&gt;GitHub's redirect is a browser-level courtesy. Amplify's webhook was registered against the original URL and doesn't follow it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fikovri1x7w497ljdvk7u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fikovri1x7w497ljdvk7u.png" alt="Reconnect Repository button on the AWS Amplify Console" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The finding
&lt;/h3&gt;

&lt;p&gt;Finally, I searched the internet, asked AI for help, and found that I couldn't find the fix in the AWS Amplify console. Changing the repository URL directly in the Amplify Console is not supported; GitHub's automatic redirect doesn't fix Amplify's broken webhook. The hard link must be updated via CLI.&lt;/p&gt;




&lt;h2&gt;
  
  
  When you need to use the update-app command
&lt;/h2&gt;

&lt;p&gt;I also found out that updating information via CLI was a fix not only for my problem. More scenarios share the same root cause and the same solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 1 — GitHub username change (my case):&lt;/strong&gt; AWS Amplify cannot automatically follow username renames. When I renamed my username from &lt;code&gt;suzanamelomoraes&lt;/code&gt; to &lt;code&gt;suzanamelo-m&lt;/code&gt;, my automated builds stopped working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 2 — Renaming a repository:&lt;/strong&gt; Renaming a repository also changes the GitHub URL, and since Amplify is connected via that URL, auto-build stops working. The same CLI fix applies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 3 — Moving from a personal to an organisation account:&lt;/strong&gt; Multiple people have reported needing to move their repository from a personal GitHub profile to an organisation's profile, but there's no way to do it in the AWS Amplify UI console without creating a whole new app. This is a common scenario when a solo project grows into a team project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 4 — Switching Git providers:&lt;/strong&gt; People migrating from Bitbucket to GitHub also hit the same wall: the repository URL changes completely and Amplify breaks. The AWS CLI docs confirm the command works across providers, using &lt;code&gt;oauthToken&lt;/code&gt; for Bitbucket and CodeCommit, and &lt;code&gt;accessToken&lt;/code&gt; for GitHub (for this article, I'm covering only &lt;code&gt;accessToken&lt;/code&gt; for GitHub as this is the one I use, but I'll include helpful links in the list of resources at the bottom).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 5 — Switching between Bitbucket accounts:&lt;/strong&gt; Even switching between different Bitbucket accounts connected to Amplify requires the same &lt;code&gt;update-app&lt;/code&gt; CLI workaround.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to fix it
&lt;/h2&gt;

&lt;p&gt;Since my repository is on GitHub, I'm describing the fix I made to it. I'll leave links in the Resources to help you if you're running into issues with other web-based Git repository hosting services.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before running the fix, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The AWS Command Line Interface (AWS CLI) installed on your machine (you can check out how to do it &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Access to the AWS Console (to find your App ID)&lt;/li&gt;
&lt;li&gt;A GitHub Personal Access Token with &lt;code&gt;repo&lt;/code&gt; scope (use &lt;code&gt;oauthToken&lt;/code&gt; for Bitbucket and CodeCommit)&lt;/li&gt;
&lt;li&gt;Your Amplify App ID&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you set up your Amplify app some time ago, you may be using the older OAuth method rather than the GitHub App. Apps deployed via AWS Amplify using the older OAuth method continue to work for CI/CD, but AWS strongly recommends migrating to the GitHub App for new or updated connections.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  2. Step-by-Step Fix
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Step 1 — Generate a GitHub Personal Access Token
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to: &lt;a href="https://github.com/settings/tokens" rel="noopener noreferrer"&gt;https://github.com/settings/tokens&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Generate new token&lt;/strong&gt; → &lt;strong&gt;Generate new token (classic)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Give it a name, e.g. &lt;code&gt;Amplify Repo Update&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;repo&lt;/code&gt; scope&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Generate token&lt;/strong&gt; and copy it immediately (you won't see it again)&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Security tip:&lt;/strong&gt; Delete this token after confirming the fix is working. It is only needed for the one-time &lt;code&gt;update-app&lt;/code&gt; command.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h4&gt;
  
  
  Step 2 — Find Your Amplify App ID
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://console.aws.amazon.com/amplify/" rel="noopener noreferrer"&gt;AWS Amplify Console&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Select your app&lt;/li&gt;
&lt;li&gt;Look for the App ID — it can be found in the Console Overview and starts with &lt;code&gt;d&lt;/code&gt; followed by letters and numbers (e.g. &lt;code&gt;d2x3uf5yeo5smt&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Also note the region from your console URL, e.g. &lt;code&gt;us-east-1&lt;/code&gt;, &lt;code&gt;eu-north-1&lt;/code&gt; or &lt;code&gt;ap-southeast-2&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyl5uzjtc7sht5ix0w7sr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyl5uzjtc7sht5ix0w7sr.png" alt="AWS Amplify Console Overview showing the App ID field location" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  Step 3 — Set Up AWS CLI Credentials (&lt;code&gt;aws login&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Before you can run any AWS CLI command in your terminal, you need to get AWS credentials for local development.&lt;br&gt;
Credentials exist to authenticate your local machine or applications and authorise programmatic requests to Amazon Web Services.&lt;/p&gt;

&lt;p&gt;The AWS CLI command &lt;code&gt;aws login&lt;/code&gt; lets you start building immediately after signing up for AWS, as easily as you do in the AWS Console. Running &lt;code&gt;aws login&lt;/code&gt; in your terminal opens your default web browser to authenticate as you would via console.&lt;/p&gt;

&lt;p&gt;Once authorised in the browser, it creates short-lived, identity-based credentials for your command-line tasks, eliminating the need to use or store long-lived static access keys, which are always at risk of accidental exposure, leading to security breaches.&lt;/p&gt;

&lt;p&gt;To get authenticated:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3a. Check your CLI version&lt;/strong&gt;&lt;br&gt;
To use AWS login, you need to have AWS CLI version 2, which must be &lt;code&gt;2.32.0&lt;/code&gt; or later.&lt;br&gt;
If you just installed the AWS CLI in the prerequisites step, jump to 3b below.&lt;/p&gt;

&lt;p&gt;To check the version you have, run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If your version is older than &lt;code&gt;2.32.0&lt;/code&gt;, take a look at the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration.html" rel="noopener noreferrer"&gt;Migration guide for the AWS CLI version 2&lt;/a&gt; to learn about the differences between the versions and avoid any breakage.&lt;/p&gt;

&lt;p&gt;If there are no breaking changes, follow the Migration guide for the AWS CLI version 2. It covers both uninstalling v1 and installing v2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3b. Log in and set up your Region&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To start the login process, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it’s the first time you run aws login or you have not set a default Region, the CLI prompts you to specify the AWS Region of your choice. You will see a prompt like that in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;No AWS region has been configured. The AWS region is the geographic location of your AWS resources.
If you have used AWS before and already have resources &lt;span class="k"&gt;in &lt;/span&gt;your account, specify which region they were created &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; If you have not created resources &lt;span class="k"&gt;in &lt;/span&gt;your account before, you can pick the region closest to you: https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-regions.html.

You are able to change the region &lt;span class="k"&gt;in &lt;/span&gt;the CLI at any &lt;span class="nb"&gt;time &lt;/span&gt;with the &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"aws configure set region NEW_REGION"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
AWS Region &lt;span class="o"&gt;[&lt;/span&gt;us-east-1]:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you define your region, the CLI will open a sign-in session in your default browser, and you will see the sign-in options page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvg3tizpcat00yx7z4bzm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvg3tizpcat00yx7z4bzm.png" alt="AWS sign-in page showing authentication options after running aws login" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select “Continue with Root or IAM user” and log in to your AWS account as you would normally.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Even though you will see a button saying “Continue with Root or IAM user”, AWS best practices recommend avoiding using the Root account for those types of tasks. The recommendation is to create a user to manage your project and give it only the access it needs.&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.amazonaws.com%2Fuploads%2Farticles%2F7co5uhjsj639mfcx42i7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7co5uhjsj639mfcx42i7.png" alt="AWS IAM user sign-in form requesting account ID, IAM username, and password" width="800" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you finish the sign-in process, you will be directed to a screen confirming that your credentials have been shared successfully. You can close the browser tab and return to your terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpovpjo4hhx5buinokdll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpovpjo4hhx5buinokdll.png" alt="AWS sign-in success screen confirming that credentials have been shared with the AWS CLI" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3c. Configure a named profile&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You're now authenticated and ready to run CLI commands. If you're managing multiple projects or environments, you can also create a named profile to keep credentials organised. Choose a name that makes sense to you and your application. For this example, I’m going to call it &lt;em&gt;“blog”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To create a profile, run &lt;code&gt;aws login&lt;/code&gt; and set a name for your profile. The same command works when you want to authenticate specifically for this application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws login &lt;span class="nt"&gt;--profile&lt;/span&gt; blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3d. Update region later if needed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As you previously saw when you ran that aws login for the first time, you are able to change the region in the CLI at any time with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure &lt;span class="nb"&gt;set &lt;/span&gt;region NEW_REGION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are updating your region under your profile (step 3c)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure &lt;span class="nb"&gt;set &lt;/span&gt;region NEW_REGION &lt;span class="nt"&gt;--profile&lt;/span&gt; blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Step 4 — Update the Repository in AWS Amplify
&lt;/h4&gt;

&lt;p&gt;Run the following command, replacing the placeholders with your actual values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws amplify update-app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--app-id&lt;/span&gt; YOUR_APP_ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository&lt;/span&gt; https://github.com/NEW_USERNAME/REPO_NAME &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--access-token&lt;/span&gt; YOUR_GITHUB_TOKEN &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; YOUR_REGION &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--profile&lt;/span&gt; blog  &lt;span class="c"&gt;# omit this line if you skipped step 3c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful response will include JSON output with your app details. Here is what the structure looks like, with sensitive values redacted:&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;"app"&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;"appId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_APP_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"appArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:amplify:YOUR_REGION:YOUR_ACCOUNT_ID:apps/YOUR_APP_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-app-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"repository"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/suzanamelo-m/suzanamelo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WEB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"createTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"updateTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"environmentVariables"&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;"defaultDomain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-app-id.amplifyapp.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enableBranchAutoBuild"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enableBranchAutoDeletion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enableBasicAuth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"productionBranch"&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;"lastDeployTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-15T10:00:00.000Z"&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;"SUCCEED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"branchName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to confirm from this output: the &lt;code&gt;"repository"&lt;/code&gt; field now shows your new URL, and &lt;code&gt;"status": "SUCCEED"&lt;/code&gt; confirms your last deployment is intact.&lt;/p&gt;

&lt;p&gt;If you open the Amplify Console after running the command, you should also see the new repository URL reflected under &lt;strong&gt;App settings → Branch Settings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6ge1zu8f98bw8mqnfus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6ge1zu8f98bw8mqnfus.png" alt="AWS Amplify Branch Settings showing the correct source repository" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  Step 5 — Verify &amp;amp; Trigger a New Build
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Option A&lt;/em&gt; — Via the Amplify Console:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Amplify Console and open your app&lt;/li&gt;
&lt;li&gt;Click on your branch (e.g. &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;prod&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Run build&lt;/strong&gt; button in the top right corner&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Option B&lt;/em&gt; — Via the AWS CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws amplify start-job &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--app-id&lt;/span&gt; YOUR_APP_ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--branch-name&lt;/span&gt; main &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--job-type&lt;/span&gt; RELEASE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; YOUR_REGION &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--profile&lt;/span&gt; blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Step 6 — Delete the GitHub Token
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to: &lt;a href="https://github.com/settings/tokens" rel="noopener noreferrer"&gt;https://github.com/settings/tokens&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Find the token you created&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Delete&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;The Amplify connection will continue to work after the token is deleted. The token was only needed for the one-time &lt;code&gt;update-app&lt;/code&gt; command, not for ongoing builds.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Going further: managing multiple AWS environments
&lt;/h2&gt;

&lt;p&gt;AWS profiles are named collections of credentials and settings that let you manage multiple AWS accounts or environments from your local machine, without mixing credentials.&lt;/p&gt;

&lt;p&gt;Instead of constantly overwriting your default settings, profiles let you manage different environments, permission levels, and accounts without re-authenticating every time.&lt;/p&gt;

&lt;p&gt;You can use AWS profiles for a variety of management cases, for example, to separate your &lt;code&gt;[development]&lt;/code&gt;, &lt;code&gt;[staging]&lt;/code&gt;, and &lt;code&gt;[production]&lt;/code&gt; accounts to reduce the risk of accidental changes, handle different clients and projects in different regions, or, in my case, to create a separate environment specifically for my blog-related credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Useful Commands
&lt;/h3&gt;

&lt;p&gt;List all configured profiles:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you authenticated using &lt;code&gt;aws login&lt;/code&gt;, your credentials are temporary and won't appear here. Use &lt;code&gt;aws sts get-caller-identity --profile blog&lt;/code&gt; to verify your active session instead.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.aws/credentials
&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.aws/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run any AWS command with a specific profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws &amp;lt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;--profile&lt;/span&gt; blog &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a profile as the default for a terminal session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify which identity is active:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sts get-caller-identity &lt;span class="nt"&gt;--profile&lt;/span&gt; blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update a profile's region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure &lt;span class="nb"&gt;set &lt;/span&gt;region NEW_REGION &lt;span class="nt"&gt;--profile&lt;/span&gt; blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete a profile — manually remove the relevant block from both files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano ~/.aws/credentials   &lt;span class="c"&gt;# Remove [blog] block&lt;/span&gt;
nano ~/.aws/config        &lt;span class="c"&gt;# Remove [profile blog] block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;AWS Amplify hosting is an incredible tool, especially when you want to benefit from the AWS infrastructure (CloudFront, Route 53, IAM). In my case, I already had my custom domain and Route 53, which made the setup even smoother.&lt;/p&gt;

&lt;p&gt;It's also very handy when you want to avoid third-party services and rely on AWS native tools for authentication and databases.&lt;/p&gt;

&lt;p&gt;AWS Amplify focuses on enhancing the user experience and making it even easier to manage your applications through its UI console. But not every issue can be sorted in the UI console. We just covered one issue that Amplify can't protect you from, but now you know exactly how to handle it when it happens.&lt;/p&gt;

&lt;p&gt;This fix taught me more than I expected. Not just about how Amplify manages repository connections, but about how the AWS CLI has evolved. &lt;code&gt;aws login&lt;/code&gt; is a genuinely useful addition, and I want to write more about it properly.&lt;br&gt;
If you've hit a different Amplify wall or you're curious about more CLI basics, let me know. Your comments or questions might become the next article. 🤗&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New to AWS Amplify hosting? Start here&lt;/strong&gt; — &lt;a href="https://suzanamelo.com/posts/amplify-blog/" rel="noopener noreferrer"&gt;From Procrastination to Publishing: How AWS Amplify Helped Me Finally Start Blogging&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CLI installation guide:&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CLI &lt;code&gt;update-app&lt;/code&gt; full reference:&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/amplify/update-app.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cli/latest/reference/amplify/update-app.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Personal Access Tokens (classic):&lt;/strong&gt; &lt;a href="https://github.com/settings/tokens" rel="noopener noreferrer"&gt;https://github.com/settings/tokens&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS login:&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sign-in.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sign-in.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified developer access to AWS with ‘aws login’:&lt;/strong&gt; &lt;a href="https://aws.amazon.com/blogs/security/simplified-developer-access-to-aws-with-aws-login/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/security/simplified-developer-access-to-aws-with-aws-login/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Amplify Troubleshooting:&lt;/strong&gt; &lt;a href="https://docs.amplify.aws/react/build-a-backend/troubleshooting/" rel="noopener noreferrer"&gt;https://docs.amplify.aws/react/build-a-backend/troubleshooting/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;start-job&lt;/code&gt; CLI reference:&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/amplify/start-job.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cli/latest/reference/amplify/start-job.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrating from OAuth to the Amplify GitHub App (for users on older setups):&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/amplify/latest/userguide/setting-up-GitHub-access.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/amplify/latest/userguide/setting-up-GitHub-access.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bitbucket — changing accounts:&lt;/strong&gt; &lt;a href="https://repost.aws/questions/QU3ibFSag6QdmlrQA55tr4-g/amplify-bitbucket-sign-to-different-account" rel="noopener noreferrer"&gt;https://repost.aws/questions/QU3ibFSag6QdmlrQA55tr4-g/amplify-bitbucket-sign-to-different-account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrating from Bitbucket to GitHub:&lt;/strong&gt; &lt;a href="https://www.isme.es/2023/09/10/migrate-blog-source-from-bitbucket-to-github.html" rel="noopener noreferrer"&gt;https://www.isme.es/2023/09/10/migrate-blog-source-from-bitbucket-to-github.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on my blog, suzanamelo.com, where I write about cloud, AWS, AI, and what it actually takes to change careers in tech.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>awsamplify</category>
      <category>awscli</category>
      <category>github</category>
    </item>
    <item>
      <title>AWS Blocks: A New Way to Build on AWS</title>
      <dc:creator>Rishi</dc:creator>
      <pubDate>Wed, 17 Jun 2026 10:44:42 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-blocks-a-new-way-to-build-on-aws-3a6k</link>
      <guid>https://dev.to/aws-builders/aws-blocks-a-new-way-to-build-on-aws-3a6k</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlg727v5emb6a2xxonlf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlg727v5emb6a2xxonlf.png" alt="AWS Blocks" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just explored AWS Blocks today, and the idea is quite interesting.&lt;/p&gt;

&lt;p&gt;If you've ever built a full-stack application on AWS, you know how quickly things get out of hand.&lt;/p&gt;

&lt;p&gt;A simple Todo app can easily turn into creating DynamoDB tables &amp;gt;&amp;gt; wiring Lambda functions &amp;gt;&amp;gt; configuring API Gateway routes &amp;gt;&amp;gt; setting up Cognito &amp;gt;&amp;gt; debugging IAM permissions &amp;gt;&amp;gt; LocalStack for local testing&lt;/p&gt;

&lt;p&gt;Before you've even written much business logic, you've already wired together half a dozen AWS services.&lt;/p&gt;

&lt;p&gt;AWS Blocks aims to solve this with a concept called Infrastructure From Code (IFC). No separate infrastructure files. Infrastructure is inferred from the code you write.&lt;/p&gt;

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

&lt;p&gt;Instead of writing application code in one place and infrastructure definitions in CDK/Terraform somewhere else, you work with higher-level building blocks such as:&lt;/p&gt;

&lt;p&gt;• KVStore&lt;br&gt;
• DistributedTable&lt;br&gt;
• Auth&lt;br&gt;
• Realtime&lt;br&gt;
• FileBucket&lt;br&gt;
• Agent&lt;/p&gt;

&lt;p&gt;During local development, these blocks are simulated locally.&lt;/p&gt;

&lt;p&gt;When deployed, the same blocks are translated into actual AWS resources such as DynamoDB, Cognito, S3, API Gateway, Bedrock, and more.&lt;/p&gt;

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

&lt;p&gt;Have you tried AWS Blocks yet? What are your thoughts on Infrastructure From Code?&lt;/p&gt;

</description>
      <category>aws</category>
      <category>infrastructureascode</category>
    </item>
    <item>
      <title>Building a Serverless, Multi-Backend Web Search Service for AI Agents on AWS</title>
      <dc:creator>Matheus das Mercês</dc:creator>
      <pubDate>Wed, 17 Jun 2026 07:46:59 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-a-serverless-multi-backend-web-search-service-for-ai-agents-on-aws-1219</link>
      <guid>https://dev.to/aws-builders/building-a-serverless-multi-backend-web-search-service-for-ai-agents-on-aws-1219</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;From small ones to more complex agentic architectures, &lt;strong&gt;agents are everywhere&lt;/strong&gt;. As more teams build &lt;strong&gt;AI-powered&lt;/strong&gt; solutions, web search is becoming a fundamental capability: access current information, verify facts, and gather external context.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk through how we built a serverless, multi-backend web search service at PostNL, creating a foundation that can evolve from a single search backend to a &lt;strong&gt;centralized&lt;/strong&gt;, provider-agnostic service on AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why web search as a shared capability
&lt;/h2&gt;

&lt;p&gt;While integrating a search provider is relatively straightforward for a single app, the challenge becomes more interesting when multiple teams start building agents that need to &lt;strong&gt;access information beyond what the LLM already knows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If teams have the freedom to implement their web search services independently, that can lead to &lt;strong&gt;fragmentation&lt;/strong&gt;. Different providers, different APIs, some of them relying on public web search APIs, different cost models, etc.&lt;/p&gt;

&lt;p&gt;At that point, web search is no longer just an application feature, but a &lt;strong&gt;shared capability&lt;/strong&gt;. Providing a centralized web search service enables teams to consume a &lt;strong&gt;consistent interface&lt;/strong&gt; while allowing platform owners to manage costs, operations, and future provider choices in a single place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing a centralized web search service
&lt;/h2&gt;

&lt;p&gt;At PostNL, the &lt;em&gt;AI Center of Excellence (AI CoE)&lt;/em&gt; supports teams across the organization in adopting and scaling AI solutions. As more teams began experimenting with AI agents, the need for a web search service became clear.&lt;/p&gt;

&lt;p&gt;Rather than recommending a specific provider, we decided to focus on a &lt;strong&gt;platform-oriented solution&lt;/strong&gt;. Our goal was to provide a single web search capability that teams could easily consume, while keeping the underlying search implementation flexible and &lt;strong&gt;replaceable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;From the beginning, we defined a few key design principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single interface for all consumers;&lt;/li&gt;
&lt;li&gt;Support for multiple search backends;&lt;/li&gt;
&lt;li&gt;Low operational overhead;&lt;/li&gt;
&lt;li&gt;Cost-efficient operation;&lt;/li&gt;
&lt;li&gt;The ability to evolve without impacting consumers;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These principles led us to an architecture centered around a &lt;strong&gt;lightweight routing layer&lt;/strong&gt; that sits between AI applications and the underlying web search providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the backend doesn't matter to consumers
&lt;/h3&gt;

&lt;p&gt;Perhaps the most important aspect of the design is that consumers &lt;strong&gt;never interact directly with the underlying web search providers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because all requests flow through the routing layer, the backend can evolve independently of the applications using the service. Today, the router forwards requests to the primary search backend; tomorrow it could route traffic to additional providers, apply failover policies, or make routing decisions based on cost, geography, or freshness requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial architecture
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The service is exposed through a private API Gateway, ensuring that only authorized consumers within the network can access it through a VPC endpoint. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Requests are routed to a lightweight &lt;strong&gt;Lambda-based router&lt;/strong&gt;, which acts as the abstraction layer between clients and the underlying search backend. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The initial backend is a self-hosted &lt;a href="https://github.com/searxng/searxng" rel="noopener noreferrer"&gt;SearXNG&lt;/a&gt; deployment running on &lt;strong&gt;ECS Fargate behind an internal Application Load Balancer&lt;/strong&gt;, providing a scalable and centrally managed web search capability while keeping the architecture flexible for future providers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building the Router
&lt;/h2&gt;

&lt;p&gt;The Router is the &lt;strong&gt;central component&lt;/strong&gt; of the system. It enables future backend changes without impacting consumers.&lt;/p&gt;

&lt;p&gt;The overall flow is fixed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive requests from API Gateway&lt;/li&gt;
&lt;li&gt;Forward requests to the primary backend&lt;/li&gt;
&lt;li&gt;Return responses from the backend&lt;/li&gt;
&lt;li&gt;Perform basic request validation if required&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Router follows a &lt;strong&gt;hexagonal architecture&lt;/strong&gt;, also known as ports and adapters. The application core defines the web search behavior and interfaces, while infrastructure concerns such as API Gateway events, HTTP clients, and provider-specific integrations are implemented as adapters.&lt;/p&gt;

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

&lt;p&gt;This keeps the core logic independent from any specific backend. Today, the Router forwards requests to SearXNG. In the future, additional &lt;strong&gt;adapters&lt;/strong&gt; can be introduced for other providers without changing the public API or affecting consumers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime choice
&lt;/h3&gt;

&lt;p&gt;For the runtime, we selected &lt;strong&gt;Go&lt;/strong&gt; because the Router is mostly a lightweight HTTP orchestration component. It does not perform heavy computation; it validates requests, applies routing logic, calls a backend, and returns a response.&lt;/p&gt;

&lt;p&gt;Go is a good fit for this type of workload because it provides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fast startup times&lt;/li&gt;
&lt;li&gt;Low memory usage&lt;/li&gt;
&lt;li&gt;Strong HTTP support in the standard library&lt;/li&gt;
&lt;li&gt;Simple concurrency primitives&lt;/li&gt;
&lt;li&gt;Easy deployment as a small Lambda binary&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The reasoning behind Lambda
&lt;/h3&gt;

&lt;p&gt;AWS Lambda was selected to keep the operational footprint small. The Router does not need long-running infrastructure, local state, or complex runtime management. Keeping it &lt;strong&gt;stateless&lt;/strong&gt; allows Lambda to scale horizontally as request volume changes.&lt;/p&gt;

&lt;p&gt;At first glance, introducing a Lambda-based router in the request path may raise &lt;strong&gt;scalability concerns&lt;/strong&gt;, especially for a service that could be consumed by multiple applications. However, the router intentionally remains lightweight, performing only request validation, routing, and protocol translation. By keeping the component stateless and focused on orchestration rather than search execution, Lambda provides automatic scaling with minimal operational overhead while maintaining low latency.&lt;/p&gt;

&lt;p&gt;This &lt;strong&gt;separation&lt;/strong&gt; keeps the Router simple today while allowing it to evolve later with routing policies, fallback logic, circuit breakers, and additional backend adapters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying an open-source web search backend
&lt;/h2&gt;

&lt;p&gt;After establishing the Router layer, the next decision was selecting the initial search backend.&lt;/p&gt;

&lt;p&gt;Rather than immediately integrating &lt;strong&gt;commercial&lt;/strong&gt; web search providers, we wanted a solution that was self-hosted, easy to experiment with, and replaceable in the future.&lt;/p&gt;

&lt;p&gt;For the MVP, we selected &lt;a href="https://github.com/searxng/searxng" rel="noopener noreferrer"&gt;SearXNG&lt;/a&gt;, an open-source metasearch engine.&lt;/p&gt;

&lt;p&gt;Why SearXNG?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;SearXNG&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Self-hosted&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregates multiple engines&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Easy container deployment&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vendor independence&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;SearXNG runs as a containerized service on ECS Fargate and is exposed internally through an Application Load Balancer. For this workload, ECS Fargate offered the simplest path to a production-ready deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consuming the service
&lt;/h2&gt;

&lt;p&gt;As the service is exposed through a private API Gateway, teams at PostNL can consume the service through a &lt;strong&gt;standard HTTP interface&lt;/strong&gt;, where authentication, quotas, and usage tracking are handled centrally. This provides clear visibility into adoption and usage patterns while ensuring that the platform can be governed consistently as more teams onboard.&lt;/p&gt;

&lt;p&gt;The platform is intentionally focused on &lt;strong&gt;search retrieval&lt;/strong&gt;. Responses consist of search results returned by the backend and are not interpreted or summarized by an LLM. Any reasoning or answer generation remains the responsibility of the consuming application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing for a multi-backend future
&lt;/h2&gt;

&lt;p&gt;Although the initial implementation relies on a single backend, the service was designed from the beginning to support multiple search providers. The Router follows a hexagonal architecture, where the application core remains independent from backend-specific implementations through well-defined ports and adapters.&lt;/p&gt;

&lt;p&gt;Potential future enhancements include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Additional search providers&lt;/li&gt;
&lt;li&gt;Provider failover and fallback mechanisms&lt;/li&gt;
&lt;li&gt;Health-based routing&lt;/li&gt;
&lt;li&gt;Query-specific routing policies&lt;/li&gt;
&lt;li&gt;Configurable provider selection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By investing in the abstraction layer early, the service remains flexible and can evolve incrementally as requirements change, while continuing to provide a stable interface for consumers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As AI agents become more common, capabilities such as web search are increasingly moving from individual applications into shared capabilities. By centralizing web search behind a consistent interface, teams can focus on building solutions rather than integrating and operating search providers.&lt;/p&gt;

&lt;p&gt;The specific technologies and providers will likely evolve over time, but the underlying principle remains the same: &lt;strong&gt;common capabilities&lt;/strong&gt; are often most valuable when they are provided once and consumed by many.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>ai</category>
      <category>aws</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
