<?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: will peixoto</title>
    <description>The latest articles on DEV Community by will peixoto (@willpeixoto).</description>
    <link>https://dev.to/willpeixoto</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F500557%2F266eb3b8-34d7-4516-b8ba-6f0987c83870.jpeg</url>
      <title>DEV Community: will peixoto</title>
      <link>https://dev.to/willpeixoto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/willpeixoto"/>
    <language>en</language>
    <item>
      <title>Lambda MicroVMs: shell access, lifecycle hooks and the best practices that matter</title>
      <dc:creator>will peixoto</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/lambda-microvms-shell-access-lifecycle-hooks-and-the-best-practices-that-matter-2a2l</link>
      <guid>https://dev.to/aws-builders/lambda-microvms-shell-access-lifecycle-hooks-and-the-best-practices-that-matter-2a2l</guid>
      <description>&lt;p&gt;Let me tell you about a scene anyone from the infra side will recognize right away. When Lambda showed up back in 2014 and started spreading, infrastructure folks would come over and ask: "OK, but how do I connect to the Lambda to check it's running? Run a &lt;code&gt;ps&lt;/code&gt;, a &lt;code&gt;tail&lt;/code&gt;, look at the app from the inside?". And the answer was always the same: you don't. A function is a black box, you look at CloudWatch and pray.&lt;/p&gt;

&lt;p&gt;Well, with Lambda MicroVMs you can now attach a real shell. =D In this post I show how to open that shell, how to implement in Node the lifecycle hooks that keep the MicroVM healthy, and the official best practices for performance, security and cost.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Validity note:&lt;/strong&gt; written in October 2026, checking every command, parameter and limit against the official Lambda MicroVMs docs. The service is new and the docs are still moving (I even found two spellings of the same log group on different pages), so I'll keep this post up to date. If something doesn't match what you see in your console, let me know in the comments. Official docs: &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/microvms-best-practices.html" rel="noopener noreferrer"&gt;Best practices for Lambda MicroVMs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Connecting a shell to a Lambda MicroVM
&lt;/h2&gt;

&lt;p&gt;There are two ways in. In the &lt;strong&gt;console&lt;/strong&gt;, you select the MicroVM and click Connect. From the &lt;strong&gt;CLI&lt;/strong&gt;, you generate a shell token and connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda-microvms create-microvm-shell-auth-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--microvm-identifier&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;--expiration-in-minutes&lt;/span&gt; 30

&lt;span class="c"&gt;# once inside, list the tasks and exec into one:&lt;/span&gt;
ctr task &lt;span class="nb"&gt;ls
&lt;/span&gt;ctr task &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;--exec-id&lt;/span&gt; shell &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /bin/sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shell is opt-in. The MicroVM has to be launched with the &lt;code&gt;SHELL_INGRESS&lt;/code&gt; network connector, and if it came up without it, &lt;code&gt;create-microvm-shell-auth-token&lt;/code&gt; returns a &lt;code&gt;ValidationException&lt;/code&gt;. Connectors are set on &lt;code&gt;run-microvm&lt;/code&gt; and don't change while the MicroVM is running, so you decide at launch time which MicroVMs accept that way in.&lt;/p&gt;

&lt;p&gt;For the rest of your debugging, three fields help a ton: &lt;code&gt;terminationMessage&lt;/code&gt; on &lt;code&gt;get-microvm&lt;/code&gt; (for a MicroVM that died), the build logs in CloudWatch under &lt;code&gt;/aws/lambda-microvms/&amp;lt;image-name&amp;gt;&lt;/code&gt;, and &lt;code&gt;StateReason&lt;/code&gt; when a network connector lands in &lt;code&gt;FAILED&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lifecycle hooks: what keeps the MicroVM standing
&lt;/h2&gt;

&lt;p&gt;Remember the &lt;a href="https://dev.to/aws-builders/aws-lambda-microvms-run-untrusted-code-with-vm-level-isolation-no-infra-to-manage-3i3"&gt;snapshot caveat&lt;/a&gt; (the MicroVM boots from a frozen state, so anything that needs to be fresh can't be frozen)? AWS handles it with lifecycle hooks: four HTTP endpoints your app implements, which the service calls with a &lt;code&gt;POST&lt;/code&gt; at the right moment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/run&lt;/code&gt;: post-initialization. This is where you generate UUIDs, secrets and random values, never at image build time, or every MicroVM is born with the same "random". Use a CSPRNG. &lt;code&gt;/run&lt;/code&gt; also receives in its body the &lt;code&gt;runHookPayload&lt;/code&gt; (per-MicroVM config, such as a tenant ID, in a string of up to 16 KB that you pass with &lt;code&gt;--run-hook-payload&lt;/code&gt; on &lt;code&gt;run-microvm&lt;/code&gt;) and the &lt;code&gt;microvmId&lt;/code&gt;, which Lambda injects.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/suspend&lt;/code&gt;: graceful cleanup before suspending (flush pending writes, close connections, release resources).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/resume&lt;/code&gt;: revalidates connections and cached state. AWS SDK connections usually recover on their own; custom connections may not, so check them here. The MicroVM stays &lt;code&gt;SUSPENDED&lt;/code&gt; while this hook runs and only flips to &lt;code&gt;RUNNING&lt;/code&gt; once it returns 200.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/terminate&lt;/code&gt;: flushes data before shutting down. It only runs on final termination; before a suspend, &lt;code&gt;/suspend&lt;/code&gt; is the one doing the work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hooks arrive on the path &lt;code&gt;/aws/lambda-microvms/runtime/v1/&amp;lt;hook&amp;gt;&lt;/code&gt;, on a port you configure, and external traffic only starts flowing after &lt;code&gt;/run&lt;/code&gt; returns &lt;strong&gt;200&lt;/strong&gt;. If &lt;code&gt;/run&lt;/code&gt; fails or times out, the MicroVM can go straight to &lt;code&gt;TERMINATING&lt;/code&gt; without ever reaching &lt;code&gt;RUNNING&lt;/code&gt;, so handle errors and timeouts inside the hook.&lt;/p&gt;

&lt;p&gt;About the port: the docs don't tell you to split hooks and app, and in the example below both share 8080, the default port for external traffic. Make that call on purpose, looking at which ports the access token opens through &lt;code&gt;allowedPorts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are also two hooks for the image build phase. &lt;code&gt;/ready&lt;/code&gt; returns 200 once the app has finished starting, and that's when the service takes the disk and memory snapshot (while you're not ready, return 503 right away and Lambda keeps retrying until the timeout). &lt;code&gt;/validate&lt;/code&gt; is called after the build, on a fresh MicroVM created from the image: that's where you run test payloads that exercise your code paths, and Lambda tracks which snapshot regions were touched so it can prefetch them and cut startup latency. So &lt;code&gt;/ready&lt;/code&gt; makes sure the snapshot captures an app that's ready to serve, and &lt;code&gt;/validate&lt;/code&gt; is the one that speeds up boot.&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%2Fubri7evqa4dq60ovc2a9.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fubri7evqa4dq60ovc2a9.png" alt="Sequence diagram of a MicroVM lifecycle: the /ready hook unlocks the snapshot, /validate runs on a MicroVM created from the image, then /run lets traffic in and the suspend, resume and terminate cycle happens at runtime" width="800" height="988"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our Node HTTP server it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http&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;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sessionSecret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// generated in /run, NEVER at image build time&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&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="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&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;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;HOOK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/aws/lambda-microvms/runtime/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;HOOK&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/run`&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="c1"&gt;// randomness and init live here; return 200 before traffic comes in&lt;/span&gt;
      &lt;span class="nx"&gt;sessionSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ready&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="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;HOOK&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/suspend`&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="c1"&gt;// cleanup before freezing&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cleanedUp&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="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;HOOK&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/resume`&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="c1"&gt;// revalidate connections/state on wake-up&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;revalidated&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="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;HOOK&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/terminate`&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;// flush data on shutdown&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;flushed&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="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;// ... your actual application&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;world&lt;/span&gt;&lt;span class="dl"&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;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the app stops answering after a resume, suspect number one is a missing &lt;code&gt;/resume&lt;/code&gt; (or the app not binding to the right port, like 8080, after it wakes up).&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick proof: the snapshot freezes your "random"
&lt;/h2&gt;

&lt;p&gt;That warning about generating randomness in &lt;code&gt;/run&lt;/code&gt; sounds like a detail until you measure it. I put together a small demo that runs &lt;strong&gt;locally, on my machine&lt;/strong&gt;: no AWS, no cost, just the Node stdlib. It simulates the build producing the snapshot and boots two "MicroVMs" from it. The code ships with the demo post, the series finale.&lt;/p&gt;

&lt;p&gt;The output, captured for real:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;== 3. antes do /run: o servico nao entrega trafego ==
amostra-A  GET /  -&amp;gt; HTTP 503

== 5. depois do /run 200: trafego liberado ==
amostra-A  GET /  -&amp;gt; HTTP 200

== 6. veredito ==
token gerado no BUILD  A vs B: IGUAL     &amp;lt;- colide entre analises
   A: 6fcf9c71-2024-4901-acfb-2593ed5a2fbb
   B: 6fcf9c71-2024-4901-acfb-2593ed5a2fbb
token gerado no /run   A vs B: diferente &amp;lt;- ok
   A: 2011db3c-3120-4b9a-b89d-caa48b034c8f
   B: 496f5bfc-7c7f-4cd7-b39f-2ea3c9b8c281
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The demo prints in Portuguese: "antes do /run" is before &lt;code&gt;/run&lt;/code&gt;, "IGUAL" is identical, "diferente" is different.) You can see both things in the same output: the token baked at build time comes out &lt;strong&gt;identical&lt;/strong&gt; in both, and traffic only gets a 200 after &lt;code&gt;/run&lt;/code&gt; responds.&lt;/p&gt;

&lt;p&gt;Heads up: this demo &lt;strong&gt;does not run on Lambda MicroVMs&lt;/strong&gt;. It exercises the hooks contract locally, with a &lt;code&gt;snapshot.json&lt;/code&gt; standing in for the real freeze. It's there to show the behavior. Proof on the real service only comes from running on a MicroVM in AWS, and that's for the demo post, the series finale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official best practices, by pillar
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Performance.&lt;/strong&gt; Pick the smallest MicroVM that covers your CPU, memory and bandwidth (bandwidth scales with configured memory). Keep the snapshot lean: strip build dependencies and temp files before the image is captured, which cuts run and resume time. And for latency-sensitive workloads, keep your most-used environments &lt;strong&gt;suspended&lt;/strong&gt; so they resume fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security.&lt;/strong&gt; Issue short-lived tokens (15 to 30 min) and have refresh logic on the client before they expire. Scope the token to the ports the app actually uses, with the &lt;code&gt;allowedPorts&lt;/code&gt; parameter. Sensitive traffic (database, internal API) goes through a VPC egress connector, which goes through your security groups and network ACLs. And apply least-privilege: split the &lt;strong&gt;build&lt;/strong&gt; role (image creation) from the &lt;strong&gt;execution&lt;/strong&gt; role (runtime), each with the bare minimum. Handy detail: &lt;code&gt;/ready&lt;/code&gt; and &lt;code&gt;/validate&lt;/code&gt; run under the build role, and the runtime hooks run under the execution role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost.&lt;/strong&gt; Set &lt;code&gt;maxIdleDurationSeconds&lt;/code&gt; in the idle policy to suspend idle MicroVMs (a suspended MicroVM pays no compute, only snapshot storage). Put a safety ceiling with the maximum duration (&lt;code&gt;--maximum-duration-in-seconds&lt;/code&gt; in the CLI, up to 8 hours, counting running plus suspended time) so a forgotten instance doesn't surprise you on the bill. Use &lt;code&gt;suspendedDurationSeconds&lt;/code&gt; to automatically terminate a MicroVM that sat parked for too long. Tag your images for cost allocation in Cost Explorer, and delete image versions you no longer use, because they cost storage too.&lt;/p&gt;

&lt;p&gt;Before you decide how much isolation and availability your use case needs, it's worth looking at the price of each step up. I ran those numbers in &lt;a href="https://dev.to/aws-builders/the-price-of-high-availability-resilience-in-architecture-decisions-are-strategic-not-just-4hhf"&gt;High availability has a price: resilience is a decision, not a stack&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser apps: a proxy in front of the MicroVM
&lt;/h2&gt;

&lt;p&gt;An app that runs in the &lt;strong&gt;browser&lt;/strong&gt; (a VS Code server, a web IDE) runs into a rule in the docs: every request to the MicroVM endpoint needs the JWE token in the &lt;code&gt;X-aws-proxy-auth&lt;/code&gt; header, and there's no unauthenticated option. When the user opens a URL, clicks a link or loads an iframe, the browser won't let you set that header. For a WebSocket opened by your own JavaScript, the docs let you pass token and port as subprotocols (&lt;code&gt;lambda-microvms.authentication.&amp;lt;token&amp;gt;&lt;/code&gt;), but that doesn't cover regular navigation.&lt;/p&gt;

&lt;p&gt;The architecture answer is a reverse proxy of your own in front (the Gateway pattern): a component that takes the browser request already authenticated the way your app does it, fetches or mints the MicroVM token, and injects &lt;code&gt;X-aws-proxy-auth&lt;/code&gt; before forwarding. It also needs a &lt;strong&gt;user → MicroVM map&lt;/strong&gt;, because each MicroVM has its own endpoint and there's no load balancing across them. With that map, when the user closes the tab and comes back later, you know which instance to route to. The orchestrator that already calls &lt;code&gt;run-microvm&lt;/code&gt; is a natural place to keep that map.&lt;/p&gt;

&lt;p&gt;If all you need is a custom domain, AWS published a pattern with an Application Load Balancer rewriting the &lt;code&gt;Host&lt;/code&gt; header, but there the client still has to send the token. It doesn't replace the auth proxy.&lt;/p&gt;

&lt;p&gt;If what's orchestrating these MicroVMs is an AI agent operating your account, the other side of the coin is permission control, which I covered in &lt;a href="https://dev.to/aws-builders/aws-mcp-server-which-one-to-use-when-and-how-to-set-it-up-now-that-aws-recommends-just-one-3b7"&gt;AWS MCP Server: which one to use, when, and how to set it up&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The errors you'll see (and what to do)
&lt;/h2&gt;

&lt;p&gt;From the troubleshooting table in the docs, these are the ones I'd keep at hand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;502 Bad Gateway:&lt;/strong&gt; the app crashed or isn't listening. Check the &lt;code&gt;EXPOSE&lt;/code&gt; and &lt;code&gt;CMD&lt;/code&gt; in your Dockerfile and the runtime logs. With auto-resume on, it can also be a MicroVM that failed to resume (check its state with &lt;code&gt;get-microvm&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;429 Too Many Requests:&lt;/strong&gt; you went over the rate (account or MicroVM). Retry with exponential backoff and jitter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dropping connections:&lt;/strong&gt; idle timeout. Implement keepalive (ping/pong) or raise &lt;code&gt;maxIdleDurationSeconds&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build failed (&lt;code&gt;CREATION_FAILED&lt;/code&gt;):&lt;/strong&gt; look at the logs under &lt;code&gt;/aws/lambda-microvms/&amp;lt;image-name&amp;gt;&lt;/code&gt;, check your Dockerfile syntax and the build role's S3 permissions. Run &lt;code&gt;docker build&lt;/code&gt; locally to reproduce.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And for basic monitoring: when a MicroVM terminates out of nowhere, &lt;code&gt;get-microvm&lt;/code&gt; returns a &lt;code&gt;stateReason&lt;/code&gt; with the cause. That's the first place to look.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you take away
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You can shell into Lambda&lt;/strong&gt; now, via the console or &lt;code&gt;create-microvm-shell-auth-token&lt;/code&gt;, but only if the MicroVM was launched with the &lt;code&gt;SHELL_INGRESS&lt;/code&gt; connector.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;lifecycle hooks&lt;/strong&gt; (&lt;code&gt;/run&lt;/code&gt;, &lt;code&gt;/suspend&lt;/code&gt;, &lt;code&gt;/resume&lt;/code&gt;, &lt;code&gt;/terminate&lt;/code&gt;) are what keep the MicroVM healthy. Generate randomness in &lt;code&gt;/run&lt;/code&gt;, revalidate connections in &lt;code&gt;/resume&lt;/code&gt;, and use &lt;code&gt;/validate&lt;/code&gt; to shorten boot time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; short-lived tokens with refresh, per-port scope, VPC egress, and a build role separate from the execution role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; suspend when idle, cap the duration, terminate what sat parked, and clean up image versions you don't use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/microvms-best-practices.html" rel="noopener noreferrer"&gt;Best practices for Lambda MicroVMs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/microvms-how-it-works.html" rel="noopener noreferrer"&gt;Core concepts&lt;/a&gt; (states and transitions)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/microvms-launching.html" rel="noopener noreferrer"&gt;Running and using MicroVMs&lt;/a&gt; (lifecycle hooks, run hook payload, suspend/resume)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/microvms-images.html" rel="noopener noreferrer"&gt;MicroVM images&lt;/a&gt; (build hooks &lt;code&gt;/ready&lt;/code&gt; and &lt;code&gt;/validate&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/microvms-security.html" rel="noopener noreferrer"&gt;Security and permissions&lt;/a&gt; (IAM roles, tokens, shell)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/microvms-networking.html" rel="noopener noreferrer"&gt;Networking&lt;/a&gt; (authentication, ports, VPC egress)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/microvms-troubleshooting.html" rel="noopener noreferrer"&gt;Troubleshooting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/compute/adding-custom-domains-to-aws-lambda-microvms-with-application-load-balancer/" rel="noopener noreferrer"&gt;Adding custom domains to AWS Lambda MicroVMs with Application Load Balancer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://willpeixoto.dev/lambda-microvms-shell-lifecycle-hooks-best-practices" rel="noopener noreferrer"&gt;willpeixoto.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Running a MicroVM use case already? Tell me which hook saved you (or burned you for not having it). And if you're from the infra side and always wanted to shell into a Lambda, now's your moment. If this post helped, drop a like and share it around. Thanks a lot!&lt;/p&gt;

&lt;p&gt;BUILD. SCALE. REPEAT. =D&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>lambda</category>
      <category>security</category>
    </item>
    <item>
      <title>Lambda Web Functions: when to switch from Event Functions, and what to do with no South America region</title>
      <dc:creator>will peixoto</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/lambda-web-functions-when-to-switch-from-event-functions-and-what-to-do-with-no-south-america-4198</link>
      <guid>https://dev.to/aws-builders/lambda-web-functions-when-to-switch-from-event-functions-and-what-to-do-with-no-south-america-4198</guid>
      <description>&lt;p&gt;Lambda Web Functions runs your Node.js HTTP server directly on Lambda, with many requests per environment and billing for active CPU. That changes the math for SSR and for APIs that spend the day waiting on a database. The catch: AWS launched it in five regions, and none of them is in South America.&lt;/p&gt;

&lt;p&gt;If you've ever put Express on Lambda, you know the ritual: Lambda Web Adapter or &lt;code&gt;serverless-http&lt;/code&gt; translating events into &lt;code&gt;req/res&lt;/code&gt;, and a bill that charges for every millisecond spent sitting there waiting for Postgres to answer. That's what AWS changed on October 12. And if your users are in Brazil (mine are, I'm writing from São Paulo), the launch comes with a region decision baked in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Validity note:&lt;/strong&gt; written in October 2026, six days after the announcement, checking limits, pricing and IaC against the official docs. Freshly launched services pick up regions and runtimes fast, and I'll update this post when that happens (the first update will be a South America region showing up). If something doesn't match what you're seeing, tell me in the comments. The living reference is the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/web-functions.html" rel="noopener noreferrer"&gt;official Lambda Web Functions documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Lambda Web Functions is (and what happened to the Lambda you already use)
&lt;/h2&gt;

&lt;p&gt;Lambda Web Functions is a new function format where your code is a regular HTTP server listening on a port, instead of an &lt;code&gt;(event, context)&lt;/code&gt; handler. The AWS News Blog mentions Express, Hono, Fastify, Next.js "or any Node.js framework", with no adapter in between.&lt;/p&gt;

&lt;p&gt;The launch also came with a rename. The Lambda you already know is now called &lt;strong&gt;Lambda Event Functions&lt;/strong&gt;, with no code change and no redeploy. Lambda now has three formats, all built on Firecracker: Event Functions, Web Functions and MicroVMs. Each one serves a different programming model (I broke down the third one in my post on &lt;a href="https://dev.to/aws-builders/aws-lambda-microvms-run-untrusted-code-with-vm-level-isolation-no-infra-to-manage-3i3"&gt;Lambda MicroVMs for untrusted code isolation&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The new vocabulary, anchored in what you already use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web Function&lt;/strong&gt; is the top-level resource, the umbrella.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revision&lt;/strong&gt; is an immutable snapshot of code and config (.zip package in S3, runtime, timeout, env vars, execution role, per-environment concurrency, network connector). Think of an Event Function &lt;code&gt;version&lt;/code&gt;: change any of those fields and a new revision is born.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endpoint&lt;/strong&gt; is the HTTPS front door, with its own domain and TLS. It plays the role of a weighted alias: it routes to at most two revisions, with a percentage weight (integer from 1 to 100) on each. Canary and blue/green come built in, no CodeDeploy needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changes in the execution and billing model
&lt;/h2&gt;

&lt;p&gt;With Event Functions, every table gets its own waiter. The waiter takes the order, walks it to the kitchen, then stands next to the table waiting for the dish. You pay that waiter for the whole time standing there, including the time spent staring at the kitchen door. That's the "1 request = 1 environment, billed by wall-clock duration times configured memory" model.&lt;/p&gt;

&lt;p&gt;With Web Functions, one waiter covers several tables: while one dish is in the kitchen, they take the order at the next table. And you only pay for the time they're actually working. In the docs, that turns into two changes.&lt;/p&gt;

&lt;p&gt;The first is per-environment concurrency: each execution environment serves several requests in parallel. The ceiling is &lt;code&gt;maxConcurrencyPerEnvironment&lt;/code&gt;, set on the revision, from 1 to 128, default 64. Below that ceiling, Lambda itself adjusts the limit dynamically based on resource usage.&lt;/p&gt;

&lt;p&gt;The second is active CPU billing. The pricing page splits it into three dimensions (us-east-1, Arm):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Active vCPU&lt;/td&gt;
&lt;td&gt;$0.0000276944 per vCPU-second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;$0.0000036667 per GB-second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests&lt;/td&gt;
&lt;td&gt;$0.30 per million&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Granularity is 1 ms, with data transfer billed separately. Time spent waiting on a query or an API call burns very little CPU, and only active CPU goes on the bill. Memory follows a different rule: the environment pays for the memory it consumes &lt;strong&gt;for as long as it exists&lt;/strong&gt;, including the gaps between requests.&lt;/p&gt;

&lt;p&gt;The rest of the execution contract, straight from the docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda injects &lt;code&gt;AWS_LAMBDA_HTTP_ENDPOINT&lt;/code&gt; (&lt;code&gt;host:port&lt;/code&gt;), and your server has up to 10 seconds to listen on exactly that address. As soon as it's listening, Lambda marks the environment as ready and starts sending requests.&lt;/li&gt;
&lt;li&gt;On shutdown you get a &lt;code&gt;SIGTERM&lt;/code&gt;, and the process has up to 1 second to clean up.&lt;/li&gt;
&lt;li&gt;It scales to zero with no traffic. Cold starts happen when a new environment comes up; for small traffic increases, Lambda keeps some headroom of environments ahead of demand.&lt;/li&gt;
&lt;li&gt;The environment is long-lived and isn't frozen between requests the way an Event Function is.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hands-on: the server and the deploy
&lt;/h2&gt;

&lt;p&gt;The minimum code is an HTTP server. Here's a version with &lt;code&gt;node:http&lt;/code&gt;, zero dependencies, following the contract in the docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js (the runtime loads index.js from the package root by default)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:http&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_LAMBDA_HTTP_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Global scope: process-level things only (SDK client, connection pool, config).&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&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;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Everything that belongs to the request stays in here, never in a global.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-request-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;n/a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/health&lt;/span&gt;&lt;span class="dl"&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&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="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&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;startedAt&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&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="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello from Lambda Web Functions!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`listening on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// You get up to 1 s after SIGTERM. Don't count on it to persist data.&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;unref&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;The &lt;code&gt;x-request-id&lt;/code&gt; header is illustrative (the right header depends on your front door). The example is really about scope: with up to 128 requests in the same process, a global variable holding the current request's user leaks one customer's data to another.&lt;/p&gt;

&lt;p&gt;For deploys, the fastest path is the CLI's &lt;code&gt;aws lambda-web deploy&lt;/code&gt;. For IaC, CloudFormation already has three types: &lt;code&gt;AWS::Lambda::WebFunction&lt;/code&gt;, &lt;code&gt;AWS::Lambda::WebFunctionRevision&lt;/code&gt; and &lt;code&gt;AWS::Lambda::WebFunctionEndpoint&lt;/code&gt;. The template below only uses properties that appear in the CloudFormation reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2010-09-09'&lt;/span&gt;
&lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;CodeBucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
  &lt;span class="na"&gt;ExecutionRoleArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;WebApp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Lambda::WebFunction&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-api&lt;/span&gt;

  &lt;span class="na"&gt;WebAppRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Lambda::WebFunctionRevision&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;WebApp&lt;/span&gt;           &lt;span class="c1"&gt;# accepts name or ARN&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;first revision&lt;/span&gt;
      &lt;span class="na"&gt;BuildConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;CodeConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;S3Object&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;CodeBucket&lt;/span&gt;
            &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.zip&lt;/span&gt;
        &lt;span class="na"&gt;RuntimeConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs24.x&lt;/span&gt;
      &lt;span class="na"&gt;ServiceConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ExecutionRoleArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;ExecutionRoleArn&lt;/span&gt;
        &lt;span class="na"&gt;TimeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
        &lt;span class="na"&gt;MaxConcurrencyPerEnvironment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32&lt;/span&gt;  &lt;span class="c1"&gt;# 1 to 128, default 64&lt;/span&gt;

  &lt;span class="na"&gt;WebAppEndpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Lambda::WebFunctionEndpoint&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;WebApp&lt;/span&gt;
      &lt;span class="na"&gt;EndpointName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
      &lt;span class="na"&gt;EndpointType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HomeRegion&lt;/span&gt;            &lt;span class="c1"&gt;# can't change after creation&lt;/span&gt;
      &lt;span class="na"&gt;AuthType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IamAuth&lt;/span&gt;                   &lt;span class="c1"&gt;# required for CloudFront with OAC&lt;/span&gt;
      &lt;span class="na"&gt;RevisionWeights&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;RevisionId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;WebAppRevision.RevisionId&lt;/span&gt;
          &lt;span class="na"&gt;Weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
      &lt;span class="na"&gt;ScalingConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;MaxEnvironments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;               &lt;span class="c1"&gt;# optional, minimum 2&lt;/span&gt;

&lt;span class="na"&gt;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;EndpointDomain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;WebAppEndpoint.DomainName&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI &lt;code&gt;deploy&lt;/code&gt; creates an &lt;code&gt;ApplicationManaged&lt;/code&gt; endpoint (public, no Lambda-level authentication) by default, which is why the template ships with &lt;code&gt;IamAuth&lt;/code&gt;. Also, &lt;code&gt;EndpointType&lt;/code&gt; and regions are fixed: to change them, you create a new endpoint and move your clients to the new domain. The What's New post also mentions AWS SAM and AWS CDK; I only checked the CloudFormation reference property by property, so the template is plain CloudFormation.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to switch from Event Functions to Web Functions
&lt;/h2&gt;

&lt;p&gt;The docs list the Web Functions use cases: full-stack web apps, REST and GraphQL APIs, HTTP microservices, SSR and response streaming (LLM output, for example). Queues and file uploads, per the same docs, stay on Event Functions.&lt;/p&gt;

&lt;p&gt;My rule of thumb is the ratio between CPU and waiting. If a request takes 200 ms end to end and 50 ms of that is CPU (the rest is database, external API, AI model), on Event Functions you pay for the 200 ms; on Web Functions, you pay for the 50 ms of CPU plus the environment's memory. LLM streaming is the extreme case: seconds of open connection, almost no CPU.&lt;/p&gt;

&lt;p&gt;The reverse holds too. If the request is pure CPU (generating a PDF, resizing images, heavy crypto), the advantage disappears and a risk shows up: synchronous CPU blocks the event loop for &lt;strong&gt;every&lt;/strong&gt; in-flight request in that environment. The docs tell you to use &lt;code&gt;worker_threads&lt;/code&gt; or lower &lt;code&gt;maxConcurrencyPerEnvironment&lt;/code&gt;. That's the Bulkhead pattern applied inside the process: you cap how many requests share the same resource so one slow request doesn't sink the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-offs (it's not all sunshine and rainbows)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Shared state across requests
&lt;/h3&gt;

&lt;p&gt;One request per environment forgave a lot of sins. The official best practices forbid credentials, customer data, response objects or mutable request state in global variables. Connection pools and SDK clients can stay global, as long as you validate stale connections and cap caches. And the 512 MB &lt;code&gt;/tmp&lt;/code&gt; is shared across requests, so file names have to be unique.&lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js only, Arm64 only
&lt;/h3&gt;

&lt;p&gt;At launch it's &lt;code&gt;nodejs24.x&lt;/code&gt; only, Arm64 only. Native modules compiled for x86_64 won't run. There are no runtime management controls: you can't pin a patch version. Java, Python or .NET teams stay on Event Functions (or MicroVMs).&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixed size
&lt;/h3&gt;

&lt;p&gt;Every environment gets 2 GB of memory, no choice, split between heap, buffers, native modules and every in-flight request. The default V8 heap is 1,024 MB; you can raise it with &lt;code&gt;NODE_OPTIONS=--max-old-space-size=...&lt;/code&gt;, as long as you leave room for everything else. On Event Functions you pick anything from 128 MB to 10 GB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricier requests
&lt;/h3&gt;

&lt;p&gt;$0.30 per million, versus $0.20 on Event Functions: 50% more. In high-request, low-CPU workloads, the request line adds up: in the official auth middleware example (100 million requests), $30.00 of the $64.56 total is requests alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fine print
&lt;/h3&gt;

&lt;p&gt;HTTP/1.1 only (HTTP/1.0 gets a 505), the &lt;code&gt;Upgrade&lt;/code&gt; header is stripped (WebSocket doesn't show up as supported in the docs), and the service metrics have no revision dimension. During a canary, you log the revision ID yourself in the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's run the numbers (with what's confirmed)
&lt;/h2&gt;

&lt;p&gt;I'll use the official I/O-heavy API example from the pricing page, because every assumption comes from AWS: us-east-1, Arm, 10 million requests a month, 200 ms end to end, 50 ms of active CPU per request, a single environment serving everything for 30 days (2,592,000 seconds), 0.29 GB of memory consumed, no free tier and no data transfer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU: 10,000,000 × 0.05 s = 500,000 vCPU-seconds × $0.0000276944 = &lt;strong&gt;$13.85&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Memory: 0.29 GB × 2,592,000 s × $0.0000036667 = &lt;strong&gt;$2.76&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Requests: 10 × $0.30 = &lt;strong&gt;$3.00&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Total: &lt;strong&gt;$19.61&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Memory is the open question. The runtime docs say the environment has a fixed 2 GB, and the example uses 0.29 GB. The pricing page settles half of it, because it talks about memory &lt;strong&gt;consumed&lt;/strong&gt; by the environment. It doesn't explain how that consumption is measured (peak, average, sampling). If it were the full 2 GB, the memory line in this example would jump to about $19 and the total would nearly double. That's why I'm not handing you an Event versus Web comparison built on made-up numbers. Measure before you decide:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CPU time per request versus total duration (today's &lt;code&gt;Duration&lt;/code&gt; on your Event Function is the ceiling).&lt;/li&gt;
&lt;li&gt;Process memory under the concurrency you plan to run, compared with the memory line on the bill.&lt;/li&gt;
&lt;li&gt;How many environments stay alive throughout the day, because memory is billed per live environment, even idle ones.&lt;/li&gt;
&lt;li&gt;Data transfer, which is left out of every official example and weighs heavily in the cross-region scenario in the next section.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ties straight into the Cost Optimization pillar of the Well-Architected Framework: measure real demand before picking the pricing model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap: no South America region at launch
&lt;/h2&gt;

&lt;p&gt;The launch regions are us-east-1, us-east-2, us-west-2, ap-northeast-1 (Tokyo) and eu-west-1 (Ireland). Nothing in South America, so São Paulo (sa-east-1) is out, and I couldn't find any official timeline for when it comes in. Brazil is the concrete case I deal with every day, but the same reasoning applies to anyone whose users and data live far from those five regions. If that's you, the question stops being "Web or Event?" and becomes "is it worth crossing a continent to use Web Functions?".&lt;/p&gt;

&lt;p&gt;The path AWS left ready is CloudFront. On the same day, CloudFront shipped support for Web Functions as an origin, with Origin Access Control and at no additional cost. The configuration confirmed in the docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Endpoint with &lt;code&gt;AuthType: IamAuth&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;OAC with origin type &lt;code&gt;lambda&lt;/code&gt; and signing behavior &lt;code&gt;always-amz-auth&lt;/code&gt; (CloudFront rejects the others).&lt;/li&gt;
&lt;li&gt;A resource-based policy on the Web Function allowing &lt;code&gt;lambda:InvokeWebFunctionEndpoint&lt;/code&gt; for the &lt;code&gt;cloudfront.amazonaws.com&lt;/code&gt; principal, with &lt;code&gt;aws:SourceArn&lt;/code&gt; pinned to your distribution.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cloudfront.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lambda:InvokeWebFunctionEndpoint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"aws:SourceArn"&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:cloudfront::111122223333:distribution/&amp;lt;distribution-id&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the Security side, the endpoint domain is still reachable from the internet even behind CloudFront. With &lt;code&gt;IamAuth&lt;/code&gt;, only requests signed by someone your policies allow get through; with &lt;code&gt;ApplicationManaged&lt;/code&gt;, anyone who knows the domain can call your application. To lock that down across the organization, the docs show an SCP using the &lt;code&gt;lambda:Request/WebFunctionAuthType&lt;/code&gt; condition key to deny &lt;code&gt;ApplicationManaged&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw0g9jsx980vbyb3ci4wx.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fw0g9jsx980vbyb3ci4wx.png" alt="Recommended architecture for users in Brazil: CloudFront at the edge with OAC signing requests to an IamAuth Lambda Web Functions endpoint in us-east-1, data in the same region through the egress network connector, and the alternative of staying on Event Functions in sa-east-1" width="800" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where your data lives drives the architecture. CloudFront brings the edge closer, but the function is still in the United States. If your database is in sa-east-1, every query from your function crosses the continent, and an SSR page with five queries pays that trip five times before returning a single byte, with inter-region data transfer on the bill. There's no official latency number for this, so measure it: the time of one query from us-east-1 to your database, times the number of queries on the route.&lt;/p&gt;

&lt;p&gt;There's also a networking limit: the egress network connector, which gives you access to your VPC, only works with a &lt;code&gt;HomeRegion&lt;/code&gt; endpoint. It doesn't give you internet access (you need a NAT gateway). Without a connector, the egress IP is managed by Lambda and is no good for an allow list; with a connector, the docs tell you to allow the destination by security group or by the subnets' CIDR, because there's no stable source IP either.&lt;/p&gt;

&lt;p&gt;Here's the decision table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;I/O-bound API or SSR, data can live in us-east-1, no requirement to keep data in Brazil (from a customer contract or your reading of LGPD, Brazil's data protection law, similar to GDPR)&lt;/td&gt;
&lt;td&gt;Web Functions in us-east-1 + CloudFront with OAC, data in the same region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database has to stay in sa-east-1 (contract, compliance, latency to other systems)&lt;/td&gt;
&lt;td&gt;Stay on Event Functions in sa-east-1, behind API Gateway or a Function URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU-bound workload, queues, file uploads&lt;/td&gt;
&lt;td&gt;Event Functions, in any region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java, Python or .NET stack&lt;/td&gt;
&lt;td&gt;Event Functions (or MicroVMs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorizer or middleware with lots of requests and little CPU&lt;/td&gt;
&lt;td&gt;Run the numbers: the 50% pricier request can eat the CPU savings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lots of global state in the code, no time to refactor&lt;/td&gt;
&lt;td&gt;Stay on Event Functions until the global scope is clean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need VPC access with a multi-region endpoint, or an egress IP for an allow list&lt;/td&gt;
&lt;td&gt;Event Functions; on Web Functions, VPC only with a &lt;code&gt;HomeRegion&lt;/code&gt; endpoint + connector (and NAT for internet), and validate the egress IP before promising an allow list&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I covered this kind of region trade-off, in terms of cost and availability, in &lt;a href="https://dev.to/aws-builders/the-price-of-high-availability-resilience-in-architecture-decisions-are-strategic-not-just-4hhf"&gt;high availability has a price&lt;/a&gt;. And concentrating everything in us-east-1 carries its own risk, as I explained in the post on &lt;a href="https://willpeixoto.dev/multi-cloud-less-resilient-availability-math" rel="noopener noreferrer"&gt;why multi-cloud can make you less resilient&lt;/a&gt;. From a Reliability standpoint, leaving your home region means trading one dependency for another, so choose with your eyes open.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you take away
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Web Functions runs your Node.js HTTP server on Lambda with no adapter, with up to 128 requests per environment (default 64), billing for active CPU, memory consumed while the environment is alive, and $0.30 per million requests.&lt;/li&gt;
&lt;li&gt;The win is in I/O-bound workloads: SSR, APIs that wait on a database, LLM streaming. CPU-bound work, queues and uploads stay on Event Functions.&lt;/li&gt;
&lt;li&gt;The price is discipline (zero request state in global variables, no synchronous CPU on the event loop) plus accepting a fixed 2 GB and Node.js 24 on Arm64 only.&lt;/li&gt;
&lt;li&gt;Before comparing costs, measure CPU per request, consumed memory and live environments (the docs don't detail how billed memory is measured).&lt;/li&gt;
&lt;li&gt;With no South America region, the real decision for a Brazilian workload is: data in us-east-1 with CloudFront + OAC in front, or stay on Event Functions in São Paulo. With the database on the other side of the continent, per-query latency can eat whatever you gained by switching.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/10/aws-lambda-web-functions/" rel="noopener noreferrer"&gt;What's New: AWS Lambda Web Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/aws/announcing-aws-lambda-web-functions-for-fast-consistent-web-app-performance-anywhere/" rel="noopener noreferrer"&gt;AWS News Blog: Announcing AWS Lambda Web Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/compute/exploring-aws-lambda-web-functions-for-building-web-applications/" rel="noopener noreferrer"&gt;AWS Compute Blog: Exploring AWS Lambda Web Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/web-functions.html" rel="noopener noreferrer"&gt;Developer Guide: Lambda Web Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/web-functions-best-practices.html" rel="noopener noreferrer"&gt;Best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/web-functions-endpoint-access-control.html" rel="noopener noreferrer"&gt;Endpoint access control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/web-functions-networking.html" rel="noopener noreferrer"&gt;Networking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/lambda/pricing/" rel="noopener noreferrer"&gt;AWS Lambda Pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/10/cloudfront-lambda-web-functions-origin/" rel="noopener noreferrer"&gt;What's New: CloudFront with Lambda Web Functions as an origin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-webfunction.html" rel="noopener noreferrer"&gt;CloudFormation: AWS::Lambda::WebFunction&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://willpeixoto.dev/lambda-web-functions-when-to-use" rel="noopener noreferrer"&gt;willpeixoto.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, are you running SSR or a Node API on Lambda Web Adapter today? Would you move it to a launch region behind CloudFront, or hold it close to your users until a nearby region shows up? Tell me in the comments, I want to take this conversation to the next AWS User Group São Paulo meetup. If this post helped, drop a like and share it around. Thanks a lot!&lt;/p&gt;

&lt;p&gt;BUILD. SCALE. REPEAT. =D&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>node</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Invisible Coupling: When Your Microservices Become a Distributed Monolith</title>
      <dc:creator>will peixoto</dc:creator>
      <pubDate>Fri, 30 Oct 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/invisible-coupling-when-your-microservices-become-a-distributed-monolith-803</link>
      <guid>https://dev.to/aws-builders/invisible-coupling-when-your-microservices-become-a-distributed-monolith-803</guid>
      <description>&lt;p&gt;Black Friday peak. The checkout has microservices, a CI/CD pipeline, containers, serverless functions, every piece with its own deploy and a green dashboard. Then the settlement service, way at the end of the chain, starts responding slowly, and within minutes the whole payment authorization flow is timing out. On paper, every service was independent. The customer still gave up on the purchase while waiting for the approval.&lt;/p&gt;

&lt;p&gt;That scene has a name, invisible coupling, and so does the system it produces: a distributed monolith. If this sounds familiar, stick with me, this post is for you. It kicks off a short run of posts on the topic: next I'll show how to break these dependencies by separating the hot path from the side-effects, walk through the patterns that hold that split together, and wrap up hands-on with a prototype on AWS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Validity note:&lt;/strong&gt; written in October 2026. The argument here is about architecture and doesn't expire with a release, but the AWS links (Well-Architected, Builders' Library) get reorganized from time to time. If one of them points somewhere odd, let me know in the comments and I'll fix it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Invisible coupling: what a distributed monolith really is
&lt;/h2&gt;

&lt;p&gt;Coupling is a measure of how much one service depends on another to work. My provocation, right up front: if your microservice only responds because another one responded first, that independence is an illusion. In practice, it's part of a distributed monolith.&lt;/p&gt;

&lt;p&gt;Sam Newman explores that term, distributed monolith, in &lt;em&gt;Monolith to Microservices&lt;/em&gt;, and he describes exactly the trap: you paid the full price of running separate services (network, deploys, observability, tracing) and didn't collect the prize, which was independence. The danger is the false sense of modernity. All the microservices machinery is there, but when one point fails, the system fails with it. Distributing the system didn't erase the coupling, it just made it harder to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comment that made me write this
&lt;/h2&gt;

&lt;p&gt;A few days ago, in a discussion about serverless, I read an argument that went roughly like this (translated from Portuguese):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"In complex environments, when microservices depend on others, and those depend on third parties, the latency of those requests drives cost way up and can make the model unviable. Also, in compute-heavy scenarios, serverless may not be the best option either because of cost."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This argument shows up all the time, and it touches on something real: dependency chains between microservices are expensive, fragile and slow. So far, I agree.&lt;/p&gt;

&lt;p&gt;Except this isn't a serverless problem. It's an architecture problem. Running on AWS Lambda, ECS, EKS or a VM in your server closet, if the integration is poorly designed the outcome is the same: latency, cost and fragility. A synchronous chain doesn't get faster because you swapped the runtime underneath it.&lt;/p&gt;

&lt;p&gt;That's why the "serverless vs. containers" debate feels to me like fighting over the wrong dish. Each one has its place (stateful workloads and long-running jobs call for containers; event-driven and spiky traffic is where serverless shines), but that catalog is for another day.&lt;/p&gt;

&lt;p&gt;The central point of this post is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A microservice's independence is decided by how you design its integration, and the runtime doesn't get a vote.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Temporal coupling and accumulated latency
&lt;/h2&gt;

&lt;p&gt;Temporal coupling happens when a service can only respond if another one is up at that exact moment. The availability and latency of one service start contaminating every other service in the chain.&lt;/p&gt;

&lt;p&gt;Then comes the cascade. Each synchronous hop adds a few milliseconds, which looks like nothing at first. Then you add up five calls in series and latency blows up right in the tail percentiles, those slowest requests that make up 5% or 1% of the total. That's where the customer either completes the purchase or abandons the cart.&lt;/p&gt;

&lt;p&gt;Let's do some back-of-the-napkin math, with illustrative, hypothetical numbers, just to picture it. Five services in series, each responding in 40ms at the median: the whole chain sits around 200ms. Fine so far. But real requests don't live at the median. If each hop has a 1% chance of hiccuping and taking 2 seconds, in a chain of five the chance that at least one of them hiccups gets close to 5%. One in every twenty transactions will feel it. And the customer who felt it doesn't care that YOUR average looks great. It's the same series-component arithmetic that drags availability down in the post on &lt;a href="https://willpeixoto.dev/multi-cloud-less-resilient-availability-math" rel="noopener noreferrer"&gt;why multi-cloud can leave you less resilient&lt;/a&gt;: every extra link multiplies the odds of something going wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The distributed monolith in action: payments
&lt;/h2&gt;

&lt;p&gt;Back to the payment from the opening, because that's where every millisecond counts. The customer is standing at the checkout waiting for approval, or the e-commerce site needs to confirm before they give up. A delay of a few seconds costs revenue and trust.&lt;/p&gt;

&lt;p&gt;And the bill doesn't stop at the merchant: the card network charges too. Mastercard's &lt;a href="https://www.mastercard.com/content/dam/mccom/shared/business/support/rules-pdfs/transaction-processing-rules.pdf" rel="noopener noreferrer"&gt;Transaction Processing Rules&lt;/a&gt; (sections 2.3 and 2.4) require the issuing bank to answer the authorization within a maximum time, and if the answer doesn't arrive in time the transaction times out and falls to Stand-In Processing. For Maestro and ATM transactions, an issuer with more than 2% of failures from unavailability, malfunction, or timeout for two consecutive months is already below standard, and anyone who misses the performance standards is subject to a USD 15,000 assessment on the first occurrence, rising to USD 20,000 from the third within 12 months. In payments, latency is part of the contract.&lt;/p&gt;

&lt;p&gt;In the traditional synchronous model, an authorization walks down this staircase: the Authorization service calls Fraud, which calls Limits, which calls Settlement. The response only climbs back to the customer once the last one answers. Look at the linear shape of the diagram below: nobody moves until the one below responds.&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%2Fq0aziqcb9u07uqur29k1.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fq0aziqcb9u07uqur29k1.png" alt="Synchronous payment chain: Authorization, Fraud, Limits and Settlement in series; the response only returns once every service has answered" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the distributed monolith in practice: several services tied into a single linear flow. Each hop adds latency, multiplies logs, traces and retries, and adds one more point that, if it goes down, takes the whole transaction with it.&lt;/p&gt;

&lt;p&gt;At low load, it works. The problem shows up under high concurrency: the Black Friday from the opening, a promotion that goes viral, the daily volume of a digital bank. Average latency even looks acceptable, but p95 and p99 shoot up (remember the napkin math?). Cost grows with it, and retries make the bill worse: the &lt;a href="https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/" rel="noopener noreferrer"&gt;Amazon Builders' Library article on timeouts and retries&lt;/a&gt; describes a five-deep stack of service calls where each layer retries three times on its own, and the load on the database at the bottom gets multiplied by 243. In serverless, the cost of waiting is literal: in a synchronous chain of Lambda functions, the functions at the top keep running (and keep being billed for duration) while they wait on the ones below, the &lt;a href="https://aws.amazon.com/blogs/compute/operating-lambda-anti-patterns-in-event-driven-architectures-part-3/" rel="noopener noreferrer"&gt;anti-pattern AWS itself documents&lt;/a&gt;. And a small percentage of slow transactions is enough for the frustration to show.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we got here
&lt;/h2&gt;

&lt;p&gt;Nobody woke up one day wanting to build a distributed monolith, so it's worth understanding why this pattern spread so widely. During the microservices adoption wave, one situation became routine: "I need data that lives in another microservice". Nothing wrong there, that's normal and acceptable in any distributed system. The problem was the answer, which became something close to a mantra: "then build an API, the other service exposes it and I consume it". That was the by-the-book approach at a lot of companies, and I've watched the pattern repeat itself in projects I worked on and in conversations with people in the field.&lt;/p&gt;

&lt;p&gt;And it was attractive. It leaned on good practice: well-documented REST APIs, OpenAPI/Swagger contracts, fast integration. For a while, it worked. Once the domain starts demanding low latency, high availability and resilience, the model shows its seams. Every new synchronous dependency is more temporal coupling. What was born to decouple turned into another kind of coupling, distributed and far more expensive to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try this at home
&lt;/h2&gt;

&lt;p&gt;Want to know if this story is yours? Run a thought experiment, no need to break anything in production: pick a service in your system and imagine it going down right now. How many others stop with it? If the answer is "the whole transaction", invisible coupling already lives there.&lt;/p&gt;

&lt;p&gt;A useful variation to bring to your next standup: can you deploy this service at peak time without warning the neighboring teams? If the answer is no, that independence only exists on the org chart. And that's exactly Sam Newman's criterion: when a set of services has to go to production together, you have a distributed monolith.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where independence is actually designed
&lt;/h2&gt;

&lt;p&gt;So the deciding factor is how the services talk to each other. A synchronous chain creates temporal coupling and makes everything fragile. The opposite path has a name and tooling too: Event-Driven Architecture (EDA) to reduce direct dependencies, clear domain boundaries so each service owns its own data, and the split between the hot path (the fast path the user is waiting on) and the side-effects (analytics, notifications, the receipt email, which can run in parallel without holding anyone in line). Werner Vogels summed it up in his re:Invent 2022 keynote with "the world is event-driven": the real world is asynchronous, and your architecture thanks you when you stop pretending it isn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  The same payment, now event-driven
&lt;/h3&gt;

&lt;p&gt;Go back to the payments staircase. In the redesign, the hot path keeps only what decides the approval: Authorization checks Fraud and Limits, responds to the customer and publishes a "payment authorized" event. Settlement, Notification and Analytics become consumers of that event, running in parallel, each at its own pace. If Settlement is down, Authorization keeps responding, and the event waits in the queue until it comes back. That event can travel over a queue, a bus or a stream, and each choice has its own trade-offs around ordering, replay and cost (I compared the streaming options in the post on &lt;a href="https://willpeixoto.dev/data-streaming-on-aws-kinesis-firehose-flink-msk" rel="noopener noreferrer"&gt;Kinesis, Firehose, Flink or MSK&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%2Fop9gn3i7fwvh6smpks1i.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fop9gn3i7fwvh6smpks1i.png" alt="Event-driven flow: Authorization responds to the client and publishes an event; Settlement, Notification and Analytics consume it in parallel" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the card world, settlement is already asynchronous by nature: authorization happens on the spot, and clearing and settlement come later, usually in batches. The synchronous design we saw earlier was fighting the domain itself.&lt;/p&gt;

&lt;p&gt;There's a whole toolbox behind this split (Circuit Breaker, Bulkhead, Outbox, CQRS, Saga, idempotency keys) and, above all, the decision of when to use each one. And none of them comes for free: in the post on &lt;a href="https://dev.to/aws-builders/the-price-of-high-availability-resilience-in-architecture-decisions-are-strategic-not-just-4hhf"&gt;the price of high availability&lt;/a&gt; I argued that resilience is a decision with a cost, and every one of these patterns goes into that bill. The rest is material for the next posts, where I'll go pattern by pattern. For now, hold on to the core idea: what separates an independent microservice from a hostage is the integration.&lt;/p&gt;

&lt;p&gt;This ties straight into the Reliability pillar of the Well-Architected Framework. One of its best practices, &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/rel_prevent_interaction_failure_loosely_coupled_system.html" rel="noopener noreferrer"&gt;implement loosely coupled dependencies&lt;/a&gt;, recommends making interactions asynchronous where possible, usually with a durable layer in between (an Amazon SQS queue, a Kinesis stream, AWS Step Functions), precisely so that one component's failure stays isolated instead of taking down the entire chain.&lt;/p&gt;

&lt;p&gt;Remember the scene from the beginning, the whole checkout timing out because settlement, at the end of the chain, choked? In the event-driven design, that same slowness becomes a growing queue, and the customer gets the approval. What took the checkout down was the design of a chain built to fall together, and a design can be redone. No rewriting the system from scratch and no runtime migration: the work is to go through the integrations one by one and ask which ones really need an answer right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about that comment from the beginning?
&lt;/h2&gt;

&lt;p&gt;I'm coming back to it because this whole post is an answer. In its first part, the comment got the symptom right: a microservice that depends on another, which depends on a third party, gets slow and expensive. It just blamed the wrong culprit. The latency comes from the synchronous chain, and that chain is just as slow on containers, on a VM or on Lambda. And a good part of the cost it put on serverless's tab is the cost of waiting: Lambda calling Lambda pays for duration while it waits for the answer. Take the waiting out of the path, with a lean hot path and everything else turned into events, and that bill drops along with the latency.&lt;/p&gt;

&lt;p&gt;In the second part, about heavy processing, it has a point. A workload with high, constant CPU can come out cheaper on containers, and that runtime choice is worth making case by case. But that's a different conversation. Swapping Lambda for containers in a synchronous chain only changes where the waiting happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you take away from this post
&lt;/h2&gt;

&lt;p&gt;If I could leave you with just a few ideas, these would be the ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your service only responds after another one has responded, it's part of a distributed monolith.&lt;/li&gt;
&lt;li&gt;Temporal coupling is the villain: one service's latency and availability contaminate the whole chain, and the bill shows up in p95/p99, far from the average.&lt;/li&gt;
&lt;li&gt;Swapping the runtime doesn't fix a synchronous chain. Serverless or containers, what matters is how the integration is designed.&lt;/li&gt;
&lt;li&gt;Independence is designed: EDA, clear boundaries, the hot path separated from the side-effects, and the named resilience patterns (Circuit Breaker, Bulkhead, Outbox).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, to finally answer that comment: the question was never whether to use serverless. What decides whether your system survives Black Friday is how you design the integration between services, and that holds for any runtime.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://willpeixoto.dev/invisible-coupling-distributed-monolith" rel="noopener noreferrer"&gt;willpeixoto.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the next post I'll show how to break these dependencies in practice, separating the hot path from the side-effects to cut latency and make your microservices truly independent.&lt;/p&gt;

&lt;p&gt;Before that, I want to hear from you: have you ever heard (or said) that serverless gets expensive when one service depends on another? After this post, does that argument still hold up? Tell me in the comments. If this post helped, drop a like and share it around. Thanks a lot!&lt;/p&gt;

&lt;p&gt;BUILD. SCALE. REPEAT. =D&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>architecture</category>
      <category>aws</category>
      <category>serverless</category>
    </item>
    <item>
      <title>AWS MCP Server: self-host or managed, OAuth or SigV4, and how to set it up</title>
      <dc:creator>will peixoto</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:30:16 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-mcp-server-which-one-to-use-when-and-how-to-set-it-up-now-that-aws-recommends-just-one-3b7</link>
      <guid>https://dev.to/aws-builders/aws-mcp-server-which-one-to-use-when-and-how-to-set-it-up-now-that-aws-recommends-just-one-3b7</guid>
      <description>&lt;p&gt;&lt;em&gt;🇧🇷 Também disponível em português:&lt;/em&gt; &lt;a href="https://willpeixoto.dev/aws-mcp-server-qual-usar-quando-e-como-configurar-os-dois-servidores-explicados" rel="noopener noreferrer"&gt;&lt;em&gt;AWS MCP Server: qual usar, quando e como configurar&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Knowledge MCP vs the managed AWS MCP Server, IAM, and the setup in Claude Code, the Claude app, and Kiro without pasting a key into &lt;code&gt;.env&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let me tell you a scene. I bet you've lived it.&lt;/p&gt;

&lt;p&gt;It's 11pm, you're coding with an AI sitting right next to you. You ask it something and it swears the thing exists: it makes up a service, a name that sounds a lot like something you've built before, even an implementation it promises works, with the full how-to. And you think: huh, didn't know that one, let me check the docs because it makes total sense. And bang, you find out it was hallucinating. It was writing Lambda code calling an AWS service from a function that doesn't exist, wiring up the DynamoDB table, building the IaC (Infrastructure as Code), the whole thing. Then it asks to "take a look at your account's current config" to validate the names, and you hit the wall everyone hits.&lt;/p&gt;

&lt;p&gt;The agent, besides hallucinating, has no access to your account. It can't see your resources, it has no idea what's actually there. So it does what it does best: it's creative, it's proactive, and it invents something to make you happy. To it, that thing makes sense to exist, so it assumes it exists. The little guy doesn't even blush: it hands you an ARN (Amazon Resource Name) that doesn't exist, picks a region you don't use, and guesses a table name. There it goes. It's doing exactly what it was built to do, which is make you happy.&lt;/p&gt;

&lt;p&gt;And you, tired of fighting with it and just wanting to wrap up, do what? You paste an access key into a &lt;code&gt;.env&lt;/code&gt; so it can "just see the account for a second" and give you everything right, with the real names. Who's never done that?&lt;/p&gt;

&lt;p&gt;And it's all fine, except for one detail: you forget you did it. And bang, the &lt;code&gt;.env&lt;/code&gt; went along in the commit. It's already in the git history, alerts firing everywhere, the usual mess. The key is exposed now. And that's also how your agent, in that moment where it seems to want to punish you or decided to be a little too proactive, gets real access to your account and, who knows, decides to delete the wrong stack. Or it's just the surprise bill of forty bucks in a single day, which turns into a much worse number when nobody's watching. I've been through this. You probably have too.&lt;/p&gt;

&lt;p&gt;The good news is that AWS solved this. Actually, it solved it two different ways, with names so similar they confuse a lot of people. This post is the map I wish I'd had: what the two are, which pain each one kills, and when and how to use each. Let's go.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Updated November 2026.&lt;/strong&gt; The MCP (Model Context Protocol) world moves fast. If something doesn't match the screen in front of you, check the official docs (links at the end) and tell me in the comments so I can fix it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; make the managed AWS MCP Server your default. It already searches the AWS docs with no credentials, and it inspects or operates your account when you authorize it. Keep the Knowledge MCP Server only when you want doc lookup and zero account access. And don't run both at once: duplicated tools confuse the agent. Along the way I show how to connect it in Claude Code, the Claude app, and Kiro, whether you can self-host it, how SigV4 (Signature Version 4) and read-only mode work, the IAM (Identity and Access Management) on the account side, and when NOT to use any of this.&lt;/p&gt;

&lt;h2&gt;
  
  
  But what is MCP, anyway?
&lt;/h2&gt;

&lt;p&gt;Before talking about the servers, let's level set.&lt;/p&gt;

&lt;p&gt;MCP (&lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt;) is the "plug" your AI assistant uses to talk to the outside world: tools, data and services. Anthropic created it, it's under open governance now, and over the last year every assistant worth using (Claude Code, Kiro, Cursor) started speaking MCP.&lt;/p&gt;

&lt;p&gt;Want an easy analogy? Think about the API (Application Programming Interface). The API is what lets two applications talk to each other. MCP is the same idea, just for the agent: it's what lets the AI talk to any tool without you having to build a custom integration for every single system. MCP connects the agent to the system through a standard everyone agreed to use. And that's the nice thing about a standard: you learn it once and it works for any tool and any client. So if you want to give an AI access to your system, this is the path to follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you build your own MCP server or use a managed one?
&lt;/h2&gt;

&lt;p&gt;Use a managed one when what the agent needs is AWS itself: the docs, the APIs, the resources in your account. Build your own when you need to expose your own system (an internal API, a runbook, an alert) that no managed server knows about. What changes is who carries the operation: with the managed AWS MCP Server, AWS runs the hosting and the updates, you sign in with your own IAM identity (OAuth or SigV4), and the calls land in CloudTrail; with your own, hosting, authentication and logging are all on you.&lt;/p&gt;

&lt;p&gt;For most AWS cases, you don't even need to build anything. You can build one, sure (on Lambda, on Fargate, on Amazon Bedrock AgentCore Runtime, whatever you prefer), and AWS even has official guidance for that. But AWS already runs managed servers ready to go, so a lot of the time you just plug in and use it.&lt;/p&gt;

&lt;p&gt;And it's rarely one or the other. The common setup is both plugged into the same client: the managed one for AWS, yours for the system only your company has.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two general-purpose AWS MCP servers
&lt;/h2&gt;

&lt;p&gt;AWS actually has a bunch of MCP servers, including specialized ones for specific services and workflows. But the confusion lives in two general-purpose options: the AWS Knowledge MCP Server and the managed AWS MCP Server. And the names don't help at all. Here's everything in one table, then I break down each one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;AWS Knowledge MCP Server&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;AWS MCP Server&lt;/strong&gt; (managed, GA, General Availability)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What it solves&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"My agent hallucinates AWS APIs, ARNs and service names."&lt;/td&gt;
&lt;td&gt;"My agent needs to see or act on my account, without me leaking a key."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What it accesses&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Only AWS docs and knowledge (read-only)&lt;/td&gt;
&lt;td&gt;Docs + real services in &lt;em&gt;your&lt;/em&gt; account (authenticated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Credentials&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None. You don't even need an AWS account.&lt;/td&gt;
&lt;td&gt;AWS browser sign-in (OAuth) or AWS CLI, Command Line Interface (SigV4, Signature Version 4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Audit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;td&gt;CloudTrail + CloudWatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use it when&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You want correct syntax, current docs, regional availability&lt;/td&gt;
&lt;td&gt;You want the agent inspecting or operating real infra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Risk if misused&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very low: no credentials, it can't touch your account&lt;/td&gt;
&lt;td&gt;Real. It's your account. Least-privilege matters.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you keep one sentence from this post, keep this one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One server gives your agent knowledge. The other gives it hands.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Figuring out which problem you actually have is half the battle.&lt;/p&gt;

&lt;p&gt;Want another analogy? The Knowledge server is the guru: that friend who memorized the entire AWS documentation and clears up your doubt on the spot. The managed one is the doorman checking badges: it lets you into the account for real, but it stands there checking and only opens the doors IAM (Identity and Access Management) authorized. Name doesn't match the badge? You don't get in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where does this run?&lt;/strong&gt; The AWS MCP Server is remote, and what connects to it is the MCP client: Claude Code, the Claude app (Desktop and claude.ai), Kiro, Cursor, or your own agent's code (Strands, SDK, Software Development Kit). That holds even with inference running on Bedrock, because the MCP client is the application. A production agent on AgentCore, though, typically consumes tools through the AgentCore Gateway (which also speaks MCP) or your own MCP server. That production scenario is a topic for another post in this series.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Server #1: AWS Knowledge MCP Server
&lt;/h2&gt;

&lt;p&gt;What this server does is simple: it's remote, fully managed, and it gives the model structured access to the official docs, always current. And that "current" is the point. What the model knows on its own stops at its training date, so it has no clue about what came after and ends up guessing. AWS keeps this server in sync, so it becomes your source of truth: it searches the docs, returns the page as clean markdown, checks whether a service exists in a region, lists the current regions, and now also retrieves AWS agent skills. Read-only, it doesn't write or touch the account.&lt;/p&gt;

&lt;p&gt;Why is it almost a no-brainer to turn on? Because there's no credential, and no AWS account needed. Nothing to protect, nothing to leak. The risk is basically zero and the payoff is the agent stops guessing and starts citing the real docs before it spits out the CDK (Cloud Development Kit).&lt;/p&gt;

&lt;p&gt;Use it when you're learning a service, designing the architecture you want to build and validating the idea, checking syntax, generating IaC you actually trust, or answering "is this in my region yet?" without opening the browser. Connecting is pasting a URL: add &lt;code&gt;https://knowledge-mcp.global.api.aws&lt;/code&gt; as a remote (HTTP) server in your client and that's it, no credentials at all. With it on, the agent checks the live docs before spitting out the CDK instead of guessing from training. Hallucinated ARNs drop off a cliff. Pretty good, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Server #2: AWS MCP Server, the managed one (reads and operates the account)
&lt;/h2&gt;

&lt;p&gt;This is the one that cures the &lt;code&gt;.env&lt;/code&gt; shame.&lt;/p&gt;

&lt;p&gt;The pain is different: the agent needs to see or do something in the account for real. Read the CloudWatch logs of the function that's breaking, list what's actually in the bucket, check the real schema of the DynamoDB table. The old "solution" was to hand it a long-lived credential. That's the part that keeps the security folks up at night. And honestly, it should keep you up too.&lt;/p&gt;

&lt;p&gt;What this server does: it's remote, hosted and managed by AWS, and it gives the agent authenticated access to AWS services through a small, fixed set of tools. No local install, automatic updates, and (this part I really like) every call lands in CloudTrail. The agent doesn't get a master key. It authenticates as you, through a real auth flow, on an IAM leash.&lt;/p&gt;

&lt;p&gt;Since I first wrote this, the server picked up two more capabilities: running Python scripts in a sandboxed environment and following skills curated by AWS itself (the &lt;code&gt;aws___retrieve_skill&lt;/code&gt; tool shows up next to &lt;code&gt;aws___search_documentation&lt;/code&gt;). Both also go through your IAM credentials, just like the API calls. And it pairs well with the &lt;code&gt;aws-core&lt;/code&gt; plugin I show further down, which also brings the AWS agent skills.&lt;/p&gt;

&lt;p&gt;The auth flow in plain English: there are two paths now, and the newer one is the simpler one. Today the server speaks &lt;strong&gt;OAuth directly&lt;/strong&gt;. You add the URL to your client, the first tool call opens the browser on AWS Sign-in, you log in with your usual identity, done. The token lasts 1 hour and refreshes itself for up to 12. No proxy, nothing to install.&lt;/p&gt;

&lt;p&gt;The second path is &lt;strong&gt;SigV4 with the MCP Proxy for AWS&lt;/strong&gt; (package &lt;code&gt;mcp-proxy-for-aws-cli&lt;/code&gt;), an open source proxy that runs on your machine, takes your local AWS CLI credentials, and signs every call. It still exists and it has its moment: multiple accounts in the same session, read-only mode (hiding write-capable tools from the agent), a fixed default region, an org that blocks the OAuth permissions (&lt;code&gt;signin:AuthorizeOAuth2Access&lt;/code&gt; and &lt;code&gt;signin:CreateOAuth2Token&lt;/code&gt;), or a client that can't do OAuth but can run a local proxy. The setup guide labels OAuth as "simple" (web clients, a single account, getting started) and SigV4 as "advanced", pointing it at terminal and IDE coding agents like Claude Code, Kiro and Codex. Both work in those clients; I break SigV4 down in its own section below.&lt;/p&gt;

&lt;p&gt;Either way the outcome is the same: you don't paste a key anywhere, the agent acts with your identity, and everything respects your IAM. Documentation search, by the way, needs no credentials at all.&lt;/p&gt;

&lt;p&gt;The OAuth flow, step by step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You attach the &lt;code&gt;AWSMCPSignInOAuthAccessPolicy&lt;/code&gt; managed policy to your role or user (once).&lt;/li&gt;
&lt;li&gt;You add the server URL to your client and fire the first call.&lt;/li&gt;
&lt;li&gt;The browser opens on AWS Sign-in, you authorize, and the client keeps the token (1 hour, auto-refresh up to 12).&lt;/li&gt;
&lt;li&gt;The server applies the context keys and forwards to the AWS service.&lt;/li&gt;
&lt;li&gt;IAM authorizes via your policy and responds.&lt;/li&gt;
&lt;li&gt;The whole call is logged to CloudTrail.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The "ohhh, got it" moment is this: ask "why did &lt;code&gt;checkout-prod&lt;/code&gt; start throwing 500s after 2pm?" and watch the agent pull the real CloudWatch logs, cross-reference a recent deploy, and point at the actual resource. All inside what IAM allows, all auditable, with no key in any dotfile. And it works with what you already use: Claude Code, Kiro, Cursor, any MCP-compatible client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you self-host the AWS MCP Server, or is it cloud-only?
&lt;/h2&gt;

&lt;p&gt;It's cloud-only. AWS hosts and runs the AWS MCP Server, and your client connects to a regional endpoint over HTTPS. On the SigV4 path, the only thing running on your machine is the MCP Proxy for AWS, which signs the requests and forwards them; the server itself stays remote. The Knowledge MCP Server works the same way: remote, managed, and it needs public internet access.&lt;/p&gt;

&lt;p&gt;So what if everything has to run on your side? Then you take on the operation, one of two ways. The first is the open source servers in &lt;a href="https://github.com/awslabs/mcp" rel="noopener noreferrer"&gt;awslabs/mcp&lt;/a&gt;: the older AWS API MCP Server, for example, runs locally via &lt;code&gt;uvx&lt;/code&gt;, &lt;code&gt;pip&lt;/code&gt; or Docker. The catch is that its own page now marks it as superseded by the managed AWS MCP Server, and you go back to managing credentials on the machine that runs it. The second is building your own, as in the building vs managed section above.&lt;/p&gt;

&lt;p&gt;And if your next question is "can I at least keep the traffic private?": as of this update, the official docs list no PrivateLink or VPC (Virtual Private Cloud) endpoint for the managed server. More on that in the security section.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to connect: Claude Code, the Claude app, and Kiro
&lt;/h2&gt;

&lt;p&gt;Now the practical part. The OAuth path has a single prerequisite: the identity you'll use needs the sign-in managed policy. Attach it once and forget it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam attach-role-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-name&lt;/span&gt; MyRole &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; arn:aws:iam::aws:policy/AWSMCPSignInOAuthAccessPolicy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(If you use an IAM user instead of a role, it's &lt;code&gt;attach-user-policy&lt;/code&gt; with &lt;code&gt;--user-name&lt;/code&gt;.)&lt;/p&gt;

&lt;h3&gt;
  
  
  In Claude Code
&lt;/h3&gt;

&lt;p&gt;One line, and that's really it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add aws-mcp https://aws-mcp.us-east-1.api.aws/mcp &lt;span class="nt"&gt;--transport&lt;/span&gt; http
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the first tool call the browser opens, you log in, and the agent sees the account on an IAM leash.&lt;/p&gt;

&lt;p&gt;There's also a second route: the &lt;code&gt;aws-core&lt;/code&gt; plugin. On top of the AWS MCP Server pre-configured, it brings the AWS agent skills, which are ready-made instruction packs so the agent handles CDK, serverless, containers and billing tasks well. If you want the server plus that extra context in one go, install it from Anthropic's official marketplace, which Claude Code already ships with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin &lt;span class="nb"&gt;install &lt;/span&gt;aws-core@claude-plugins-official
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Heads-up: this command runs inside Claude Code, in the terminal. The "Plugins" section in the Claude Desktop app is a different catalog, so don't go looking for &lt;code&gt;aws-core&lt;/code&gt; there (in the app, the path is the connector in the next section). The steering equivalent here is a &lt;code&gt;CLAUDE.md&lt;/code&gt; at the project root, with the same rules I show in the Kiro block below.&lt;/p&gt;

&lt;h3&gt;
  
  
  In the Claude app (Desktop and claude.ai)
&lt;/h3&gt;

&lt;p&gt;Yes, it works in the app, no terminal needed. In Claude Desktop: Settings, Connectors, "Add custom connector", give it a name (I called mine &lt;code&gt;aws-mcp&lt;/code&gt;) and paste the 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://aws-mcp.us-east-1.api.aws/mcp?oauth=initialize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;?oauth=initialize&lt;/code&gt; suffix tells the server to kick off the OAuth flow explicitly (Cursor, Kiro IDE, VS Code, Codex and Gemini CLI use the same trick, per the setup guide). Leave the OAuth Client ID and Secret fields empty, the flow handles that. On claude.ai web it's the same URL without the suffix, in the connector settings.&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%2Fnpejuvv5i083s8xds1tn.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fnpejuvv5i083s8xds1tn.png" alt="Adding the AWS MCP Server as a custom connector in Claude Desktop" width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the first call, the AWS authorization screen opens:&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%2Fbca9j7fcgl5qskhtexa5.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fbca9j7fcgl5qskhtexa5.png" alt="AWS Sign-in consent screen connecting the MCP client to the AWS MCP Server" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two warnings here, because I hit them first so you don't have to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not the Directory connector.&lt;/strong&gt; If you search "AWS" in Claude's connector directory, an "AWS API MCP Server" shows up. That is NOT the new managed one: it's the older &lt;code&gt;aws-api-mcp-server&lt;/code&gt; from awslabs (look at its tools, just &lt;code&gt;suggest_aws_commands&lt;/code&gt; and &lt;code&gt;call_aws&lt;/code&gt;), precisely one of the servers the official docs tell you to replace. The right path is the custom connector with the URL above. And don't run both, that's the exact tool-conflict scenario AWS warns about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't authorize as root.&lt;/strong&gt; The consent screen offers "Continue with Root or IAM user" and says access follows "your existing AWS permissions". Whatever identity authorizes that connection defines the agent's blast radius. Prefer IAM Identity Center or a federated role with temporary credentials, and use an IAM user only if federation isn't an option where you are. Always with the sign-in managed policy and least-privilege. Root driven by an agent is everything we don't want (I fell for this one too: logged in, worked on the first try, and it was root driving the whole account).&lt;/p&gt;

&lt;p&gt;And a practical red flag, with a catch: if the connection worked without you attaching &lt;code&gt;AWSMCPSignInOAuthAccessPolicy&lt;/code&gt;, don't jump straight to "I must be root or admin". Your identity may already carry &lt;code&gt;signin:AuthorizeOAuth2Access&lt;/code&gt; and &lt;code&gt;signin:CreateOAuth2Token&lt;/code&gt; through some other policy or permission set. Careful with the classic &lt;code&gt;aws sts get-caller-identity&lt;/code&gt; too: it shows the identity of your local CLI, and that's not necessarily the one you picked in the browser during OAuth. To be sure, check the policies of the identity you actually signed in with, and the authorization event in CloudTrail.&lt;/p&gt;

&lt;h3&gt;
  
  
  In Kiro
&lt;/h3&gt;

&lt;p&gt;In Kiro CLI (2.11 or later):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kiro-cli mcp add &lt;span class="nt"&gt;--name&lt;/span&gt; aws-mcp &lt;span class="nt"&gt;--url&lt;/span&gt; https://aws-mcp.us-east-1.api.aws/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Kiro IDE, add it as a remote server with the &lt;code&gt;?oauth=initialize&lt;/code&gt; URL, or via &lt;code&gt;mcp.json&lt;/code&gt; (per project at &lt;code&gt;&amp;lt;root&amp;gt;/.kiro/settings/mcp.json&lt;/code&gt;, global at &lt;code&gt;~/.kiro/settings/mcp.json&lt;/code&gt;; when both exist, the project one wins).&lt;/p&gt;

&lt;p&gt;Config turns the server on. To make Kiro actually prefer these tools, create a steering file at &lt;code&gt;.kiro/steering/aws-mcp.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;inclusion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Using AWS via MCP&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Before generating any AWS code or IaC (CDK, CloudFormation, SDK), validate
  service names, syntax and regional availability against the docs via the
  &lt;span class="sb"&gt;`aws-mcp`&lt;/span&gt; server (documentation search tool). Don't guess APIs or ARNs.
&lt;span class="p"&gt;-&lt;/span&gt; To inspect real account resources (CloudWatch logs, S3 items, DynamoDB
  schema), use the same &lt;span class="sb"&gt;`aws-mcp`&lt;/span&gt; server. Never ask for or use an access key.
&lt;span class="p"&gt;-&lt;/span&gt; Never run a destructive action (delete, scaling) without confirming first.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;inclusion: always&lt;/code&gt; makes that rule part of every Kiro conversation in that project.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does SigV4 authentication work with the AWS MCP Server?
&lt;/h2&gt;

&lt;p&gt;SigV4 (Signature Version 4) is the protocol AWS uses to sign API requests. With the AWS MCP Server, the MCP Proxy for AWS runs locally, reads your AWS CLI credentials and signs each request to the MCP endpoint; the server then acts as that identity, and IAM decides what goes through. Pick it over OAuth when you need several accounts in one session, read-only mode, a default region, or when your org blocks the OAuth sign-in permissions.&lt;/p&gt;

&lt;p&gt;If you landed on one of those cases, the setup asks a bit more of your local machine: AWS CLI 2.32+ signed in with &lt;code&gt;aws login&lt;/code&gt; (credentials rotate on their own every 15 minutes, sessions up to 12 hours), &lt;code&gt;uv&lt;/code&gt; installed, and the proxy in &lt;code&gt;mcp.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aws-mcp"&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;"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;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"mcp-proxy-for-aws-cli==1.7.0"&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://aws-mcp.us-east-1.api.aws/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--metadata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AWS_REGION=sa-east-1"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;-cli&lt;/code&gt; in the name. &lt;code&gt;mcp-proxy-for-aws-cli&lt;/code&gt; is the distribution meant to run through &lt;code&gt;uvx&lt;/code&gt;, with its whole dependency tree locked to tested versions. &lt;code&gt;mcp-proxy-for-aws&lt;/code&gt;, without the suffix, became the library you import into your own Python code (LangChain, Strands Agents and the like).&lt;/p&gt;

&lt;p&gt;The official docs use &lt;code&gt;mcp-proxy-for-aws-cli@latest&lt;/code&gt;, which always pulls the newest release. It's convenient, but you end up running whatever shipped yesterday without reviewing anything. I'd rather pin the version (your supply chain will thank you) and bump it on purpose, checking PyPI every now and then for a new release.&lt;/p&gt;

&lt;p&gt;The endpoint is regional (eight regions today, still no São Paulo); you connect to one of them and operate on resources in whatever region you pass in &lt;code&gt;AWS_REGION&lt;/code&gt;. Without &lt;code&gt;AWS_REGION&lt;/code&gt;, operations default to &lt;code&gt;us-east-1&lt;/code&gt;. Check the current list in the official docs, since this keeps changing.&lt;/p&gt;

&lt;p&gt;For multiple accounts, the proxy takes more than one profile in &lt;code&gt;--profile&lt;/code&gt;: the first one is the default and the others become available for switching per call. Multi-profile only exists on this path; the docs say outright that OAuth doesn't support it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the AWS MCP Server have a read-only mode?
&lt;/h2&gt;

&lt;p&gt;Yes, but only on the SigV4 path. Start the MCP Proxy for AWS with &lt;code&gt;--read-only&lt;/code&gt; and it disables the tools that may require write permissions, based on the &lt;code&gt;readOnlyHint&lt;/code&gt; annotation each tool carries. With OAuth there's no such switch in the docs; there, your read-only guardrail is IAM.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;mcp.json&lt;/code&gt; from the previous section, it's one more argument:&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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aws-mcp"&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;"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;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"mcp-proxy-for-aws-cli==1.7.0"&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://aws-mcp.us-east-1.api.aws/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--read-only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--metadata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AWS_REGION=sa-east-1"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things before you relax. First, the docs don't publish the list of which tools stay visible in read-only mode, so after turning it on, run &lt;code&gt;/tools&lt;/code&gt; or &lt;code&gt;/mcp&lt;/code&gt; in your client and look at what's left. Second, read-only mode hides tools from the agent; the boundary that actually holds is still IAM. Pair it with a read-only identity and the &lt;code&gt;aws:ViaAWSMCPService&lt;/code&gt; deny I show in the account section, and you get two layers instead of one.&lt;/p&gt;

&lt;p&gt;If you're still on the older local AWS API MCP Server, its equivalent is the &lt;code&gt;READ_OPERATIONS_ONLY=true&lt;/code&gt; environment variable, which only allows operations whose access level isn't "Write".&lt;/p&gt;

&lt;h2&gt;
  
  
  The security part you can't ignore
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Everything fails, all the time."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Werner Vogels, Amazon CTO&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If there's one part not to skip, it's this one. Werner's right, and with an AI driving your account it's worth taking that literally: assume the agent will mess up sooner or later, and design for it. Giving it access to read and operate your account is as serious as it sounds, so it's worth doing carefully.&lt;/p&gt;

&lt;p&gt;First thing: IAM is still the boss. The managed server works inside your permissions. Treat the agent like a new coworker. Or better: like that fresh intern who does everything to the letter, no questions asked, and who's so afraid of getting it wrong that they don't even stop to think. The type who, if you tell them to go find the spark stockroom to store some in a bottle, will actually go looking. Yeah, the agent is just like that too: give it too much access and it'll use it, even when it makes no sense. So, least-privilege, scoped only to what the task needs, nothing beyond that. AWS added standardized IAM context keys for these managed MCP servers, so you can write policy that knows "this call came through the MCP server" and restrict accordingly.&lt;/p&gt;

&lt;p&gt;Second: there's no long-lived key anywhere. With OAuth, what exists is a 1-hour token with automatic refresh; on the SigV4 path, temporary AWS CLI credentials that rotate on their own every 15 minutes. So no secret sits around in your shell history, your repo, or your &lt;code&gt;.env&lt;/code&gt;, which is exactly where we tend to leak them.&lt;/p&gt;

&lt;p&gt;Third: the authenticated calls are auditable. CloudTrail records what the agent requested through the server and CloudWatch gives you the metrics. After an incident you can answer "what did the agent actually do?" with a straight face. If you can't answer that today about your current setup, that alone is reason enough to switch. Just don't confuse visibility with retention: Event History covers the recent stuff, and long-term evidence needs a properly configured trail or CloudTrail Lake, with retention and access control set on purpose (and that side can cost storage and ingestion).&lt;/p&gt;

&lt;p&gt;And one limitation to check before production: as I write this, the official docs list no PrivateLink or VPC endpoint support for the managed server. What exists is an &lt;a href="https://github.com/awslabs/mcp/issues/3972" rel="noopener noreferrer"&gt;open feature request&lt;/a&gt; from the community, with nothing committed by AWS. If keeping this traffic inside the network boundary is a hard requirement for you, treat it as a blocker until the docs say otherwise.&lt;/p&gt;

&lt;p&gt;My rule of thumb? One dedicated IAM role per agent purpose. If an agent gets compromised or goes sideways, the blast radius stops at that role. Don't reuse your admin identity. And don't hand it &lt;code&gt;*&lt;/code&gt; on &lt;code&gt;*&lt;/code&gt; "just to unblock the demo", because the demo becomes prod faster than you'd think.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the AWS account side (the essentials)
&lt;/h2&gt;

&lt;p&gt;On the account side it's less than it seems, because the managed AWS MCP Server doesn't create IAM actions of its own. There's no &lt;code&gt;mcp:Invoke&lt;/code&gt; to allow: it signs every call with SigV4 using your credentials and forwards it to the service, which authorizes against your usual policies. If your identity can't call &lt;code&gt;logs:GetLogEvents&lt;/code&gt;, neither can the agent. Your current permissions are the boundary.&lt;/p&gt;

&lt;p&gt;The quick-start is this: begin with a read-only identity (a dedicated role or an SSO, Single Sign-On, permission set) and use the new context keys, &lt;code&gt;aws:ViaAWSMCPService&lt;/code&gt; and &lt;code&gt;aws:CalledViaAWSMCP&lt;/code&gt;, to deny destructive actions when the call comes from the agent, even if your role could do them. That way the agent reads all it wants and the dangerous verbs are blocked just for it. Then let CloudTrail show what it actually used and tighten the policy around that.&lt;/p&gt;

&lt;p&gt;A simple guardrail, just so you get the idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BlockDestructiveActionsViaMCP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:DeleteTable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lambda:DeleteFunction"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Bool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"aws:ViaAWSMCPService"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup deserves a post of its own, done as code. Later in the series I'll publish an &lt;strong&gt;"IAM for AI agents on AWS, with CDK"&lt;/strong&gt; covering the full step by step (CLI, console and CDK), cdk-nag, org-wide SCP (Service Control Policy) and multi-account. I'll link it here once it's out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one to use: the decision changed
&lt;/h2&gt;

&lt;p&gt;When this post first went out, the answer was "depends on the pain". Three weeks later, AWS settled it: the official setup guide now recommends migrating to the AWS MCP Server and &lt;strong&gt;removing Knowledge (and the older AWS API MCP) from your configuration&lt;/strong&gt;. The reason is practical: duplicate tools confuse the agent and hurt performance, and it's the docs themselves telling you to clean up.&lt;/p&gt;

&lt;p&gt;Knowledge is still up: GA, credential-free, working. One legitimate case remains: you want ONLY doc lookups, with no AWS account and nothing to authorize (studying a service, say). For everything else, it's one server: the managed one, which already does credential-free doc search and operates the account when you authorize it. Less config, fewer conflicts, same IAM leash.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to reach for this
&lt;/h2&gt;

&lt;p&gt;Because there's no silver bullet, and someone has to say it.&lt;/p&gt;

&lt;p&gt;Don't drop this on a production account with no guardrails. Start in a sandbox and get your IAM boundaries right before the agent can touch anything that bills or deletes. In the Frugal Architect, Werner makes the point that cost is an architecture requirement, not something to find out at the end of the month, and with an agent firing off calls that goes double.&lt;/p&gt;

&lt;p&gt;Don't grant broad permissions "for now". There's no "for now", trust me. Scope it from the first connection.&lt;/p&gt;

&lt;p&gt;And don't leave anything irreversible on autopilot. Deletes, scaling actions, money movement: keep a human in the loop. The agent proposes, you approve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mind your region and compliance
&lt;/h2&gt;

&lt;p&gt;A few things that matter if you operate outside us-east-1 (for me, that's &lt;code&gt;sa-east-1&lt;/code&gt;, São Paulo).&lt;/p&gt;

&lt;p&gt;First, separate two things: the region of the MCP endpoint and the region of the resources the agent operates. Today the endpoint lives in eight regions across the US, Europe and Asia Pacific, still without &lt;code&gt;sa-east-1&lt;/code&gt;. You connect through one of them and the agent still works on your resources wherever they live. Connecting through &lt;code&gt;us-east-1&lt;/code&gt; doesn't mean your DynamoDB table has to move there.&lt;/p&gt;

&lt;p&gt;Before pointing the agent anywhere, confirm the services you'll touch are available in your region. And here's the nice part: that's literally a question the MCP server itself answers for you (the doc search does it, no credentials). Check it instead of assuming.&lt;/p&gt;

&lt;p&gt;On data residency and privacy laws (LGPD, Brazil's General Data Protection Law, here; GDPR, the EU's General Data Protection Regulation, and friends elsewhere), if the agent is going to touch resources with personal data, scope IAM so it can't read what it shouldn't, and use the CloudTrail trail as evidence of who accessed what. That evidence is a strong piece of your compliance argument, and only a piece: LGPD and GDPR still ask about legal basis, purpose limitation, access control and retention.&lt;/p&gt;

&lt;p&gt;And for small teams: that "free" CloudTrail trail is gold when you don't have a dedicated security team. You get a record of everything the agent did without building anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  To wrap up
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;AWS has a pile of MCP servers, but the confusion lives in two general-purpose ones, one per pain: knowledge (stop hallucinating) and action (operate the account safely).&lt;/li&gt;
&lt;li&gt;The simple path is OAuth now: add the URL, log in through the browser, done. Works in Claude Code, the Claude app (Desktop and claude.ai), and Kiro.&lt;/li&gt;
&lt;li&gt;The managed one gets you off &lt;code&gt;.env&lt;/code&gt;: a short-lived token (OAuth) or temporary CLI credentials (SigV4), with least-privilege and everything logged to CloudTrail.&lt;/li&gt;
&lt;li&gt;The official recommendation became one server: migrate to the AWS MCP Server and drop Knowledge from your config. It's still alive for credential-free doc lookups, but running both confuses the agent.&lt;/li&gt;
&lt;li&gt;The managed server is cloud-only. Need it on your side? Build your own or run the open source ones, and own the operation. Need read-only? &lt;code&gt;--read-only&lt;/code&gt; on the SigV4 proxy, with IAM still as the real boundary.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you've been doing the &lt;code&gt;.env&lt;/code&gt; hack, relax, everyone has. But the tools to stop are right here, they're managed, and they're auditable. There's no good excuse left to keep handing your account's keys to a robot that occasionally goes off the rails.&lt;/p&gt;

&lt;p&gt;This post kicks off a series on MCP and agents on AWS. Next up I go deep on two tracks: setting up IAM as code (CDK) and OpenAI landing on Bedrock. Which one first? Tell me in the comments.&lt;/p&gt;

&lt;p&gt;Liked it? Drop a like, tell me what you think in the comments, and share it with your crew to keep the community strong. Thanks a lot for reading this far. See you in the next one?&lt;/p&gt;

&lt;p&gt;BUILD. SCALE. REPEAT. =D&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to go deeper
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/mcp" rel="noopener noreferrer"&gt;AWS's open source MCP servers (awslabs/mcp)&lt;/a&gt;, home of the Knowledge MCP Server&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/aws/agent-toolkit-for-aws" rel="noopener noreferrer"&gt;Agent Toolkit for AWS&lt;/a&gt;, where the managed AWS MCP Server lives&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/agent-toolkit/latest/userguide/mcp-server.html" rel="noopener noreferrer"&gt;AWS MCP Server, the official page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/agent-toolkit/latest/userguide/getting-started-aws-mcp-server.html" rel="noopener noreferrer"&gt;Official AWS MCP Server setup (OAuth and SigV4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/mcp-proxy-for-aws" rel="noopener noreferrer"&gt;MCP Proxy for AWS (flags, including &lt;code&gt;--read-only&lt;/code&gt; and &lt;code&gt;--profile&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/general/latest/gr/aws-mcp.html" rel="noopener noreferrer"&gt;AWS MCP Server endpoints and quotas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/solutions/guidance/deploying-model-context-protocol-servers-on-aws/" rel="noopener noreferrer"&gt;Guidance for deploying MCP servers on AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/aws/the-aws-mcp-server-is-now-generally-available/" rel="noopener noreferrer"&gt;The AWS MCP Server is now GA (official announcement)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Prefer Portuguese? &lt;a href="https://willpeixoto.dev/aws-mcp-server-qual-usar-quando-e-como-configurar-os-dois-servidores-explicados" rel="noopener noreferrer"&gt;Read the PT version here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If your agent is going to run code it didn't write, the next step is isolation: &lt;a href="https://dev.to/aws-builders/aws-lambda-microvms-run-untrusted-code-with-vm-level-isolation-no-infra-to-manage-3i3"&gt;AWS Lambda MicroVMs: run untrusted code with VM-level isolation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://willpeixoto.dev/aws-mcp-server-which-to-use-and-configure" rel="noopener noreferrer"&gt;willpeixoto.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>mcp</category>
      <category>ai</category>
      <category>security</category>
    </item>
    <item>
      <title>AWS Lambda MicroVMs: run untrusted code with VM-level isolation (no infra to manage)</title>
      <dc:creator>will peixoto</dc:creator>
      <pubDate>Wed, 24 Jun 2026 12:44:14 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-lambda-microvms-run-untrusted-code-with-vm-level-isolation-no-infra-to-manage-3i3</link>
      <guid>https://dev.to/aws-builders/aws-lambda-microvms-run-untrusted-code-with-vm-level-isolation-no-infra-to-manage-3i3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;AWS just shipped &lt;strong&gt;Lambda MicroVMs&lt;/strong&gt;, a new serverless primitive that gives each user or session a &lt;strong&gt;VM-level isolated sandbox&lt;/strong&gt;, with near-instant launch and &lt;strong&gt;state preserved for up to 8 hours&lt;/strong&gt;, all on Firecracker. Here is what it is, when to reach for it instead of a plain Lambda Function, and how to architect on top of it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;🇧🇷 &lt;a href="https://willpeixoto.dev/aws-lambda-microvms-codigo-isolado-serverless" rel="noopener noreferrer"&gt;Leia em português&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let me put you in a situation. You need to run a piece of code you did not write. Maybe it is the script your user pasted into your platform, maybe it is the snippet an AI agent just generated and wants to execute. And then comes the question that keeps anyone working with multi-tenant up at night: how do I run this without handing a stranger the keys to the house?&lt;/p&gt;

&lt;p&gt;Until last week you had three paths, each with a catch. A VM gives you strong isolation but takes minutes to boot. A container starts in seconds but shares a kernel, so running untrusted code there takes a pile of hardening. And the Lambda Function was built for short request-response, not for a session that has to keep live state between one interaction and the next (externalizing it to DynamoDB stores the data, not the live runtime: the running process, the loaded packages, the memory). In the end you chose between performance and isolation. No way around it. Or there was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Container, VM, or Lambda: the trade-off none of them solved alone
&lt;/h2&gt;

&lt;p&gt;This pattern got common: AI coding assistants, interactive code environments, analytics, vulnerability scanners, game servers running player scripts. They all need the same thing: give each user their own environment to run code the team did not write, safely and without lag.&lt;/p&gt;

&lt;p&gt;The knot is that real isolation and low latency pull in opposite directions. From a security angle you want a hard boundary between tenants (the Security pillar of the Well-Architected Framework: isolate what is not trusted). From an experience angle you want that environment up the instant the user shows up. Reconciling the two was the expensive work.&lt;/p&gt;

&lt;p&gt;And there is a nice irony in this story. We spent years learning to build stateless apps, and now state is a requirement again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The solution to the future was hiding in the past.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a line a friend dropped in a conversation, and it has not left my head since. Ever felt that way? Because I have. And it is roughly what Lambda MicroVMs does: it brings state back, without handing you the weight of a full VM.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Lambda MicroVMs is
&lt;/h2&gt;

&lt;p&gt;Lambda MicroVMs is a new primitive &lt;strong&gt;inside&lt;/strong&gt; Lambda, built exactly for that gap. Each MicroVM gives a single user or session its own isolated environment that boots fast, keeps memory and disk for the whole session, and pauses to a low cost when the user steps away.&lt;/p&gt;

&lt;p&gt;The magic comes from &lt;strong&gt;Firecracker&lt;/strong&gt;, the same lightweight virtualization that already runs over 15 trillion Lambda invocations a month. This is not raw new tech, it is the mature foundation of Lambda itself, exposed in a new way.&lt;/p&gt;

&lt;p&gt;The model is &lt;strong&gt;image-then-launch&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%2Fzr3z1cwd5n28mnspugkj.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fzr3z1cwd5n28mnspugkj.png" alt="lambda microvm lifecycle " width="799" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You build the &lt;strong&gt;image&lt;/strong&gt; once (AWS runs your Dockerfile, initializes the app, and takes a snapshot of memory and disk). After that, every MicroVM you launch resumes from that snapshot instead of cold-booting. That is why launch and resume are near-instant, even for a multi-gigabyte session.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is actually for (with examples you will recognize)
&lt;/h2&gt;

&lt;p&gt;The main cue: this only enters the picture if you are &lt;strong&gt;building a platform that runs third-party code&lt;/strong&gt;. If your app does not execute outside code, you do not need it. It is a building block for people who build that kind of product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replit, CodeSandbox, "VS Code in the browser":&lt;/strong&gt; the user types code in the browser and it runs isolated, per user, holding state while the tab is open. That "runs isolated" is the MicroVM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code interpreter (like ChatGPT's or Claude's):&lt;/strong&gt; you ask "plot this CSV", the AI writes Python and &lt;strong&gt;runs it&lt;/strong&gt; to answer you. The runtime that executes that generated code, isolated per conversation, is the use case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD runner (and relatives):&lt;/strong&gt; a job runs the code of a Pull Request that may come from any stranger's fork, untrusted by definition, so you want an isolated, disposable runner per job. Same family: a scanner that runs a suspicious binary, a coding-interview platform (the candidate's code runs isolated), an AI agent that runs shell commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The thread tying it all together: &lt;strong&gt;each user, session, or job needs its own isolated environment, and the code running there is not code you wrote.&lt;/strong&gt; That is the cue to use a MicroVM instead of a Lambda Function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda Function or Lambda MicroVM?
&lt;/h2&gt;

&lt;p&gt;They do not compete, they complete each other. The official comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Lambda Functions&lt;/th&gt;
&lt;th&gt;Lambda MicroVMs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;request-response or event-driven (APIs, data processing, automation)&lt;/td&gt;
&lt;td&gt;persistent environments running user or AI-produced untrusted code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Programming model&lt;/td&gt;
&lt;td&gt;function handler invoked in a supported runtime&lt;/td&gt;
&lt;td&gt;any application: run your own binaries, listen on ports, use Linux OS capabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duration&lt;/td&gt;
&lt;td&gt;up to 15 min per invocation; multi-step workflows up to a year with Lambda Durable Functions&lt;/td&gt;
&lt;td&gt;up to 8 hours per session; suspend and resume across sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;service-provided runtimes (or customer-provided)&lt;/td&gt;
&lt;td&gt;customer-provided MicroVM images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inbound networking&lt;/td&gt;
&lt;td&gt;direct invocations or event-source integrations; response streaming&lt;/td&gt;
&lt;td&gt;inbound access to any port using OSI Layer 7 protocols&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency&lt;/td&gt;
&lt;td&gt;one request per execution environment at a time&lt;/td&gt;
&lt;td&gt;multiple concurrent connections per MicroVM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment state&lt;/td&gt;
&lt;td&gt;warm starts may reuse the environment, but state may not persist across invocations&lt;/td&gt;
&lt;td&gt;memory and disk state preserved on suspend, restored on resume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;automatic: Lambda creates and destroys environments in response to traffic&lt;/td&gt;
&lt;td&gt;developer-controlled: you create, suspend, resume, and terminate via API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifecycle&lt;/td&gt;
&lt;td&gt;fully managed by Lambda&lt;/td&gt;
&lt;td&gt;developer-controlled, with optional idle policies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;per-request + GB-seconds&lt;/td&gt;
&lt;td&gt;per-second of compute while running + snapshot storage while suspended&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most common confusion: people assume the duration is the same as Lambda's. The startup is similar (both resume from a snapshot), but a Function dies at 15 minutes while a MicroVM holds a session for up to 8 hours with state intact. The real design: your app keeps Lambda Functions for the event-driven backbone, and &lt;strong&gt;calls&lt;/strong&gt; MicroVMs only for the steps that need to run untrusted code in isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works in practice: from endpoint to orchestration
&lt;/h2&gt;

&lt;p&gt;Three things that trip people up at first, together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The endpoint has a status.&lt;/strong&gt; When you call &lt;code&gt;run-microvm&lt;/code&gt;, you get an ID and a dedicated HTTPS endpoint for that MicroVM. But it is not ready instantly: it goes through states, from launch to &lt;code&gt;RUNNING&lt;/code&gt; (about 2 seconds), and when idle it moves to suspended, coming back on resume. The endpoint is per MicroVM, per session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One image, many MicroVMs.&lt;/strong&gt; You build the image once (&lt;code&gt;create-microvm-image&lt;/code&gt;) and each MicroVM is a &lt;code&gt;run-microvm&lt;/code&gt;. Want two? Call it twice, and you get two independent instances. Idle behavior is governed by the &lt;code&gt;idle-policy&lt;/code&gt;: &lt;code&gt;maxIdleDurationSeconds&lt;/code&gt; (suspend after X idle) and &lt;code&gt;autoResumeEnabled&lt;/code&gt; (the next request wakes the MicroVM on its own, in about 1s, no manual restart). When you are done, &lt;code&gt;terminate-microvm&lt;/code&gt; releases everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You become the orchestrator.&lt;/strong&gt; Since the endpoint is per session, something has to decide when to launch and where to route. Typically a Lambda Function in the backbone does it: it keeps a &lt;code&gt;session -&amp;gt; MicroVM&lt;/code&gt; map (a store like DynamoDB in production), calls &lt;code&gt;RunMicrovm&lt;/code&gt; on a user's first access, stores the ID and endpoint, mints a short-lived token with &lt;code&gt;CreateMicrovmAuthToken&lt;/code&gt;, and proxies the request to the MicroVM's endpoint with the &lt;code&gt;X-aws-proxy-auth&lt;/code&gt; header. If the instance is suspended and &lt;code&gt;autoResume&lt;/code&gt; is on, the request itself wakes it. Add a routine to terminate orphan MicroVMs and you have the skeleton. The backbone code is in the next post in the series. And do not confuse this with Step Functions: MicroVM is the execution environment, Step Functions is an orchestrator, different layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost, limits, and what is still missing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cost is a decision, not a detail.&lt;/strong&gt; Werner Vogels keeps hammering in the &lt;strong&gt;Frugal Architect&lt;/strong&gt; that cost is an architecture requirement, not a number you discover on the bill. The suspend is exactly that in practice: you pay a lot for VM-level isolation, but only while the user is active. When they leave, the MicroVM suspends and the cost drops, with no loss of state. Designing your &lt;code&gt;idle-policy&lt;/code&gt; on purpose is a cost decision. The model, from the official table: you pay &lt;strong&gt;per second of compute while it runs&lt;/strong&gt;, and only &lt;strong&gt;snapshot storage while it is suspended&lt;/strong&gt;. Unit prices are on the &lt;a href="https://aws.amazon.com/lambda/pricing/" rel="noopener noreferrer"&gt;Lambda pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits:&lt;/strong&gt; ARM64, up to 16 vCPUs, 32 GB of memory, and 32 GB of disk per MicroVM, and up to 8 hours of total runtime. Provisioning is flexible: you set a baseline and burst up to 4x at peak, paying the baseline while it runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IaC:&lt;/strong&gt; you can use the console, CloudFormation, and CDK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Dockerfile + zip, and not a prebuilt ECR image?&lt;/strong&gt; Aidan Steele dug into it: Lambda builds two copies of the image, one for Graviton 3 and one for Graviton 4, so it needs the source to recompile. The base comes from ECR Public, but pushing your own prebuilt image from a private ECR as the artifact is not the path. One thing that confuses people coming from containers: ECR does not leave your life. You do not &lt;strong&gt;deliver&lt;/strong&gt; the MicroVM image via ECR, but &lt;strong&gt;inside&lt;/strong&gt; the running MicroVM you can run Docker and &lt;code&gt;docker pull&lt;/code&gt; your private ECR images at runtime. ECR is for consumption inside, not for delivering the image itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Networking and region:&lt;/strong&gt; inbound traffic on configurable ports (HTTP/2, gRPC, WebSockets), service-provided JWE auth, outbound to the internet or your VPC. And it is available so far only in US East (N. Virginia, Ohio), US West (Oregon), Europe (Ireland), and Asia Pacific (Tokyo).&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to use it
&lt;/h2&gt;

&lt;p&gt;If the workload is short request-response with no state, it stays a Lambda Function. A MicroVM there is a cannon for a mosquito. And if you just need &lt;strong&gt;more than 15 minutes with your own (trusted) code&lt;/strong&gt;, a MicroVM is also overkill: for a long job, look at Fargate; for a multi-step workflow, Lambda Durable Functions (up to a year, as the table shows). MicroVMs are for when the differentiator is &lt;strong&gt;isolating untrusted code&lt;/strong&gt;, not just going past 15 minutes.&lt;/p&gt;

&lt;p&gt;There is also a gotcha AWS itself flags, and it rhymes with the determinism conversation: since the MicroVM boots from a pre-initialized snapshot (the equivalent of Lambda SnapStart, as Aidan Steele confirmed by testing), apps that generate unique content, open connections, or load ephemeral data at init may diverge. The snapshot froze a moment; whatever needs to be fresh per session cannot be frozen along with it. The fix has a name: &lt;strong&gt;lifecycle hooks&lt;/strong&gt; to re-initialize randomness when each MicroVM is created. Map that out before assuming it just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it kill the container?
&lt;/h2&gt;

&lt;p&gt;No, and the reason is even better.&lt;/p&gt;

&lt;p&gt;The hype of the week is "containers are obsolete." They are not. Quite the opposite: Aidan Steele tested it and &lt;strong&gt;you can run Docker inside a MicroVM&lt;/strong&gt;, with OS capabilities enabled. So the MicroVM does not kill the container, it is more isolated and still runs containers inside. The honest cut is different: there is one specific spot, running untrusted code in isolation, where you will no longer want to harden a container by hand. There the MicroVM wins. Everywhere else, the container is still king.&lt;/p&gt;

&lt;h2&gt;
  
  
  The details the docs leave out
&lt;/h2&gt;

&lt;p&gt;Aidan Steele spent launch day poking at the service and found some really interesting things that are not in the official docs.&lt;br&gt;
I read it and figured it was worth bringing here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You can get a shell into the MicroVM&lt;/strong&gt;, via the &lt;code&gt;CreateMicrovmShellAuthToken&lt;/code&gt; API, with pty as a first-class citizen (Lambda Functions do not have it). Gold for IDE and coding-agent use cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outbound UDP is blocked by default&lt;/strong&gt; and DNS is a local stub, so DNS inside a container falls back to 8.8.8.8 and fails. The fix is to run with Lambda's DNS: &lt;code&gt;docker run --dns 169.254.169.253&lt;/code&gt;, or go via VPC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda network connectors:&lt;/strong&gt; a reified VPC config (subnets, security groups, an IAM role for the ENI) with its own lifecycle. The network team creates it, the developer just consumes it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; (his tests): image build 2-3 min; &lt;code&gt;RunMicrovm&lt;/code&gt; to RUNNING about 2s, plus 2s to serve; suspend and resume about 1s each.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What you take away
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Lambda MicroVMs fills a real gap: VM-level isolation &lt;strong&gt;with&lt;/strong&gt; near-instant launch &lt;strong&gt;and&lt;/strong&gt; per-session state, which no single service delivered together.&lt;/li&gt;
&lt;li&gt;It does not replace the Lambda Function, it complements it. Function in the backbone, MicroVM for the untrusted code.&lt;/li&gt;
&lt;li&gt;The idle suspend is a deliberate cost lever, design your &lt;code&gt;idle-policy&lt;/code&gt; on purpose.&lt;/li&gt;
&lt;li&gt;Before locking in architecture: check the region (no São Paulo yet), the limits (ARM64, 16 vCPU, 32 GB, 8h), and the snapshot caveat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post was the map. In the next one in the series I actually spin up a MicroVM and we prove the isolation in practice, launching two MicroVMs and testing whether one can reach the other, with the repo on GitHub for you to run along.&lt;/p&gt;

&lt;p&gt;Got a case where you run user or AI code that today is duct-taped onto a container or a hand-rolled VM? Does this primitive fit? Drop a like, share it with whoever is building a multi-tenant platform, and let's talk. Cheers! =D&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://willpeixoto.dev" rel="noopener noreferrer"&gt;willpeixoto.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>serverless</category>
      <category>lambda</category>
      <category>firecracker</category>
    </item>
    <item>
      <title>High Availability Has a Price: Resilience Is a Decision, Not a Stack</title>
      <dc:creator>will peixoto</dc:creator>
      <pubDate>Thu, 23 Oct 2025 16:26:18 +0000</pubDate>
      <link>https://dev.to/aws-builders/the-price-of-high-availability-resilience-in-architecture-decisions-are-strategic-not-just-4hhf</link>
      <guid>https://dev.to/aws-builders/the-price-of-high-availability-resilience-in-architecture-decisions-are-strategic-not-just-4hhf</guid>
      <description>&lt;p&gt;&lt;strong&gt;The cost of high availability: resilience starts with strategic decisions, not technology.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://willpeixoto.dev/resiliencia-custo-ha-multi-regiao-ou-on-premise" rel="noopener noreferrer"&gt;🇧🇷 Read in Portuguese&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a major outage like the October 2025 event in AWS's us-east-1 region, the smoke clears and the same questions that haunt CTOs and architects always resurface:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Should we be multi-region?" Or worse (and a little nostalgic): "Should we go back to on-premises?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The truth is that the right answer is rarely technical.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's strategic, first and foremost.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As Werner Vogels (Amazon CTO) likes to put it in his talks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Everything fails, all the time."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that's exactly it. The central question isn't &lt;strong&gt;whether&lt;/strong&gt; it will fail, it's &lt;strong&gt;when&lt;/strong&gt; it will fail and &lt;strong&gt;how&lt;/strong&gt; prepared you'll be when that inevitable moment arrives. Because it will arrive. Whether you're in the cloud, on-premises, or running a complex multi-cloud setup.&lt;/p&gt;

&lt;p&gt;What really separates resilient teams isn't the absence of failure. It's the &lt;strong&gt;speed, clarity, and effectiveness&lt;/strong&gt; with which they respond and recover.&lt;/p&gt;

&lt;p&gt;And that's where real architectural maturity lives: resilience isn't about choosing "multi-region" or "on-premises." It's about &lt;strong&gt;understanding the inherent risk, documenting the choice transparently, and reacting with a plan&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Context Behind the Question: The Paradox of Visible Failure
&lt;/h2&gt;

&lt;p&gt;Every time there's a big outage, I notice technical teams and executives splitting into two extreme reactions, both driven by fear and pressure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"We need to go multi-region, now! Cost is secondary!"&lt;/li&gt;
&lt;li&gt;"See? The cloud isn't reliable. We should have stayed on-premises, where we had control!"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both extremes are dangerous shortcuts.&lt;/p&gt;

&lt;p&gt;Multi-region is not a vaccine against downtime, and going back to on-premises is not a synonym for control; it just moves the maintenance complexity onto you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A Crucial Point of Reflection:&lt;/strong&gt; The cloud doesn't fail more than a traditional data center, it just fails in a way that is more &lt;strong&gt;visible, shared, and, ironically, democratic&lt;/strong&gt;. On AWS, problems scale globally and become trending topics in minutes. On-premises, they hide behind scattered logs, long repair times, and, often, they only hit you. &lt;strong&gt;Honestly: do you believe your company has a greater capacity than AWS (or any major cloud provider) to manage physical security, cabling, power, cooling, and, above all, the resilience of infrastructure at global scale?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Migrating or evolving an architecture, at its core, is not about "throwing everything away" or "buying the hype." It's about &lt;strong&gt;keeping what's good in the legacy and removing what limits growth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This isn't a black-and-white fight of "Cloud vs. Data Center." It's a strategic game of &lt;strong&gt;Conscious Resilience vs. Comfort Zone&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cost vs. Continuity: The Economics Behind the 9s
&lt;/h2&gt;

&lt;p&gt;In the world of infrastructure, each additional "9" in your SLA (Service Level Agreement) doesn't just cost more. It costs &lt;strong&gt;exponentially&lt;/strong&gt; more.&lt;/p&gt;

&lt;p&gt;To illustrate the real impact of each availability tier, here's the maximum allowed downtime per year:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;99% (two 9s):&lt;/strong&gt; about &lt;strong&gt;3.65 days&lt;/strong&gt; of downtime per year. &lt;em&gt;Cost and complexity:&lt;/em&gt; baseline (1x).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;99.9% (three 9s):&lt;/strong&gt; about &lt;strong&gt;8 hours and 46 minutes&lt;/strong&gt; of downtime per year. &lt;em&gt;Cost and complexity:&lt;/em&gt; 1.5x to 2x the baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;99.99% (four 9s):&lt;/strong&gt; about &lt;strong&gt;52 minutes&lt;/strong&gt; of downtime per year. &lt;em&gt;Cost and complexity:&lt;/em&gt; 2x to 3x. Requires Multi-AZ and strong automation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;99.999% (five 9s):&lt;/strong&gt; about &lt;strong&gt;5 minutes&lt;/strong&gt; of downtime per year. &lt;em&gt;Cost and complexity:&lt;/em&gt; 3x and up. Requires flawless automation and, often, a Multi-Region architecture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each tier jump means more than doubling or tripling infrastructure; it also demands &lt;strong&gt;operational review and sophistication&lt;/strong&gt;. And here's the catch: every additional &lt;strong&gt;9&lt;/strong&gt; has to be justified by &lt;strong&gt;ROI (Return on Investment)&lt;/strong&gt;, never by technical pride.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📢 &lt;strong&gt;The Non-Negotiable Factor: Regulation.&lt;/strong&gt; For sectors like finance, healthcare, or telecom, the SLA choice isn't always purely economic. Often, the availability requirement (and the data recovery capability, the RPO) is &lt;strong&gt;imposed by law or industry rules&lt;/strong&gt;. In those cases, the debate isn't &lt;em&gt;whether&lt;/em&gt; you can afford it, but &lt;em&gt;how&lt;/em&gt; to hit the legally mandated SLA at the lowest possible cost and complexity, because the cost of a regulatory fine outweighs any technical saving.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Rule of Thumb for Complexity:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High availability (within a single region):&lt;/strong&gt; can cost 1.5x to 2x the baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Region (Active/Passive):&lt;/strong&gt; can cost 2.5x to 3x.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Cloud (Active/Active):&lt;/strong&gt; almost never reduces risk. On the contrary, it usually increases the &lt;strong&gt;failure surface&lt;/strong&gt; and operational complexity.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conscious Decisions: The Virtue of ADRs
&lt;/h2&gt;

&lt;p&gt;Every architectural choice is a commitment based on a &lt;strong&gt;context&lt;/strong&gt;, and that context is volatile. Without a record, the context is lost, which condemns us to redo decisions, revisit old discussions, and rack up unnecessary cost.&lt;/p&gt;

&lt;p&gt;That's where the practice of &lt;strong&gt;ADRs (Architecture Decision Records)&lt;/strong&gt; becomes crucial. These aren't 50-page documents. They're short records that capture the &lt;strong&gt;decision&lt;/strong&gt;, the &lt;strong&gt;reason&lt;/strong&gt;, and the &lt;strong&gt;accepted risk&lt;/strong&gt; at a specific point in time.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Example ADR (focused on the accepted risk):&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# ADR-014: Do not use multi-region replication in the MVP&lt;/span&gt;

Context:
&lt;span class="p"&gt;-&lt;/span&gt; Current traffic &amp;lt; 10 req/s.
&lt;span class="p"&gt;-&lt;/span&gt; Multi-region replication cost is estimated at &amp;gt; 3x current cost.

Decision:
Keep a single-region architecture (using Multi-AZ for intra-region HA),
with a daily cross-region backup.

Review Trigger:
After reaching an average of 100 req/s, or when the current SLA (99.95%)
starts causing business impact.

Accepted Risk / Consequence:
Risk of total service downtime if an outage affects the entire region
(estimated RTO of 4 hours for cross-region recovery).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An ADR doesn't prevent failure. But it prevents failure from catching the team by surprise, because the risk was mapped, accepted, and justified by the business. It's the map for future discussions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Selective Resilience: Not Everything Needs HA (and That's Fine)
&lt;/h2&gt;

&lt;p&gt;Selective resilience is a &lt;strong&gt;virtue of economy and clarity&lt;/strong&gt;. Not every service needs global redundancy. Spending finite resources (money and engineering attention) on unnecessary redundancy is one of the biggest forms of waste in architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prioritize High Availability (HA) only for what truly matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct revenue functions:&lt;/strong&gt; components critical to the financial transaction (e.g., &lt;strong&gt;checkout&lt;/strong&gt; and &lt;strong&gt;payment APIs&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The critical customer journey:&lt;/strong&gt; functions that block the core value of the product (e.g., &lt;strong&gt;login&lt;/strong&gt; or the &lt;strong&gt;main catalog&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regulatory and legal risk:&lt;/strong&gt; services where failure triggers &lt;strong&gt;legal fines&lt;/strong&gt; or breaches a &lt;strong&gt;penalizing contractual SLA&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrity of critical data:&lt;/strong&gt; where data loss violates an acceptable &lt;strong&gt;RPO&lt;/strong&gt; (e.g., mandatory data retention systems).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else? It can be restored through a well-defined recovery playbook. Batch jobs, internal back-office systems, and dashboards can tolerate minutes (or even hours) of downtime, as long as the reprocessing plan is clear.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;High availability with no purpose is like installing an airbag on a bicycle.&lt;/strong&gt; It's a sophisticated solution to a problem that doesn't exist in that context.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Managed != Failure-Proof: The Serverless Mindset
&lt;/h2&gt;

&lt;p&gt;A common mistake is believing that using serverless services (Lambda, DynamoDB, SQS, EventBridge) is a synonym for immunity to failure. It isn't.&lt;/p&gt;

&lt;p&gt;Failure will come, and often from where you least expect it, because the serverless paradigm shifts the &lt;strong&gt;risk surface&lt;/strong&gt;, it doesn't remove it.&lt;/p&gt;

&lt;p&gt;The key point is this:&lt;/p&gt;

&lt;p&gt;Managed services reduce your &lt;strong&gt;operational surface&lt;/strong&gt; (you don't manage the OS, patching, or capacity), but they &lt;strong&gt;don't replace good design and preparation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;During the October 2025 us-east-1 outage, plenty of 100% serverless applications went down. Not because serverless failed them, but because they leaned on a single region. When DNS resolution for the regional DynamoDB endpoint broke, anything pinned to us-east-1 (directly, or indirectly through a global control plane like IAM or STS) broke with it. Multi-AZ wouldn't have saved you here: the endpoint was regional, not zonal. And the applications that recovered slowest were frequently the ones whose code answered the failure with aggressive, unbounded retries, turning one outage into a self-inflicted retry storm.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Real resilience doesn't come from AWS. It comes from the architecture you design &lt;em&gt;on top&lt;/em&gt; of it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Decision Belongs to the Business, the Clarity to the Architect
&lt;/h2&gt;

&lt;p&gt;The difference between "having an opinion" and "having influence" lies in your ability to translate technical complexity into &lt;strong&gt;strategic clarity&lt;/strong&gt;. Your job isn't to scare the board with jargon. It's to give them the visibility they need to decide consciously.&lt;/p&gt;

&lt;p&gt;Experience has taught me that a team's maturity can be measured precisely by its ability to ask the right question:&lt;/p&gt;

&lt;p&gt;❓ Where Is Your Team's Maturity?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Immature teams focus on the tool:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They ask: &lt;strong&gt;"Which stack solves this?"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;They ask: &lt;strong&gt;"Should we use K8s or Serverless?"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;They ask: &lt;strong&gt;"What does Netflix do?"&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Mature teams focus on risk and the business:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They ask: &lt;strong&gt;"What risk are we willing to accept for this cost?"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;They ask: &lt;strong&gt;"What RTO/RPO does the end customer require from this service?"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;They ask: &lt;strong&gt;"What does our business need to survive a disaster?"&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is that two teams can use the exact same &lt;strong&gt;cloud&lt;/strong&gt;: one scales predictably, the other lives in panic mode. The difference isn't the cloud. It's the level of understanding, documentation, and technical humility behind the decisions made.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Common Trap:&lt;/strong&gt; Who hasn't heard an executive say, "Technical decisions are up to the Architecture team"? What they're actually doing is transferring responsibility for defining &lt;strong&gt;business risk&lt;/strong&gt;. Your team defines the &lt;strong&gt;HOW&lt;/strong&gt; (the stack), but the Business defines the &lt;strong&gt;HOW MUCH&lt;/strong&gt; (the acceptable RTO and RPO). It's your job to &lt;strong&gt;hand the question back&lt;/strong&gt;, so the risk decision belongs to the business.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Translating Resilience Concepts for Leadership
&lt;/h3&gt;

&lt;p&gt;(After all, who hasn't heard: &lt;em&gt;"Now translate that so I can understand it!"&lt;/em&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Multi-Region Failover

   Translation: Insurance against catastrophe. It guarantees that a
                regional disaster won't take us offline for days,
                reducing revenue loss to a few hours.

   Question:    How many hours (or minutes) of downtime can the
                business accept for service X if an entire region goes down?
------------------------------------------------------------------
2. Active-Active Setup

   Translation: Maximum, uninterrupted availability. It lets us perform
                any maintenance or update without ever impacting the
                end customer.

   Question:    Does service X need to be 100% continuous? Can we afford
                a 15-minute maintenance window?
------------------------------------------------------------------
3. RTO / RPO

   Translation: Defining the limit of the damage. These are the numbers
                that tell us what we can lose, and for how long, before
                fines or reputation become unsustainable.

   Question:    How much data (RPO) can we lose, and how long (RTO) does
                the team have to restore the service before the business breaks?
------------------------------------------------------------------
4. SPOF (Single Point of Failure)

   Translation: The Achilles' heel of revenue. It's the weak point that,
                if broken, paralyzes the whole company. This is where the
                risk must be zero.

   Question:    If this component goes down, what's the financial loss
                in 1 hour?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There is no such thing as a &lt;strong&gt;failure-proof&lt;/strong&gt; architecture.&lt;/p&gt;

&lt;p&gt;But there is such a thing as an &lt;strong&gt;organization that is surprise-proof&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And it starts with conscious decisions, documentation (the ADRs), and the technical humility to accept that error and risk are part of the equation.&lt;/p&gt;

&lt;p&gt;Teams that understand the &lt;strong&gt;"why"&lt;/strong&gt; before diving into the &lt;strong&gt;"how"&lt;/strong&gt; build systems that don't just scale. They &lt;strong&gt;survive&lt;/strong&gt;, and grow predictably.&lt;/p&gt;




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

&lt;p&gt;For anyone who wants to go deeper on risk decisions and architectural patterns, these are the documents we use as a foundation for resilience on any cloud (with a focus on AWS):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Well-Architected Framework (Reliability Pillar):&lt;/strong&gt; the fundamental guide to understanding disaster recovery (DR) and high availability (HA) principles. &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/reliability.html" rel="noopener noreferrer"&gt;Reliability Pillar&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disaster Recovery of Workloads on AWS:&lt;/strong&gt; the key document for going deeper on RTO/RPO and choosing between patterns like Pilot Light and Active-Active. &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/disaster-recovery-workloads-on-aws/introduction.html" rel="noopener noreferrer"&gt;DR Whitepaper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB Global Tables:&lt;/strong&gt; an excellent practical case study of HA at the data layer, abstracting away multi-region complexity. &lt;a href="https://aws.amazon.com/dynamodb/global-tables/" rel="noopener noreferrer"&gt;DynamoDB Global Tables&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge Resilience Guide:&lt;/strong&gt; essential for anyone working with serverless, focused on event-based resilience patterns. &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-resilience.html" rel="noopener noreferrer"&gt;EventBridge Resilience Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Post-Event Summary: Amazon DynamoDB Service Disruption in US-EAST-1 (Oct 19-20, 2025):&lt;/strong&gt; the primary source on the outage referenced in this article, straight from AWS. &lt;a href="https://aws.amazon.com/message/101925" rel="noopener noreferrer"&gt;Service Disruption Summary&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Essential Resilience Glossary
&lt;/h2&gt;

&lt;p&gt;So everyone is on the same page, here are some key terms used in this article, explained simply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High Availability (HA):&lt;/strong&gt; the ability of a system to keep operating even when one or more of its components fail. Measured in "9s" (e.g., 99.99%).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outage:&lt;/strong&gt; an unplanned interruption of a service: the service goes down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-premises:&lt;/strong&gt; infrastructure and data centers you own, physically located at the company (not in the cloud).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Region:&lt;/strong&gt; using data centers in two or more different geographic cloud regions (e.g., US East and São Paulo) for maximum protection against regional disasters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-AZ (Multi-Availability Zone):&lt;/strong&gt; using two or more Availability Zones (isolated, nearby data centers) &lt;strong&gt;within&lt;/strong&gt; the same cloud region. This is the baseline HA pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SLA (Service Level Agreement):&lt;/strong&gt; a formal agreement defining the level of service a provider is expected to deliver (usually measured in uptime).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ROI (Return on Investment):&lt;/strong&gt; a financial metric measuring the relationship between money earned (or saved) and money invested.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ADR (Architecture Decision Record):&lt;/strong&gt; a short document recording an architectural decision, the reasoning, and the accepted risk at a specific point in time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RTO (Recovery Time Objective):&lt;/strong&gt; the maximum acceptable &lt;strong&gt;time&lt;/strong&gt; a system can be down after a failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RPO (Recovery Point Objective):&lt;/strong&gt; the amount of &lt;strong&gt;data&lt;/strong&gt; (measured in time, e.g., 5 minutes) that can be lost during a disaster event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless:&lt;/strong&gt; a cloud computing model where the provider manages all the infrastructure and the developer focuses only on the code, paying only for usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit Breaker:&lt;/strong&gt; a software pattern that, when a dependency starts failing repeatedly, "opens" the circuit to protect the rest of the application from cascading failures.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;Three posts on the blog that carry this conversation into different corners:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://willpeixoto.dev/multi-cloud-less-resilient-availability-math" rel="noopener noreferrer"&gt;Is multi-cloud more resilient? The math says no&lt;/a&gt;: what happens to your availability math once you put two clouds in series.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://willpeixoto.dev/data-streaming-on-aws-kinesis-firehose-flink-msk" rel="noopener noreferrer"&gt;Data Streaming on AWS: Kinesis, Firehose, Flink, or MSK?&lt;/a&gt;: where RPO stops being a number on a slide and becomes a service choice.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://willpeixoto.dev/aws-lambda-microvms-untrusted-code-isolation" rel="noopener noreferrer"&gt;AWS Lambda MicroVMs: run untrusted code with VM-level isolation&lt;/a&gt;: when isolation moves from implementation detail to architectural requirement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And I would like to hear from you: how did your architecture hold up during the last regional outage, and which trade-offs had you already written down &lt;em&gt;before&lt;/em&gt; it happened? Tell me in the comments, or find me on LinkedIn and let's compare notes.&lt;/p&gt;

&lt;p&gt;BUILD. SCALE. REPEAT. =D&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>architecture</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
