<?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: Diego Broetto</title>
    <description>The latest articles on DEV Community by Diego Broetto (@broettod).</description>
    <link>https://dev.to/broettod</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%2F2972664%2F3a9f248e-ec85-4254-b34c-7e3dbe50465e.jpg</url>
      <title>DEV Community: Diego Broetto</title>
      <link>https://dev.to/broettod</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/broettod"/>
    <language>en</language>
    <item>
      <title>AWS Lambda MicroVMs: Sandboxes Isolados com Controle Total do Ciclo de Vida</title>
      <dc:creator>Diego Broetto</dc:creator>
      <pubDate>Tue, 18 Aug 2026 15:55:37 +0000</pubDate>
      <link>https://dev.to/broettod/aws-lambda-microvms-sandboxes-isolados-com-controle-total-do-ciclo-de-vida-3kbc</link>
      <guid>https://dev.to/broettod/aws-lambda-microvms-sandboxes-isolados-com-controle-total-do-ciclo-de-vida-3kbc</guid>
      <description>&lt;p&gt;No dia 22/07/2026 a AWS anunciou o &lt;a href="https://aws.amazon.com/blogs/aws/run-isolated-sandboxes-with-full-lifecycle-control-aws-lambda-introduces-microvms/" rel="noopener noreferrer"&gt;AWS Lambda MicroVMs&lt;/a&gt;, um recurso novo dentro do Lambda, com API própria, separado das Functions que a gente já conhece. E não é só mais uma opção de runtime. É um primitivo de computação novo, pensado pra um problema que vinha incomodando bastante gente: como dar a cada usuário (ou sessão, ou agente de IA) um ambiente isolado de verdade, que suba rápido e mantenha estado, sem você ter que construir sua própria camada de virtualização do zero.&lt;/p&gt;

&lt;p&gt;Antes de ir pro exemplo prático vale entender por que isso existe. Se você constrói algo como um assistente de código com IA, um ambiente de execução interativo, uma plataforma de análise de dados ou um scanner de vulnerabilidades, você provavelmente já bateu de frente com esse trilema: VM tradicional isola bem mas demora minutos pra subir, container sobe rápido mas compartilha kernel com o host (o que exige um trabalho de hardening considerável pra rodar código não confiável), e Function as a Service é ótimo pra evento-resposta mas não guarda estado entre interações de uma sessão longa. O MicroVMs foi desenhado exatamente pra esse buraco no meio.&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%2Feqtprjw1s4yefqfl999s.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%2Feqtprjw1s4yefqfl999s.png" alt=" " width="800" height="636"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Um aviso rápido antes de continuar: nos últimos artigos eu vim criando todos os templates utilizando o AWS SAM. Desta vez troquei pra Terraform, e vale explicar o porquê. SAM/CloudFormation continuam cobrindo bem o pedaço nativo de MicroVM Image e Network Connector. Isso não mudou. Mas o resto do laboratório (VPC, buckets, S3 Files, IAM) é onde eu realmente vivo no dia a dia, e o provider &lt;code&gt;hashicorp/aws&lt;/code&gt; (a partir da v6.40.0) já tem recursos nativos pra Amazon S3 Files (&lt;code&gt;aws_s3files_*&lt;/code&gt;). Pra imagem e o network connector, enquanto o provider não tem recurso nativo pra isso (&lt;a href="https://github.com/hashicorp/terraform-provider-aws/issues/48526" rel="noopener noreferrer"&gt;issue #48526&lt;/a&gt; ainda aberta), uso &lt;code&gt;null_resource&lt;/code&gt; + &lt;code&gt;local-exec&lt;/code&gt; como ponte pro AWS CLI. &lt;/p&gt;




&lt;h2&gt;
  
  
  1.0 O que muda em relação ao Lambda que você já usa
&lt;/h2&gt;

&lt;p&gt;O motor por baixo é o mesmo Firecracker que já roda as Functions, então o isolamento de VM (kernel próprio, sem recurso compartilhado entre sessões) é a mesma garantia de sempre. A diferença está no modelo de uso: em vez de deploy-then-invoke, aqui é &lt;strong&gt;imagem-então-lançamento&lt;/strong&gt;. Você empacota uma aplicação com Dockerfile, a AWS builda essa imagem, inicializa a aplicação dentro dela e tira um snapshot Firecracker da memória e do disco naquele estado pronto. Todo MicroVM lançado a partir dessa imagem depois disso não faz boot frio: ele resume direto do snapshot.&lt;/p&gt;

&lt;p&gt;Na prática isso significa que sua API já está de pé no instante em que o lançamento termina. E tem outra coisa que o Lambda tradicional não te dá: controle explícito de ciclo de vida. Você define uma política de idle (quanto tempo sem tráfego até suspender, quanto tempo suspenso antes de encerrar de vez, se retoma automaticamente ou não), e o MicroVM pode ficar suspenso com estado intacto por até 8 horas de runtime total. Por padrão, o container roda com um conjunto restrito de capabilities do Linux, e é isso que vai importar no exemplo a seguir.&lt;/p&gt;




&lt;h2&gt;
  
  
  2.0 Colocando isso pra rodar
&lt;/h2&gt;

&lt;p&gt;Montei uma API de workspace de arquivos com FastAPI, com o objetivo de: provar que o estado sobrevive não só a uma suspensão, mas ao fim completo do MicroVM. O disco não é local, que é parte do snapshot e some quando o MicroVM é terminado. É o &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-files-mounting-lambda.html" rel="noopener noreferrer"&gt;Amazon S3 Files&lt;/a&gt; montado dentro da imagem, um sistema de arquivos que expõe objetos do S3 como se fossem arquivos comuns, via NFS, no mesmo caminho local. O workspace passa a viver fora do ciclo de vida de qualquer MicroVM individual, e cada arquivo escrito vira um objeto de verdade no bucket por trás.&lt;/p&gt;

&lt;p&gt;O repositório completo está em &lt;a href="https://github.com/diegobroetto/lambda-microvms-workspace-demo" rel="noopener noreferrer"&gt;&lt;code&gt;lambda-microvms-workspace-demo&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 A API: app.py
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_powertools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UploadFile&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.responses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FileResponse&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Sem inject_lambda_context aqui: esse decorator injeta cold_start,
# function_request_id etc. a partir do objeto context de uma invocação
# Lambda, e esse FastAPI roda como servidor de longa duração dentro do
# MicroVM, sem um context por request. O que eu ganho de graça mesmo
# assim é o log em JSON estruturado, sem escrever formatter na mão.
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workspace-microvm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Esse caminho é o ponto de montagem do Amazon S3 Files, não disco
# local do MicroVM. Cada arquivo aqui é, por baixo, um objeto no S3,
# então sobrevive à suspensão E à terminação do MicroVM.
&lt;/span&gt;&lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt/workspace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;health&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;mounted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_mount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workspace_mounted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UploadFile&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;destination&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Arquivo salvo via S3 Files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size_bytes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stored_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;


&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_files&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modified_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ctime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st_mtime&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Listagem de workspace solicitada&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_arquivos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workspace_dir&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/files/{filename}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;download_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Download solicitado para arquivo inexistente&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Arquivo não encontrado no workspace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;FileResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2 Montando o S3 Files: entrypoint.sh
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/workspace

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;S3FILES_MOUNT_TARGET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERRO: variável S3FILES_MOUNT_TARGET não definida."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# S3FILES_MOUNT_TARGET chega aqui como variável de ambiente definida&lt;/span&gt;
&lt;span class="c"&gt;# na CRIAÇÃO DA IMAGEM, não no lançamento. Mais sobre esse detalhe&lt;/span&gt;
&lt;span class="c"&gt;# na seção 2.4.&lt;/span&gt;
mount &lt;span class="nt"&gt;-t&lt;/span&gt; nfs4 &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;nfsvers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4.1,rsize&lt;span class="o"&gt;=&lt;/span&gt;1048576,wsize&lt;span class="o"&gt;=&lt;/span&gt;1048576,hard,timeo&lt;span class="o"&gt;=&lt;/span&gt;600,retrans&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;S3FILES_MOUNT_TARGET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:/lambda"&lt;/span&gt; /mnt/workspace

&lt;span class="nb"&gt;exec &lt;/span&gt;uvicorn app:app &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.3 Estrutura do repositório: da imagem ao Terraform
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lambda-microvms-workspace-demo/
├── app/                    # FastAPI + entrypoint.sh + Dockerfile
├── infra/                  # Terraform "flat": vpc, s3, s3files, iam, microvm, outputs
├── scripts/                # só helpers chamados pelo apply/destroy (create_*, delete_*)
├── docs/
│   └── MANUAL.md           # guia manual passo a passo pra lançar, testar e provar durabilidade
└── Makefile                # só init / plan / apply / destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reparem numa coisa, o repositório não tem &lt;code&gt;make run&lt;/code&gt;, &lt;code&gt;make test&lt;/code&gt; nem &lt;code&gt;make proof&lt;/code&gt;. Isso é proposital. O &lt;code&gt;Makefile&lt;/code&gt; só cobre &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt; e &lt;code&gt;destroy&lt;/code&gt;: tudo que é infraestrutura versionada. Lançar um MicroVM, gerar token, testar com curl e reproduzir a prova de durabilidade viraram uma sequência de comandos documentada em &lt;code&gt;docs/MANUAL.md&lt;/code&gt;, não um script automatizado. O motivo é o mesmo que já defendi antes: imagem é infraestrutura versionada, MicroVM em execução é sessão efêmera, e sessão efêmera não deveria ter um &lt;code&gt;make&lt;/code&gt; escondendo o que está de fato acontecendo, e muito menos ir parar no state do Terraform.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;"Sempre que trabalho com Makefiles, me vem a memória do tempo da graduação, quando criava os arquivos de build para rodar meus programas em C."&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2.4 Provisionando com Terraform
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;infra/&lt;/code&gt; fica flat de propósito: &lt;code&gt;vpc.tf&lt;/code&gt;, &lt;code&gt;s3.tf&lt;/code&gt;, &lt;code&gt;s3files.tf&lt;/code&gt;, &lt;code&gt;iam.tf&lt;/code&gt;, &lt;code&gt;microvm.tf&lt;/code&gt;, &lt;code&gt;outputs.tf&lt;/code&gt;. A VPC é mínima, sem NAT gateway: nem o MicroVM nem os mount targets do S3 Files precisam de saída pra internet, só de rota entre si na porta 2049.&lt;/p&gt;

&lt;p&gt;Uma peça de IAM que não tinha aparecido antes: o S3 Files usa um papel próprio pra sincronizar o que é escrito via NFS de volta pro bucket, e esse papel assume o principal &lt;code&gt;elasticfilesystem.amazonaws.com&lt;/code&gt;. O S3 Files compartilha parte da arquitetura de serviço com o EFS por baixo, o que explica o principal emprestado. Esse papel de sync precisa de &lt;code&gt;s3:GetObject&lt;/code&gt;, &lt;code&gt;s3:PutObject&lt;/code&gt;, &lt;code&gt;s3:ListBucket&lt;/code&gt; e &lt;code&gt;s3:DeleteObject&lt;/code&gt; no bucket do workspace, e o bucket &lt;strong&gt;precisa ter versionamento habilitado&lt;/strong&gt;. Sem isso a sincronização não funciona.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# infra/iam.tf (trecho)&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"s3files_sync"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-s3files-sync-role"&lt;/span&gt;

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

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy"&lt;/span&gt; &lt;span class="s2"&gt;"s3files_sync_bucket_access"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-s3files-sync-bucket-access"&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3files_sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;microvm.tf&lt;/code&gt; é onde fica a ponte via CLI, porque o provider ainda não tem &lt;code&gt;aws_lambda_microvm_image&lt;/code&gt; nem equivalente pro network connector. O detalhe que mudou desde a última vez: o &lt;code&gt;S3FILES_MOUNT_TARGET&lt;/code&gt; entra como variável de ambiente na &lt;strong&gt;criação da imagem&lt;/strong&gt;, não no lançamento. Variáveis de ambiente do MicroVM são resolvidas em build time e congelam no snapshot. Se eu passasse isso no &lt;code&gt;run-microvm&lt;/code&gt;, o &lt;code&gt;entrypoint.sh&lt;/code&gt; simplesmente não veria o valor, porque ele já rodou antes de qualquer parâmetro de lançamento existir.&lt;/p&gt;

&lt;p&gt;Outro ajuste: o provider do Terraform não expõe o DNS do mount target do S3 Files (diferente do EFS, que tem um DNS previsível por AZ). O jeito que encontrei foi ler o IP privado da ENI do mount target direto do Terraform e passar esse IP como valor da variável de ambiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# infra/microvm.tf (trecho)&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_network_interface"&lt;/span&gt; &lt;span class="s2"&gt;"s3files_mount_target"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"description"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"EFS mount target for fs-*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# o S3 Files reaproveita o padrão de descrição do EFS na ENI&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"subnet-id"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"null_resource"&lt;/span&gt; &lt;span class="s2"&gt;"microvm_image"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;triggers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;artifact_etag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_artifact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;etag&lt;/span&gt;
    &lt;span class="nx"&gt;mount_ip&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_network_interface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3files_mount_target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_ip&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/../scripts/create_microvm_image.sh"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;IMAGE_NAME&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;
      &lt;span class="nx"&gt;BASE_IMAGE_ARN&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:lambda:${var.aws_region}:aws:microvm-image:al2023-1"&lt;/span&gt;
      &lt;span class="nx"&gt;BUILD_ROLE_ARN&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;microvm_build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
      &lt;span class="nx"&gt;ARTIFACT_URI&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3://${aws_s3_bucket.artifact.id}/${aws_s3_object.app_artifact.key}"&lt;/span&gt;
      &lt;span class="nx"&gt;MOUNT_TARGET_IP&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_network_interface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3files_mount_target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_ip&lt;/span&gt;
      &lt;span class="nx"&gt;MIN_MEMORY_MIB&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;microvm_memory_mb&lt;/span&gt;
      &lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;
      &lt;span class="nx"&gt;OUTPUT_FILE&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/../.build/image_arn.txt"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;scripts/create_microvm_image.sh&lt;/code&gt; monta o comando de fato:&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-image &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-image-arn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASE_IMAGE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-role-arn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_ROLE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--code-artifact&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;uri&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARTIFACT_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--additional-os-capabilities&lt;/span&gt; &lt;span class="s1"&gt;'["ALL"]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--environment-variables&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;S3FILES_MOUNT_TARGET&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MOUNT_TARGET_IP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resources&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;minimumMemoryInMiB&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIN_MEMORY_MIB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reparei que a memória se divide em dois lugares diferentes do ciclo de vida: &lt;code&gt;minimumMemoryInMiB&lt;/code&gt; na criação da imagem define o piso (o mesmo piso de 512 MB que o S3 Files exige pra habilitar leitura direta do S3), e a política de idle, que é por lançamento, só aparece depois, no &lt;code&gt;run-microvm&lt;/code&gt;. Faz sentido: memória é característica da imagem (do jeito que a aplicação foi construída e testada), idle é característica de cada sessão.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;scripts/&lt;/code&gt; ficou só com os helpers que o Terraform chama (&lt;code&gt;create_microvm_image.sh&lt;/code&gt;, &lt;code&gt;create_network_connector.sh&lt;/code&gt;) e os de limpeza (&lt;code&gt;delete_microvm_image.sh&lt;/code&gt;, &lt;code&gt;delete_network_connector.sh&lt;/code&gt;, chamados no &lt;code&gt;terraform destroy&lt;/code&gt;). Nada de script de teste ali. Isso é o próximo passo, na mão.&lt;/p&gt;

&lt;p&gt;Pra provisionar tudo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make init &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make apply
&lt;span class="c"&gt;# equivalente a:&lt;/span&gt;
&lt;span class="c"&gt;# cd infra &amp;amp;&amp;amp; terraform init &amp;amp;&amp;amp; terraform apply&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.5 Rodando na mão: do lançamento ao teste
&lt;/h3&gt;

&lt;p&gt;O &lt;code&gt;docs/MANUAL.md&lt;/code&gt; documenta essa sequência inteira. Primeiro, pego os outputs do Terraform e lanço o MicroVM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;infra
&lt;span class="nv"&gt;IMAGE_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; microvm_image_arn&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;EXECUTION_ROLE_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; microvm_execution_role_arn&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;CONNECTOR_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; network_connector_arn&lt;span class="si"&gt;)&lt;/span&gt;

aws lambda-microvms run-microvm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-identifier&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-version&lt;/span&gt; &lt;span class="s2"&gt;"1.0"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--execution-role-arn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXECUTION_ROLE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--egress-network-connectors&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONNECTOR_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--idle-policy&lt;/span&gt; &lt;span class="s1"&gt;'{"maxIdleDurationSeconds":300,"suspendedDurationSeconds":1800,"autoResumeEnabled":true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem variável de ambiente aqui: ela já foi resolvida lá atrás, na imagem. O comando devolve &lt;code&gt;microvmId&lt;/code&gt; e &lt;code&gt;endpointUrl&lt;/code&gt;. Com isso, gero um token de curta duração:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws lambda-microvms create-microvm-auth-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--microvm-identifier&lt;/span&gt; &amp;lt;microvmId&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--expiration-in-minutes&lt;/span&gt; 30 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowed-ports&lt;/span&gt; &lt;span class="s1"&gt;'[{"port":5000}]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'authToken."X-aws-proxy-auth"'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;E testo o fluxo básico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"&amp;lt;endpointUrl&amp;gt;/health"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-proxy-auth: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&amp;lt;endpointUrl&amp;gt;/files"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-proxy-auth: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@notas.txt"&lt;/span&gt;

curl &lt;span class="s2"&gt;"&amp;lt;endpointUrl&amp;gt;/files"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-proxy-auth: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  2.6 Provando a durabilidade: terminate e relança
&lt;/h3&gt;

&lt;p&gt;Até aqui, nada muito diferente do disco local. A prova de verdade vem agora: em vez de deixar o MicroVM só suspender, eu &lt;strong&gt;termino ele por completo&lt;/strong&gt; e lanço um MicroVM novo, do zero, a partir da mesma imagem.&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 terminate-microvm &lt;span class="nt"&gt;--microvm-identifier&lt;/span&gt; &amp;lt;microvmId-original&amp;gt;

&lt;span class="c"&gt;# lança um MicroVM completamente novo, com um microvmId diferente&lt;/span&gt;
aws lambda-microvms run-microvm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-identifier&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-version&lt;/span&gt; &lt;span class="s2"&gt;"1.0"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--execution-role-arn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXECUTION_ROLE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--egress-network-connectors&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONNECTOR_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--idle-policy&lt;/span&gt; &lt;span class="s1"&gt;'{"maxIdleDurationSeconds":300,"suspendedDurationSeconds":1800,"autoResumeEnabled":true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gero um token novo pro &lt;code&gt;microvmId&lt;/code&gt; novo, e chamo &lt;code&gt;GET /files&lt;/code&gt; de novo:&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%2F7w66hybedqz8th3ihpuy.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%2F7w66hybedqz8th3ihpuy.png" alt=" " width="800" height="654"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;notas.txt&lt;/code&gt; aparece no &lt;code&gt;GET /files&lt;/code&gt; do MicroVM novo, com um &lt;code&gt;microvmId&lt;/code&gt; que nunca existiu antes. O snapshot desse MicroVM não tinha nenhuma referência a esse arquivo, porque o arquivo nunca esteve no disco local pra começo de conversa. Ele sempre foi um objeto no bucket S3 por trás, só sendo lido através do mount do S3 Files.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.7 Limpando
&lt;/h3&gt;

&lt;p&gt;Antes do &lt;code&gt;terraform destroy&lt;/code&gt;, preciso terminar qualquer MicroVM ativo na mão:&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 terminate-microvm &lt;span class="nt"&gt;--microvm-identifier&lt;/span&gt; &amp;lt;microvmId-atual&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O motivo é de rede, não de custo: um MicroVM em execução mantém uma interface de rede anexada às subnets privadas via egress network connector, e o destroy da VPC falha enquanto essa ENI ainda existir. Só depois disso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make destroy
&lt;span class="c"&gt;# equivalente a:&lt;/span&gt;
&lt;span class="c"&gt;# cd infra &amp;amp;&amp;amp; terraform destroy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3.0 O que eu levo em conta ao usar isso
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Durabilidade e estado
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;S3 Files resolve um problema que nem suspensão longa nem export manual resolvem bem sozinhos.&lt;/strong&gt; Disco local sobrevive suspensão mas está preso ao teto de 8 horas e ao ciclo de vida daquele MicroVM específico. Escrever direto no bucket via boto3 funciona, mas exige um passo explícito de upload que é fácil de esquecer. Montar S3 Files tira essa escolha do meio: a escrita já cai como objeto no bucket, sem passo extra, e ainda por cima falando com o S3, que é o storage que eu já confio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;512 MB de memória é o corte pra leitura direta do S3, e agora isso vive na imagem, não no lançamento.&lt;/strong&gt; Abaixo desse valor o mount ainda funciona, mas sem o caminho otimizado de leitura direta do S3 Files. Definir &lt;code&gt;minimumMemoryInMiB&lt;/code&gt; na criação da imagem em vez de deixar solto no &lt;code&gt;run-microvm&lt;/code&gt; evita que alguém lance um MicroVM subdimensionado sem perceber.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Segurança e acesso
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A capability de montagem é opt-in por um motivo, e eu penso duas vezes antes de habilitar.&lt;/strong&gt; &lt;code&gt;--additional-os-capabilities '["ALL"]'&lt;/code&gt; amplia bastante o que o container consegue fazer dentro do MicroVM, não é só liberação pontual pra mount. Vale pesar isso contra o quanto de isolamento eu realmente preciso pro caso de uso. Pra rodar código gerado por IA de origem desconhecida, por exemplo, eu penso duas vezes antes de dar essa capability extra.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permissão de S3 Files é separada de permissão de S3 comum, e agora percebi que são três papéis, não dois.&lt;/strong&gt; &lt;code&gt;s3files:ClientMount&lt;/code&gt; e &lt;code&gt;s3files:ClientWrite&lt;/code&gt; na role de execução do MicroVM cobrem o acesso via NFS. Mas o próprio S3 Files também precisa de uma role de sync (assumindo &lt;code&gt;elasticfilesystem.amazonaws.com&lt;/code&gt;) pra escrever de volta no bucket, com versionamento obrigatório. Três papéis diferentes fazendo três coisas diferentes, fácil de confundir se você já tem uma policy de S3 pronta e assume que ela cobre tudo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rede não é implícita, é uma peça que eu declaro.&lt;/strong&gt; Sem o egress network connector apontando pra minha VPC, o MicroVM simplesmente não alcança o mount target do S3 Files. Isso é bom: rede que só existe porque eu declarei explicitamente é mais fácil de auditar do que rede que "só funciona" por padrão.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Padrões de implementação
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Variável de ambiente pro mount vai na imagem, não no lançamento, e isso é fácil de errar uma vez.&lt;/strong&gt; Da primeira vez que tentei passar &lt;code&gt;S3FILES_MOUNT_TARGET&lt;/code&gt; no &lt;code&gt;run-microvm&lt;/code&gt;, o &lt;code&gt;entrypoint.sh&lt;/code&gt; simplesmente não via o valor. Variáveis de ambiente do MicroVM são resolvidas em build time, junto com o resto do snapshot. Isso é consistente com a lição do post anterior sobre estado congelado, só que na direção contrária: aqui eu &lt;em&gt;preciso&lt;/em&gt; que congele, porque é informação de infraestrutura, não de sessão.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O provider do Terraform não expõe DNS do mount target do S3 Files, só a ENI.&lt;/strong&gt; Diferente do EFS, que tem um endpoint DNS previsível, tive que resolver o IP privado da interface de rede via &lt;code&gt;data "aws_network_interface"&lt;/code&gt; filtrando pela descrição e pela subnet. Funciona, mas é um detalhe frágil: se a AWS mudar o padrão de descrição da ENI, esse filtro quebra.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Montar em runtime (dentro do container), não em build time (na instrução &lt;code&gt;RUN&lt;/code&gt; do Dockerfile), continua valendo.&lt;/strong&gt; O mount em si ainda acontece no &lt;code&gt;entrypoint.sh&lt;/code&gt; quando o container sobe, porque o mount target só é alcançável quando a rede da imagem já está configurada. A variável de ambiente pode congelar no build, a chamada de &lt;code&gt;mount&lt;/code&gt; não pode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Múltiplos MicroVMs podem montar o mesmo file system ao mesmo tempo, e isso muda o desenho.&lt;/strong&gt; Diferente do disco local, que é exclusivo daquele MicroVM, o S3 Files permite compartilhar workspace entre sessões diferentes se fizer sentido pro seu caso. Vale filtrar por sessão dentro da aplicação (um prefixo de diretório por usuário, por exemplo) pra não misturar dado de gente diferente sem querer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NFS tem custo de latência que disco local não tem.&lt;/strong&gt; Toda leitura e escrita passa pela rede até o mount target. Pra arquivo pequeno e acesso frequente isso raramente é perceptível, mas pra workload de I/O intenso vale medir antes de assumir que dá na mesma.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Observabilidade
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Powertools funciona em MicroVMs, mas nem todo pedaço faz sentido do mesmo jeito.&lt;/strong&gt; O &lt;code&gt;Logger&lt;/code&gt; estruturado em JSON é ganho direto, sem precisar escrever formatter na mão. Mas &lt;code&gt;inject_lambda_context&lt;/code&gt;, que injeta &lt;code&gt;cold_start&lt;/code&gt; e &lt;code&gt;function_request_id&lt;/code&gt; automaticamente, foi desenhado pra um &lt;code&gt;lambda_handler(event, context)&lt;/code&gt; que roda uma vez por invocação. Aqui é um servidor FastAPI de longa duração, sem um &lt;code&gt;context&lt;/code&gt; novo a cada request, então esse pedaço específico eu deixei de fora. Vale essa checagem sempre que trouxer uma lib pensada pra Function pra dentro de um MicroVM: parte do contrato pode não existir mais.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.5 Arquitetura e deploy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Terraform e SAM/CloudFormation não competem aqui, cada um cobre uma parte diferente do mesmo problema.&lt;/strong&gt; SAM tem recurso nativo pra &lt;code&gt;AWS::Lambda::MicrovmImage&lt;/code&gt; desde o lançamento, o provider Terraform da AWS, não. Daí o &lt;code&gt;null_resource&lt;/code&gt; + &lt;code&gt;local-exec&lt;/code&gt; como ponte enquanto o &lt;a href="https://github.com/hashicorp/terraform-provider-aws/issues/48526" rel="noopener noreferrer"&gt;issue #48526&lt;/a&gt; não fecha. Em compensação, o Terraform já tem recursos nativos pra S3 Files (&lt;code&gt;aws_s3files_*&lt;/code&gt;) desde a v6.40.0, o que deixa o resto do laboratório mais direto de escrever do que ficaria em CloudFormation puro. Escolher a stack de IaC também é escolher onde você aceita workaround (palavra chique para gambiarra).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagem entra no pipeline de IaC, lançamento não, e isso ficou ainda mais explícito nessa versão do repositório.&lt;/strong&gt; O &lt;code&gt;Makefile&lt;/code&gt; só tem &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt; e &lt;code&gt;destroy&lt;/code&gt;, de propósito, sem &lt;code&gt;run&lt;/code&gt;, sem &lt;code&gt;test&lt;/code&gt;. Lançar um MicroVM é decisão de runtime, documentada em &lt;code&gt;docs/MANUAL.md&lt;/code&gt; como sequência manual de comandos, não escondida atrás de um alvo de Makefile que faria parecer parte do provisionamento.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MicroVMs e Functions não competem, se complementam.&lt;/strong&gt; O próprio anúncio da AWS deixa isso explícito: Functions seguem sendo a escolha certa pro backbone evento-resposta da aplicação, e MicroVMs entra especificamente na etapa que precisa rodar código não confiável (gerado por usuário ou por IA) isolado, com estado que precisa sobreviver além de uma única execução.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.6 Limites honestos deste laboratório
&lt;/h3&gt;

&lt;p&gt;Não tem CI rodando isso. Os nomes de alguns parâmetros de CLI e os principals de trust policy (&lt;code&gt;elasticfilesystem.amazonaws.com&lt;/code&gt; pro papel de sync, por exemplo) eu confirmei o suficiente pra funcionar no meu teste, mas não bati contra a referência oficial linha por linha. Vale conferir antes de replicar em produção. A criação de imagem não é totalmente idempotente: rodar &lt;code&gt;create_microvm_image.sh&lt;/code&gt; duas vezes cria uma versão nova em vez de atualizar in-place. E a ponte via &lt;code&gt;null_resource&lt;/code&gt; + &lt;code&gt;local-exec&lt;/code&gt; pro image/connector é workaround assumido, não o caminho idiomático. Se seu time prioriza Terraform 100% nativo pra esse pedaço específico, SAM ainda é a opção mais direta hoje.&lt;/p&gt;




&lt;h2&gt;
  
  
  4.0 Conclusão
&lt;/h2&gt;

&lt;p&gt;O que mais me chamou atenção nesse anúncio foi perceber que a AWS não criou uma tecnologia nova, ela deu acesso direto a uma peça que já sustentava o Lambda por trás dos panos há anos. Isso é coerente quando se entende a forma que o Firecracker funciona: entender a engenharia por baixo ajuda a prever pra onde o produto vai. E o Powertools que usei aqui pro Logger é o mesmo que detalhei &lt;a href="https://dev.to/broettod/observabilidade-serverless-sem-surpresa-no-bolso-logs-metricas-e-traces-com-cloudwatch-x-ray-do-1hb9"&gt;naquele post sobre observabilidade serverless&lt;/a&gt;. O Logger continua valendo em MicroVM, só o contrato de invocação que muda. Trocar SAM por Terraform pra montar esse laboratório não mudou a história que o artigo conta, só me obrigou a ser mais explícito sobre o que cada stack cobre nativamente e onde ainda falta peça. Pensei em testar isso e ver até onde dá pra empurrar: rodar um agente de IA gerando e executando código de verdade dentro de um MicroVM com esse mesmo workspace compartilhado via S3 Files, e medir o custo real de manter várias sessões vivas ao mesmo tempo, isso pode gerar um novo artigo pra compartilhar.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>serverless</category>
      <category>firecracker</category>
    </item>
    <item>
      <title>Observabilidade serverless sem surpresa no bolso: logs, métricas e traces com CloudWatch + X-Ray do jeito certo</title>
      <dc:creator>Diego Broetto</dc:creator>
      <pubDate>Thu, 16 Jul 2026 19:50:43 +0000</pubDate>
      <link>https://dev.to/broettod/observabilidade-serverless-sem-surpresa-no-bolso-logs-metricas-e-traces-com-cloudwatch-x-ray-do-1hb9</link>
      <guid>https://dev.to/broettod/observabilidade-serverless-sem-surpresa-no-bolso-logs-metricas-e-traces-com-cloudwatch-x-ray-do-1hb9</guid>
      <description>&lt;p&gt;Tem uma fatura de CloudWatch que eu nunca vou esquecer.&lt;/p&gt;

&lt;p&gt;Uma função Lambda simples, rodando há algumas semanas em produção. Sem grande volume. Sem nada de especial. Só que alguém tinha esquecido de configurar retenção de log, o X-Ray estava com sampling em 100%, e o log level estava em DEBUG porque "vou ajustar depois". A conta veio quase três vezes o esperado.&lt;/p&gt;

&lt;p&gt;O pior não foi o valor. Foi saber que meu cliente tinha pagado para guardar lixo. Stack trace de erro que já estava resolvido. Trace de requisição que durou 12ms e nunca mais ninguém ia abrir. Log de evento completo com payload de cliente em texto plano num log group que ia existir pra sempre.&lt;/p&gt;

&lt;p&gt;Observabilidade serverless não é caro por natureza. Fica caro quando você liga tudo no máximo e esquece. Este artigo é sobre não esquecer.&lt;/p&gt;




&lt;h2&gt;
  
  
  1.0 O problema específico do serverless
&lt;/h2&gt;

&lt;p&gt;Em servidores tradicionais, você tem SSH. Tem processo rodando. Tem &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;htop&lt;/code&gt;, &lt;code&gt;journalctl&lt;/code&gt;. Quando algo dá errado, você entra na máquina e olha.&lt;/p&gt;

&lt;p&gt;No Lambda, não tem nada disso. A função executa em milissegundos, o container some, e você ficou com o que colocou no log. Se não colocou nada útil, ou colocou tudo de forma errada, você está debugando no escuro.&lt;/p&gt;

&lt;p&gt;Aí vem o impulso natural: loga tudo. Ativa X-Ray em todas as funções. Cria métrica para cada coisa que se move. Funciona. Mas o preço aparece no fim do mês.&lt;/p&gt;

&lt;p&gt;A questão não é observar menos. É observar o que importa.&lt;/p&gt;




&lt;h2&gt;
  
  
  2.0 Os três pilares e o que cada um custa de verdade
&lt;/h2&gt;

&lt;p&gt;Antes de falar de implementação, vale entender a tabela de preços do que você está usando.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudWatch Logs&lt;/strong&gt; cobra em três momentos distintos: ingestão ($0,50/GB), armazenamento ($0,03/GB/mês) e consultas no Logs Insights ($0,005/GB escaneado). O armazenamento parece barato até você calcular que o padrão de retenção é "nunca expirar". Um log group de função Lambda com volume médio, sem retenção configurada, acumula sem parar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudWatch Metrics&lt;/strong&gt; tem métricas padrão do Lambda gratuitas. São muitas e cobrem bem o essencial: &lt;code&gt;Duration&lt;/code&gt;, &lt;code&gt;Errors&lt;/code&gt;, &lt;code&gt;Throttles&lt;/code&gt;, &lt;code&gt;ConcurrentExecutions&lt;/code&gt;, &lt;code&gt;IteratorAge&lt;/code&gt; para Streams. O custo começa quando você cria métricas customizadas: $0,30 por métrica por mês. Soa pouco. Mas se você criar uma métrica por &lt;code&gt;tenant_id&lt;/code&gt; com 500 tenants, são $150/mês só de métricas, sem incluir as chamadas de API para publicá-las.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;X-Ray&lt;/strong&gt; cobra $5,00 por 1 milhão de traces registrados e $0,50 por 1 milhão consultados. O sampling padrão da AWS é 1 requisição por segundo mais 5% do restante. Para a maioria das cargas, está OK. Para uma função processando 10 mil req/seg, 5% são 500 traces por segundo, 43 milhões por mês, $215 só de traces.&lt;/p&gt;

&lt;p&gt;Esses números não são argumentos contra usar essas ferramentas. São argumentos para configurá-las de forma intencional.&lt;/p&gt;




&lt;h2&gt;
  
  
  3.0 Logs: estrutura antes de volume
&lt;/h2&gt;

&lt;p&gt;O problema mais comum que vejo não é o volume de logs. É o formato.&lt;/p&gt;

&lt;p&gt;Log em texto livre não serve em escala. Você não consegue filtrar, não consegue agregar, não consegue criar métricas a partir dele. Funciona para debug manual no console, mas quebra quando você precisa entender o que aconteceu às 3h da manhã em 200 invocações simultâneas.&lt;/p&gt;

&lt;p&gt;JSON é o formato certo. O Lambda suporta nativamente desde 2023:&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;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;LoggingConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;LogFormat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;JSON&lt;/span&gt;
      &lt;span class="na"&gt;ApplicationLogLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;INFO&lt;/span&gt;
      &lt;span class="na"&gt;SystemLogLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WARN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com isso, cada log já sai estruturado com &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;level&lt;/code&gt;, &lt;code&gt;requestId&lt;/code&gt; e a mensagem. Você ainda precisa estruturar o que coloca no log, mas o envelope já está pronto.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Powertools Logger: o que ele faz que você não quer fazer na mão
&lt;/h3&gt;

&lt;p&gt;O AWS Lambda Powertools tem um &lt;code&gt;Logger&lt;/code&gt; que, além de JSON, injeta contexto da invocação automaticamente:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pedidos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@logger.inject_lambda_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pedido recebido&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pedido_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que aparece no log:&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;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INFO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pedido recebido"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pedido_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"valor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;179.80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cold_start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pedidos-criar-prod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_memory_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_arn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:lambda:..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-07-14T10:23:41.123Z"&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;"pedidos"&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;Exemplo no console:&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%2Fqiebsavu8pz8asi5603g.webp" 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%2Fqiebsavu8pz8asi5603g.webp" alt=" " width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cold_start&lt;/code&gt; está lá de graça. &lt;code&gt;function_request_id&lt;/code&gt; está lá. Tudo que você precisaria escrever na mão já vem junto.&lt;/p&gt;

&lt;p&gt;Um detalhe importante: &lt;code&gt;log_event=False&lt;/code&gt;. Com &lt;code&gt;True&lt;/code&gt;, o payload completo do evento vai para o log. Útil em desenvolvimento. Em produção, isso significa que dados do cliente vão para o CloudWatch em texto plano, e você vai pagar para armazenar informação que provavelmente não deveria estar lá. Controle isso por variável de ambiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@logger.inject_lambda_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LOG_EVENT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No template SAM:&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;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;LOG_EVENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!If&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;IsDev&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2 append_keys: propagando contexto sem repetição
&lt;/h3&gt;

&lt;p&gt;Dentro do handler, você frequentemente quer que o &lt;code&gt;pedido_id&lt;/code&gt; apareça em todos os logs daquela invocação, não só no primeiro. O &lt;code&gt;append_keys&lt;/code&gt; faz isso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pedido_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pathParameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pedido_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pedido_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Daqui pra frente, todo log inclui pedido_id automaticamente
&lt;/span&gt;    &lt;span class="n"&gt;pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buscar_pedido&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pedido encontrado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.3 Retenção: configure no primeiro deploy, não depois
&lt;/h3&gt;

&lt;p&gt;O padrão do Lambda é criar o log group automaticamente com retenção "Nunca expirar". Isso parece inofensivo até você perceber que é um bug, não uma feature.&lt;/p&gt;

&lt;p&gt;Configure via CloudFormation/SAM para não depender de lembrar:&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;PedidosLogGroup&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::Logs::LogGroup&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;LogGroupName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/aws/lambda/pedidos-criar-${Environment}"&lt;/span&gt;
    &lt;span class="na"&gt;RetentionInDays&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;LogRetentionDays&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com um parâmetro &lt;code&gt;LogRetentionDays&lt;/code&gt; que varia por ambiente: 7 dias em dev, 30 em staging, 90 em prod. Ou o que compliance exigir, desde que não seja infinito.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Log level por ambiente
&lt;/h3&gt;

&lt;p&gt;DEBUG em produção é a forma mais eficiente de dobrar sua conta de logs. Controle via Mappings no SAM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Mappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;EnvConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;LogLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEBUG&lt;/span&gt;
    &lt;span class="na"&gt;staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;LogLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;INFO&lt;/span&gt;
    &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;LogLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;INFO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4.0 Um debug real: dois bugs atrás de uma mensagem genérica
&lt;/h2&gt;

&lt;p&gt;Isso aconteceu comigo enquanto preparava o projeto de exemplo deste artigo. Deixo aqui porque é um caso melhor do que qualquer exemplo que eu inventaria.&lt;/p&gt;

&lt;p&gt;Depois do primeiro deploy bem-sucedido, testei o &lt;code&gt;POST /pedidos&lt;/code&gt; e recebi:&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="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Internal Erro. Try again."&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;Sem mais detalhes. E propositalmente sem mais detalhes o handler captura qualquer exceção genérica e devolve essa mensagem amigável de propósito, porque uma API não deveria vazar stack trace para quem está do outro lado. Isso é certo do ponto de vista de segurança. Também significa que, sem observabilidade decente no backend, você fica cego.&lt;/p&gt;

&lt;p&gt;Fui direto para o CloudWatch Logs Insights:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"ERROR"&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ocorrencias&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;tipo_erro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operacao&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt; &lt;span class="n"&gt;ocorrencias&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o log estruturado entregou na hora:&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;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operacao"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"criar_pedido"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tipo_erro"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TypeError"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mensagem_erro"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Float types are not supported. Use Decimal types instead."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pedido_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O DynamoDB, via boto3, não aceita &lt;code&gt;float&lt;/code&gt; nativo do Python, só &lt;code&gt;Decimal&lt;/code&gt;. O handler estava salvando &lt;code&gt;valor&lt;/code&gt; (&lt;code&gt;179.80&lt;/code&gt;) direto como veio do JSON. A função &lt;code&gt;processar_pedido&lt;/code&gt; já fazia essa conversão certinho, só a &lt;code&gt;criar_pedido&lt;/code&gt; tinha escapado. Corrigi convertendo para &lt;code&gt;Decimal(str(valor))&lt;/code&gt; apenas no item que vai para o DynamoDB, mantendo &lt;code&gt;float&lt;/code&gt; no dict usado para log, métrica e resposta HTTP, não queria efeito colateral na serialização JSON por causa de um tipo que só o DynamoDB exige.&lt;/p&gt;

&lt;p&gt;Testei de novo. Erro genérico de novo. Voltei no Insights, mesma query, e apareceu um segundo erro, completamente diferente do primeiro:&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;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operacao"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"criar_pedido"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tipo_erro"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ClientError"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mensagem_erro"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User: .../pedidos-criar-dev is not authorized to perform: events:PutEvents on resource: arn:aws:events:us-east-1:...:event-bus/default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pedido_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&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;Esse foi mais sutil. O código tinha &lt;code&gt;os.environ.get("EVENT_BUS_NAME", "default")&lt;/code&gt;, um fallback razoável à primeira vista. O problema é que a variável &lt;code&gt;EVENT_BUS_NAME&lt;/code&gt; nunca tinha sido definida no template, então o fallback sempre disparava, e o código tentava publicar no event bus &lt;em&gt;padrão&lt;/em&gt; da conta. A IAM policy, corretamente, só liberava &lt;code&gt;PutEvents&lt;/code&gt; no bus customizado que o projeto realmente usa. Fallback silencioso mascarando uma variável de ambiente esquecida o tipo de bug que passa no code review porque parece defensivo.&lt;/p&gt;

&lt;p&gt;O que faz esse caso valer a pena contar não é nenhum dos dois bugs isoladamente. É que sem &lt;code&gt;tipo_erro&lt;/code&gt; e &lt;code&gt;mensagem_erro&lt;/code&gt; como campos estruturados e pesquisáveis, eu estaria lendo texto corrido no console tentando adivinhar onde, entre duas invocações, a causa mudou completamente. Com log estruturado, foram duas queries idênticas, dois resultados diferentes, dois fixes de dez minutos cada.&lt;/p&gt;

&lt;p&gt;É fácil ler a seção anterior sobre Powertools Logger e achar que &lt;code&gt;pedido_id&lt;/code&gt; e &lt;code&gt;cold_start&lt;/code&gt; automáticos são só conveniência. Na prática é a diferença entre filtrar por campo e grep em prosa.&lt;/p&gt;




&lt;h2&gt;
  
  
  5.0 Métricas: use o que é de graça antes de criar o que custa
&lt;/h2&gt;

&lt;p&gt;Tem muita coisa útil antes de você precisar criar uma métrica customizada.&lt;/p&gt;

&lt;p&gt;As métricas padrão do Lambda que mais uso no dia a dia:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Errors&lt;/code&gt; e &lt;code&gt;ErrorRate&lt;/code&gt;: taxa de erro por função. Bom para alarme de SLA.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Duration&lt;/code&gt; com estatísticas p50, p90, p99: distribuição de latência. Nunca use só a média, ela esconde outliers. Uma função com p50 de 100ms e p99 de 8000ms está com problema que a média não mostra.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Throttles&lt;/code&gt;: qualquer valor acima de zero em produção merece atenção imediata.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConcurrentExecutions&lt;/code&gt;: para detectar burst antes de virar throttle.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IteratorAge&lt;/code&gt;: se você tem função consumindo Kinesis ou DynamoDB Streams, isso é o lag do consumer. Subindo significa que você não está acompanhando o volume.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Todas gratuitas. Se essas cobrem o que você precisa, pare aqui.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Embedded Metric Format: métricas customizadas sem overhead de API
&lt;/h3&gt;

&lt;p&gt;Quando você precisa de métrica customizada, o caminho certo é EMF, não &lt;code&gt;put_metric_data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;put_metric_data&lt;/code&gt; faz uma chamada de API para o CloudWatch a cada invocação. Além do custo das chamadas, é uma dependência extra que pode falhar e adiciona latência.&lt;/p&gt;

&lt;p&gt;O EMF funciona diferente: você emite a métrica dentro do log JSON em um formato especial. O CloudWatch processa o log e cria a métrica automaticamente. Sem chamada de API extra, sem latência adicional.&lt;/p&gt;

&lt;p&gt;Com o Powertools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_powertools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Metrics&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_powertools.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MetricUnit&lt;/span&gt;

&lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MeuApp/Pedidos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pedidos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@metrics.log_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capture_cold_start_metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Isso vira uma métrica CloudWatch via EMF — sem put_metric_data
&lt;/span&gt;    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PedidoProcessado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MetricUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_dimension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aprovado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;capture_cold_start_metric=True&lt;/code&gt; emite uma métrica &lt;code&gt;ColdStart&lt;/code&gt; automaticamente. De graça, sem código extra.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 A armadilha das dimensões de alta cardinalidade
&lt;/h3&gt;

&lt;p&gt;Métricas customizadas com dimensão de alta cardinalidade são uma das formas mais rápidas de explodir o custo do CloudWatch.&lt;/p&gt;

&lt;p&gt;Exemplo: você tem 1000 clientes e quer ver quantos pedidos cada um fez.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ERRADO — 1000 métricas × $0,30 = $300/mês
&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_dimension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ClienteId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cliente_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PedidoCliente&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MetricUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para esse caso, use Logs Insights:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="n"&gt;mensagem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"Pedido processado"&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;cliente_id&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A consulta custa por GB escaneado, mas você só roda quando precisa. Dimensões de métrica fazem sentido quando a cardinalidade é baixa: status, tipo, ambiente, região. Não por cliente, não por produto individual, não por request ID.&lt;/p&gt;




&lt;h2&gt;
  
  
  6.0 X-Ray: o que ele mostra que os logs não mostram
&lt;/h2&gt;

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

&lt;p&gt;Os logs dizem que uma invocação levou 800ms. O X-Ray diz que desses 800ms, 12ms foram no seu código, 750ms foram esperando o DynamoDB responder, e os outros 38ms foram no cold start.&lt;/p&gt;

&lt;p&gt;Essa diferença muda completamente onde você vai olhar para resolver o problema.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 capture_method: subsegmentos sem boilerplate
&lt;/h3&gt;

&lt;p&gt;Com o Powertools Tracer, você adiciona subsegmentos com um decorator:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pedidos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@tracer.capture_lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capture_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_parse_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;_salvar_pedido&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;_publicar_evento&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@tracer.capture_method&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_salvar_pedido&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# O X-Ray vai mostrar exatamente quanto tempo este método levou
&lt;/span&gt;    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@tracer.capture_method&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_publicar_evento&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;eventbridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_events&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;capture_response=False&lt;/code&gt; evita que o response completo vá para o trace, o que pode incluir dados sensíveis e aumentar custo de armazenamento.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 put_annotation: filtrando traces que importam
&lt;/h3&gt;

&lt;p&gt;Anotações no X-Ray permitem filtrar traces por valor de negócio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_annotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status_pedido&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_annotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor_acima_limite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No console do X-Ray, você consegue filtrar &lt;code&gt;annotation.status_pedido = "rejeitado"&lt;/code&gt; e ver só os traces de pedidos rejeitados. Muito mais útil do que varrer logs para encontrar o mesmo padrão.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Sampling rules: a configuração que mais gente ignora
&lt;/h3&gt;

&lt;p&gt;O padrão da AWS é 1 req/seg fixo mais 5% do restante. Para a maioria das funções está OK. O problema é quando você tem funções de alto volume.&lt;/p&gt;

&lt;p&gt;Configure sampling rules por função ou por padrão de ARN:&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;"RuleName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HighVolumeFunction"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"FixedRate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ReservoirSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ServiceName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pedidos-processar-*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ResourceARN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1% para função de alto volume que você já conhece bem é diferente de 5% para uma função nova que pode ter comportamento inesperado. Trate isso por função, não como configuração global.&lt;/p&gt;

&lt;p&gt;Tem casos onde faz sentido desligar o X-Ray completamente: função de health check, processamento homogêneo de fila onde todos os traces são idênticos, função de baixa complexidade sem downstream calls. Nesses casos, logs estruturados com &lt;code&gt;Duration&lt;/code&gt; no CloudWatch cobrem o que importa.&lt;/p&gt;




&lt;h2&gt;
  
  
  7.0 Alarmes: o que monitorar de verdade
&lt;/h2&gt;

&lt;p&gt;Um dashboard sem alarme é decoração. Você precisa ser avisado quando algo sai do normal, não descobrir na próxima vez que abrir o console.&lt;/p&gt;

&lt;p&gt;Três alarmes que eu considero mínimo obrigatório por função crítica:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Taxa de erro:&lt;/strong&gt; monitore &lt;code&gt;Errors&lt;/code&gt;, não &lt;code&gt;ErrorRate&lt;/code&gt;, porque &lt;code&gt;ErrorRate&lt;/code&gt; é percentual e pode mascarar volume real. Se você tem 0,1% de erro em 1 milhão de invocações, são 1000 erros. Coloque um alarme absoluto e um relativo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latência p99:&lt;/strong&gt; não p50, não média. O p99 mostra o que seus usuários mais lentos estão experimentando. Um alarme em 80% do timeout configurado dá tempo de agir antes de começar a ter timeouts reais:&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;CriarPedidoDurationAlarm&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::CloudWatch::Alarm&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;MetricName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Duration&lt;/span&gt;
    &lt;span class="na"&gt;ExtendedStatistic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;p99&lt;/span&gt;
    &lt;span class="na"&gt;Threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24000&lt;/span&gt;  &lt;span class="c1"&gt;# 80% de 30s = 24000ms&lt;/span&gt;
    &lt;span class="na"&gt;ComparisonOperator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GreaterThanThreshold&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Throttles:&lt;/strong&gt; qualquer valor acima de zero em produção. Throttle significa que você está descartando tráfego. É uma emergência, não uma métrica para acompanhar.&lt;/p&gt;




&lt;h2&gt;
  
  
  8.0 Dashboard: visão consolidada, não por função
&lt;/h2&gt;

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

&lt;p&gt;Um dashboard por função não serve. Quando algo dá errado em produção, você quer ver o sistema inteiro, não abrir 12 dashboards em abas diferentes.&lt;/p&gt;

&lt;p&gt;Monte um dashboard por stack ou por domínio de negócio com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Taxa de erro de todas as funções em um gráfico só&lt;/li&gt;
&lt;li&gt;Latência p99 de todas as funções&lt;/li&gt;
&lt;li&gt;Invocações totais por função&lt;/li&gt;
&lt;li&gt;Throttles&lt;/li&gt;
&lt;li&gt;Métricas de negócio via EMF (pedidos criados, processados, rejeitados)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O template SAM do projeto de exemplo gera esse dashboard via CloudFormation. Nada para configurar no console, versionado junto com a infraestrutura.&lt;/p&gt;




&lt;h2&gt;
  
  
  9.0 Checklist antes de ir para produção
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] LogFormat: JSON em todas as funções (template Globals)
[ ] ApplicationLogLevel: INFO em prod (nunca DEBUG)
[ ] log_event=False em prod (controle por variável de ambiente)
[ ] RetentionInDays configurado em todos os log groups
[ ] Métricas de Duration usando p99, não média
[ ] Sampling rule do X-Ray revisada para funções de alto volume
[ ] capture_response=False no Tracer em prod
[ ] Alarme em Errors (absoluto)
[ ] Alarme em Duration p99 (&amp;gt; 80% do timeout)
[ ] Alarme em Throttles (qualquer valor &amp;gt; 0)
[ ] Dashboard consolidado por stack
[ ] Budget Alert no Cost Explorer separando CloudWatch dos outros serviços
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O último item parece paranoia até deixar de ser.&lt;/p&gt;




&lt;h2&gt;
  
  
  10.0 Projeto de exemplo
&lt;/h2&gt;

&lt;p&gt;O repositório que acompanha este artigo tem uma API de pedidos completa com quatro funções Lambda demonstrando cada conceito na prática:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;criar_pedido&lt;/code&gt;: POST /pedidos com Logger, Tracer e Metrics configurados&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;consultar_pedido&lt;/code&gt;: GET /pedidos/{id} com anotações X-Ray e métricas de hit/miss&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;processar_pedido&lt;/code&gt;: consumidor EventBridge com métricas de negócio por dimensão de status&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;decidir_pedido&lt;/code&gt;: PATCH /pedidos/{id}/aprovar e /rejeitar, aprovação e rejeição manual de pedidos que ficaram em &lt;code&gt;aguardando_aprovacao&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quarta função existe porque a classificação automática por valor deixava um estado sem saída: pedidos na faixa intermediária ficavam parados em &lt;code&gt;aguardando_aprovacao&lt;/code&gt; sem nenhum jeito de sair dali. As duas rotas aprovar e rejeitar são atendidas pela mesma Lambda, com a ação derivada do path da requisição. Menos uma função duplicada, menos um cold start a mais para gerenciar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regra de negócio (classificação automática por valor):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Valor do pedido&lt;/th&gt;
&lt;th&gt;Status resultante&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;≤ R$ 500,00&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;aprovado&lt;/code&gt; (automático)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R$ 500,01 – R$ 5.000,00&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;aguardando_aprovacao&lt;/code&gt; (precisa de &lt;code&gt;/aprovar&lt;/code&gt; ou &lt;code&gt;/rejeitar&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt; R$ 5.000,00&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;rejeitado&lt;/code&gt; (automático)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Método&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Função&lt;/th&gt;
&lt;th&gt;Notas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/pedidos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;criar_pedido&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Publica &lt;code&gt;PedidoCriado&lt;/code&gt; no EventBridge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/pedidos/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;consultar_pedido&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;404 se não existir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATCH&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/pedidos/{id}/aprovar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;decidir_pedido&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;409 se o pedido não estiver em &lt;code&gt;aguardando_aprovacao&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATCH&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/pedidos/{id}/rejeitar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;decidir_pedido&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;409 se o pedido não estiver em &lt;code&gt;aguardando_aprovacao&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A transição de status usa &lt;code&gt;ConditionExpression&lt;/code&gt; no &lt;code&gt;update_item&lt;/code&gt; do DynamoDB o mesmo padrão de idempotência do &lt;code&gt;processar_pedido&lt;/code&gt; para garantir que a decisão só é aplicada uma vez, mesmo com requisições concorrentes. Quando a condição falha, uma leitura simples desambigua entre "pedido não existe" (404) e "pedido já está em outro status" (409, com o status atual na mensagem).&lt;/p&gt;

&lt;p&gt;A observabilidade segue o mesmo padrão das outras funções: métrica EMF &lt;code&gt;PedidoDecididoManualmente&lt;/code&gt; com dimensão &lt;code&gt;Decisao&lt;/code&gt; (aprovado/rejeitado), anotação X-Ray &lt;code&gt;decisao_manual&lt;/code&gt;, e logs estruturados com &lt;code&gt;pedido_id&lt;/code&gt;, &lt;code&gt;acao&lt;/code&gt; e &lt;code&gt;novo_status&lt;/code&gt;. O dashboard ganhou um widget novo para essas decisões, e o widget "Pedidos por Status" foi corrigido para incluir a série &lt;code&gt;aguardando_aprovacao&lt;/code&gt; faltava no dashboard original, o que por si só é um bom lembrete de revisar dashboard quando o domínio de negócio ganha um status novo.&lt;/p&gt;

&lt;p&gt;O template SAM inclui log groups com retenção, alarmes em p99 e throttles, dashboard CloudWatch e Mappings de configuração por ambiente.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build &lt;span class="nt"&gt;--use-container&lt;/span&gt;
sam deploy &lt;span class="nt"&gt;--config-env&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As queries prontas para o Logs Insights estão em &lt;code&gt;monitoring/insights-queries.md&lt;/code&gt; no repositório.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O que este exemplo ainda não cobre&lt;/strong&gt;, para ser honesto: os alarmes de erro e duração hoje existem só para &lt;code&gt;criar_pedido&lt;/code&gt;, &lt;code&gt;decidir_pedido&lt;/code&gt; e &lt;code&gt;consultar_pedido&lt;/code&gt; ainda não têm réplica. Também não há notificação (SNS ou e-mail) quando um pedido entra em &lt;code&gt;aguardando_aprovacao&lt;/code&gt; hoje isso só é visível consultando a API ou o dashboard. E não existem testes automatizados. Nenhum desses pontos invalida o que foi demonstrado sobre observabilidade, mas vale deixar claro que é um projeto de exemplo, não um sistema pronto para produção.&lt;/p&gt;

&lt;p&gt;🔗 Repositório com os exemplos: &lt;a href="https://github.com/diegobroetto/observability-demo" rel="noopener noreferrer"&gt;https://github.com/diegobroetto/observability-demo&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  11.0 Conclusão
&lt;/h2&gt;

&lt;p&gt;Observabilidade serverless cara geralmente é observabilidade descuidada. DEBUG em produção, retenção infinita, X-Ray sem sampling rule em função de alto volume, métrica customizada com dimensão de alta cardinalidade qualquer um desses sozinho não parece grande coisa. Junto viram uma fatura que você não esperava.&lt;/p&gt;

&lt;p&gt;O Powertools for Lambda resolve 80% do trabalho sem boilerplate. O resto é configuração intencional no template: retenção por ambiente, log level por ambiente, sampling por tipo de função.&lt;/p&gt;

&lt;p&gt;E configure os alarmes antes de precisar deles. O dashboard você abre quando quer. O alarme te acorda quando você precisa.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Diego Broetto</dc:creator>
      <pubDate>Thu, 11 Jun 2026 20:36:29 +0000</pubDate>
      <link>https://dev.to/broettod/-27hh</link>
      <guid>https://dev.to/broettod/-27hh</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n" class="crayons-story__hidden-navigation-link"&gt;SAM CLI + Language Extensions: o fim do copy-paste em templates serverless&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/broettod" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F2972664%2F3a9f248e-ec85-4254-b34c-7e3dbe50465e.jpg" alt="broettod profile" class="crayons-avatar__image" width="800" height="1067"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/broettod" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Diego Broetto
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Diego Broetto
                
              
              &lt;div id="story-author-preview-content-3876245" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/broettod" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F2972664%2F3a9f248e-ec85-4254-b34c-7e3dbe50465e.jpg" class="crayons-avatar__image" alt="" width="800" height="1067"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Diego Broetto&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 11&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n" id="article-link-3876245"&gt;
          SAM CLI + Language Extensions: o fim do copy-paste em templates serverless
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/aws"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;aws&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/serverless"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;serverless&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cloudformation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cloudformation&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/braziliandevs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;braziliandevs&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              4&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>SAM CLI + Language Extensions: o fim do copy-paste em templates serverless</title>
      <dc:creator>Diego Broetto</dc:creator>
      <pubDate>Thu, 11 Jun 2026 20:04:33 +0000</pubDate>
      <link>https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n</link>
      <guid>https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n</guid>
      <description>&lt;h2&gt;
  
  
  1.0 Introdução
&lt;/h2&gt;

&lt;p&gt;Se você já trabalhou com AWS SAM em projetos com múltiplos ambientes, provavelmente já viveu essa cena: precisa adicionar uma nova função Lambda ao projeto, abre o &lt;code&gt;template.yaml&lt;/code&gt;, encontra o bloco da função existente, seleciona tudo, cola abaixo, começa a trocar os valores um por um. Nome da função, variável de ambiente, tag, CodeUri. Torcer pra não esquecer nada.&lt;/p&gt;

&lt;p&gt;Um tempo depois, alguém aumenta o timeout de uma das funções e esquece de replicar nas outras duas. É um caso bem comum e que já vi se repetir em vários clientes do meu dia a dia.&lt;/p&gt;

&lt;p&gt;Esse problema se chama &lt;strong&gt;copy-paste em IaC&lt;/strong&gt;. E no SAM, ele era difícil de resolver.&lt;/p&gt;

&lt;p&gt;Quem usa CloudFormation no dia a dia sabe que desde 2023 o &lt;code&gt;AWS::LanguageExtension&lt;/code&gt; com &lt;code&gt;Fn::ForEach&lt;/code&gt; permite gerar múltiplos recursos a partir de uma única definição. O problema é que o SAM CLI simplesmente não entendia esse bloco. Você tentava rodar um &lt;code&gt;sam local invoke&lt;/code&gt; com um template que usava &lt;code&gt;Fn::ForEach&lt;/code&gt; e levava um erro. O resultado era uma escolha ingrata: templates DRY ou desenvolvimento local com o SAM. Não dava pra ter os dois.&lt;/p&gt;

&lt;p&gt;Em maio de 2026, a AWS resolveu isso. O SAM CLI agora processa Language Extensions em memória durante as operações locais e o template original segue intacto para o deploy no CloudFormation.&lt;/p&gt;

&lt;p&gt;Nesse artigo vou mostrar como migrar esses templates repetitivos para uma estrutura gerada com &lt;code&gt;Fn::ForEach&lt;/code&gt;, com exemplos que você pode testar agora mesmo no seu ambiente.&lt;/p&gt;




&lt;h2&gt;
  
  
  2.0 Contexto
&lt;/h2&gt;

&lt;p&gt;Antes de entrar no código, vale entender por que esse problema durou tanto tempo.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;AWS::LanguageExtensions&lt;/code&gt; é um transform do CloudFormation lançado em 2023. Ele desbloqueia um conjunto de funções que não estão disponíveis por padrão nos templates, a principal delas sendo o &lt;code&gt;Fn::ForEach&lt;/code&gt;: você itera sobre uma lista e gera múltiplos recursos a partir de uma única definição. Parecido com um &lt;code&gt;for&lt;/code&gt; em qualquer linguagem de programação.&lt;/p&gt;

&lt;p&gt;O problema é que o SAM CLI e o CloudFormation são camadas diferentes. Quando você roda &lt;code&gt;sam local invoke&lt;/code&gt; ou &lt;code&gt;sam build&lt;/code&gt;, o SAM CLI processa o template localmente, sem passar pelo CloudFormation. E até maio de 2026, esse processamento local não sabia o que fazer com o bloco &lt;code&gt;Fn::ForEach&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.amazonaws.com%2Fuploads%2Farticles%2Ftl5cytqkciprzqekr8ab.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftl5cytqkciprzqekr8ab.png" alt="Erro do SAM CLI ao tentar processar um template com Fn::ForEach, exibindo traceback do Python e a mensagem enganosa de validação" width="800" height="67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O erro é totalmente enganoso, parece problema de indentação no YAML, mas na verdade é o SAM CLI não reconhecendo a sintaxe do Language Extensions. Quem já bateu nesse erro e ficou olhando pro template por meia hora sem achar nada errado sabe o quanto isso frustra.&lt;/p&gt;

&lt;p&gt;Isso criava um dilema real no dia a dia. Você tinha três caminhos, e nenhum era bom:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Usar Language Extensions e abrir mão do SAM CLI local: deploy direto no CloudFormation para testar. Ciclo lento, custo de iteração alto, debug difícil.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Usar SAM CLI local e abrir mão do DRY: manter os blocos repetidos no template, aceitar o risco de inconsistência entre ambientes e torcer pra ninguém esquecer de replicar uma mudança.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manter dois templates: um enxuto com &lt;code&gt;Fn::ForEach&lt;/code&gt; para o deploy, outro expandido manualmente para uso local. O pior dos mundos, duplicação do template inteiro, sincronização manual entre os dois, confusão garantida na revisão de código.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Eu ficava sempre na opção 2. O template ficava feio, mas pelo menos o &lt;code&gt;sam local invoke&lt;/code&gt; funcionava.&lt;/p&gt;

&lt;p&gt;O que a AWS fez em maio de 2026 foi processar as Language Extensions em memória, expandindo os loops antes de qualquer operação local. O template original no disco nunca é tocado, o CloudFormation continua recebendo o template com &lt;code&gt;Fn::ForEach&lt;/code&gt; intacto e faz a própria expansão no deploy.&lt;/p&gt;

&lt;p&gt;O SAM CLI cuida do desenvolvimento local, o CloudFormation cuida do deploy. Sem interferência entre os dois.&lt;/p&gt;




&lt;h2&gt;
  
  
  3.0 Testando na prática: antes e depois
&lt;/h2&gt;

&lt;p&gt;Vamos usar um cenário com três funções Lambda, uma por ambiente (DEV, HML e PROD). Cada função vai ter o mesmo código base, mas com variáveis de ambiente e configurações diferentes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 O modo antigo: copy-paste inevitável
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2010-09-09"&lt;/span&gt;
&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;

&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&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;python3.13&lt;/span&gt;
    &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256&lt;/span&gt;
    &lt;span class="na"&gt;Timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&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;DevFunction&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::Serverless::Function&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;hello-world-dev&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/dev/&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;handler.lambda_handler&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ambiente&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DEV"&lt;/span&gt;
      &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
          &lt;span class="na"&gt;TIER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;free&lt;/span&gt;
      &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
        &lt;span class="na"&gt;Project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-word-old&lt;/span&gt;

  &lt;span class="na"&gt;HmlFunction&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::Serverless::Function&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;demo-old-hml&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/hml/&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;handler.lambda_handler&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ambiente&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hml"&lt;/span&gt;
      &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hml&lt;/span&gt;
          &lt;span class="na"&gt;TIER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;standard&lt;/span&gt;
      &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hml&lt;/span&gt;
        &lt;span class="na"&gt;Project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-word-old&lt;/span&gt;

  &lt;span class="na"&gt;ProdFunction&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::Serverless::Function&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;demo-old-prod&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/prod/&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;handler.lambda_handler&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ambiente&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Prod"&lt;/span&gt;
      &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ENV&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;TIER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;premium&lt;/span&gt;
      &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
        &lt;span class="na"&gt;Project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-word-old&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;AlphaFunctionArn&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;DevFunction.Arn&lt;/span&gt;
  &lt;span class="na"&gt;hmlFunctionArn&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;HmlFunction.Arn&lt;/span&gt;
  &lt;span class="na"&gt;ProdFunctionArn&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;ProdFunction.Arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;São três blocos quase idênticos. Se você precisar mudar o runtime de &lt;code&gt;python3.13&lt;/code&gt; para &lt;code&gt;python3.14&lt;/code&gt;, precisa alterar em três lugares, ou no &lt;code&gt;Globals&lt;/code&gt;, mas só funciona para propriedades suportadas pelo bloco global. Qualquer propriedade que não vai para &lt;code&gt;Globals&lt;/code&gt; vira copy-paste obrigatório.&lt;/p&gt;

&lt;p&gt;E se precisar adicionar um quarto ambiente (&lt;code&gt;Staging&lt;/code&gt;)? Copiar um dos blocos, renomear, ajustar variáveis, criar o &lt;code&gt;src/stg/&lt;/code&gt;, adicionar o Output manualmente. São no mínimo seis pontos de toque para uma mudança que deveria ser trivial.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 O modo novo: uma definição, N funções
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2010-09-09"&lt;/span&gt;

&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::LanguageExtensions&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&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;Environments&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;CommaDelimitedList&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dev,Hml,Prod"&lt;/span&gt;

&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&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;python3.13&lt;/span&gt;
    &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256&lt;/span&gt;
    &lt;span class="na"&gt;Timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

&lt;span class="na"&gt;Mappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;EnvConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;free&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/dev/&lt;/span&gt;
    &lt;span class="na"&gt;hml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;standard&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/hml/&lt;/span&gt;
    &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;premium&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/prod/&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;Fn::ForEach::CreateFunctions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EnvName&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;Environments&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${EnvName}Function&lt;/span&gt;&lt;span class="err"&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::Serverless::Function&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="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello-world-${EnvName}"&lt;/span&gt;
          &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Fn::FindInMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EnvConfig&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CodeUri&lt;/span&gt;
          &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;handler.lambda_handler&lt;/span&gt;
          &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Funcao&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;do&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ambiente&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${EnvName}"&lt;/span&gt;
          &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
              &lt;span class="na"&gt;TIER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;Fn::FindInMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EnvConfig&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Tier&lt;/span&gt;
          &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
            &lt;span class="na"&gt;Project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-word&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;Fn::ForEach::FunctionOutputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EnvName&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;Environments&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${EnvName}FunctionArn&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ARN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;da&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;funcao&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${EnvName}"&lt;/span&gt;
        &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${EnvName}Function&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;Fn::ForEach::CreateFunctions&lt;/code&gt; itera sobre &lt;code&gt;[hml, dev, prod]&lt;/code&gt; e gera automaticamente &lt;code&gt;DevFunction&lt;/code&gt;, &lt;code&gt;HmlFunction&lt;/code&gt; e &lt;code&gt;ProdFunction&lt;/code&gt;, cada uma com as configurações corretas vindas do &lt;code&gt;Mappings&lt;/code&gt;. Os Outputs também são gerados pelo segundo &lt;code&gt;ForEach&lt;/code&gt;, sem nenhum bloco manual.&lt;/p&gt;

&lt;p&gt;O handler Python é o mesmo para todos os ambientes, todas as diferenças chegam via variáveis de ambiente:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TIER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;standard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Oi, eu sou o ambiente de &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4.0 Testando Localmente
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Pré-requisitos
&lt;/h3&gt;

&lt;p&gt;Faça a atualização do seu AWS SAM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;aws-sam-cli &lt;span class="nt"&gt;--upgrade&lt;/span&gt;
sam &lt;span class="nt"&gt;--version&lt;/span&gt;  &lt;span class="c"&gt;# 1.161.1 ou superior&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh090zre9lo3cn08bdytv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh090zre9lo3cn08bdytv.png" alt="Terminal exibindo a saída do comando sam --version com a versão atualizada do SAM CLI" width="450" height="84"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Estrutura de pastas
&lt;/h3&gt;

&lt;p&gt;Vou disponibilizar no final do artigo um link para o repositório do github onde você pode fazer um clone e realizar os testes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanklx9yu9251jgi2904b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanklx9yu9251jgi2904b.png" alt="Estrutura de pastas do projeto com os diretórios modo-antigo e modo-novo, contendo templates e código fonte por ambiente" width="423" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Testando o modo antigo
&lt;/h3&gt;

&lt;p&gt;Faça a validação do template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;modo-antigo/

sam validate &lt;span class="nt"&gt;--template&lt;/span&gt; template-old.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwhgxlald379q1h32eq5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwhgxlald379q1h32eq5.png" alt="Terminal exibindo a saída do sam validate confirmando que o template-old.yaml é um template SAM válido" width="800" height="82"&gt;&lt;/a&gt;&lt;br&gt;
Após terminar, execute o build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build &lt;span class="nt"&gt;--template&lt;/span&gt; template-old.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7obi5sdv5zylx9r8aqz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7obi5sdv5zylx9r8aqz.png" alt="Terminal exibindo a saída do sam build com o build das três funções concluído com sucesso" width="800" height="330"&gt;&lt;/a&gt;&lt;br&gt;
Agora vamos testar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke DevFunction &lt;span class="nt"&gt;--no-event&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke HmlFunction &lt;span class="nt"&gt;--no-event&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke ProdFunction &lt;span class="nt"&gt;--no-event&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9c4kydaa6ed5k8f90fwv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9c4kydaa6ed5k8f90fwv.png" alt="Terminal exibindo a saída do sam local invoke da DevFunction com a resposta JSON retornando ambiente dev e tier free" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 Testando o modo novo
&lt;/h3&gt;

&lt;p&gt;Acesse o repositório e valide o template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;modo-novo/
&lt;span class="c"&gt;# flag --language-extensions obrigatoria em todos os comandos locais&lt;/span&gt;
sam validate &lt;span class="nt"&gt;--template&lt;/span&gt; template.yaml &lt;span class="nt"&gt;--language-extensions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs8z14myf465jhojdpnpg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs8z14myf465jhojdpnpg.png" alt="Terminal exibindo a saída do sam validate confirmando que o template.yaml é um template SAM válido" width="800" height="82"&gt;&lt;/a&gt;&lt;br&gt;
Novamente, vamos fazer o build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build &lt;span class="nt"&gt;--template&lt;/span&gt; template.yaml &lt;span class="nt"&gt;--language-extensions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6mn7w01ynqxs0t7gg15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6mn7w01ynqxs0t7gg15.png" alt="Terminal exibindo a saída do sam build com o build das três funções concluído com sucesso" width="800" height="354"&gt;&lt;/a&gt;&lt;br&gt;
E por fim, testar novamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# invocar pelo nome expandido pelo ForEach&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke DevFunction &lt;span class="nt"&gt;--language-extensions&lt;/span&gt; &lt;span class="nt"&gt;--no-event&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke HmlFunction &lt;span class="nt"&gt;--language-extensions&lt;/span&gt; &lt;span class="nt"&gt;--no-event&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke ProdFunction &lt;span class="nt"&gt;--language-extensions&lt;/span&gt; &lt;span class="nt"&gt;--no-event&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fplhpc8xxpqhkoc3lvdsl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fplhpc8xxpqhkoc3lvdsl.png" alt="Terminal exibindo a saída do sam local invoke da ProdFunction com a resposta JSON retornando ambiente" width="799" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.5 E se eu precisar adicionar um novo ambiente?
&lt;/h3&gt;

&lt;p&gt;Esse é o momento que deixa o benefício mais concreto. Para adicionar um ambiente &lt;code&gt;Staging&lt;/code&gt; no modo novo, são apenas três mudanças:&lt;/p&gt;

&lt;h4&gt;
  
  
  4.5.1 Atualizar o parâmetro
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Environments&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;CommaDelimitedList&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hml,Dev,Prod,Stg"&lt;/span&gt;  &lt;span class="c1"&gt;# adicionar Staging aqui&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.5.2 Adicionar entrada no Mappings
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Mappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;EnvConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ... entradas existentes ...&lt;/span&gt;
    &lt;span class="na"&gt;Staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;standard&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/stg/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.5.3 Criar nova pasta e mover código
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;src/stg

&lt;span class="nb"&gt;cp &lt;/span&gt;src/hml/handler.py src/stg/handler.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.5.4 Conclusão
&lt;/h4&gt;

&lt;p&gt;O Output &lt;code&gt;StgFunctionArn&lt;/code&gt; é gerado automaticamente pelo segundo &lt;code&gt;ForEach&lt;/code&gt;. Nenhuma outra alteração necessária.&lt;/p&gt;

&lt;p&gt;No modo antigo, a mesma operação exigiria: copiar um bloco inteiro de ~15 linhas, renomear em todos os lugares, ajustar as variáveis, criar o diretório e o handler, e adicionar o Output manualmente. São pelo menos seis pontos de toque, cada um com risco de erro.&lt;/p&gt;




&lt;h2&gt;
  
  
  5.0 Outras funções que vieram junto
&lt;/h2&gt;

&lt;p&gt;Além do &lt;code&gt;Fn::ForEach&lt;/code&gt;, o SAM CLI passou a suportar mais funções do Language Extensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Fn::Length&lt;/code&gt; retorna o número de itens de uma lista. Útil para definir &lt;code&gt;ReservedConcurrentExecutions&lt;/code&gt; ou &lt;code&gt;ProvisionedConcurrencyConfig&lt;/code&gt; baseado no tamanho dinâmico de uma lista de parâmetros.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Fn::ToJsonString&lt;/code&gt; serializa qualquer valor para string JSON. Prático para passar configurações complexas como variável de ambiente em formato JSON sem ter que escrever o JSON manualmente no template.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Fn::FindInMap&lt;/code&gt; com &lt;code&gt;DefaultValue&lt;/code&gt; permite definir um valor de fallback quando a chave não existe no mapeamento, sem precisar de um bloco &lt;code&gt;Conditions&lt;/code&gt; inteiro para cobrir o caso base. Muito útil para templates multi-região onde algumas regiões têm configurações específicas e as demais usam o padrão.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;DeletionPolicy&lt;/code&gt; e &lt;code&gt;UpdateReplacePolicy&lt;/code&gt; condicionais permitem usar &lt;code&gt;!If&lt;/code&gt; nessas políticas, algo que antes não era suportado. Por exemplo, deletar automaticamente em ambientes de dev e reter em produção, tudo no mesmo template.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  6.0 Pontos de atenção
&lt;/h2&gt;

&lt;p&gt;Algumas coisas que aprendi testando na prática e que o anúncio oficial não deixa claro:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; A flag &lt;code&gt;--language-extensions&lt;/code&gt; é obrigatória em todos os comandos locais. O suporte é opt-in: sem a flag, o SAM CLI ignora o Language Extensions e retorna aquele erro confuso de "All Resources must be Objects", que parece indentação mas não é. Todos os comandos precisam da flag: &lt;code&gt;sam validate&lt;/code&gt;, &lt;code&gt;sam build&lt;/code&gt;, &lt;code&gt;sam local invoke&lt;/code&gt;, &lt;code&gt;sam local start-api&lt;/code&gt; e &lt;code&gt;sam sync&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; A ordem dos transforms importa. &lt;code&gt;AWS::LanguageExtensions&lt;/code&gt; deve vir obrigatoriamente antes de &lt;code&gt;AWS::Serverless-2016-10-31&lt;/code&gt; na lista. Invertido não funciona.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Correto&lt;/span&gt;
&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::LanguageExtensions&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;

&lt;span class="c1"&gt;# Errado&lt;/span&gt;
&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::LanguageExtensions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Dentro do &lt;code&gt;Fn::ForEach&lt;/code&gt;, use sempre a forma longa das funções. A short-form YAML (&lt;code&gt;!Sub&lt;/code&gt;, &lt;code&gt;!FindInMap&lt;/code&gt;, &lt;code&gt;!Ref&lt;/code&gt;, &lt;code&gt;!GetAtt&lt;/code&gt;) não funciona dentro dos blocos do loop. Use a forma explícita: &lt;code&gt;Fn::Sub&lt;/code&gt;, &lt;code&gt;Fn::FindInMap&lt;/code&gt;, &lt;code&gt;Ref&lt;/code&gt;, &lt;code&gt;Fn::GetAtt&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Errado: short-form dentro do ForEach&lt;/span&gt;
&lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!FindInMap&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;EnvConfig&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;EnvName&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Correto: forma longa&lt;/span&gt;
&lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Fn::FindInMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EnvConfig&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CodeUri&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Sem comentários inline dentro do bloco &lt;code&gt;Fn::ForEach&lt;/code&gt;. O parser YAML perde o fio do template com comentários na mesma linha dos valores do loop. Mantenha os comentários fora do bloco ou remova-os.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; Não compatível com Lambda Managed Instances. Funções geradas pelo &lt;code&gt;Fn::ForEach&lt;/code&gt; não podem usar capacity provider. Se o seu projeto usa Lambda Managed Instances, esse recurso ainda não se aplica.&lt;/p&gt;




&lt;h2&gt;
  
  
  7.0 Conclusão
&lt;/h2&gt;

&lt;p&gt;Esse lançamento fecha um gap que existia há quase três anos: o CloudFormation suportava &lt;code&gt;Fn::ForEach&lt;/code&gt; desde julho de 2023, mas o SAM CLI ficou para trás, e as equipes tinham que escolher entre templates DRY e desenvolvimento local rápido.&lt;/p&gt;

&lt;p&gt;A solução foi processar o Language Extensions em memória durante operações locais sem tocar no template original. O CloudFormation faz sua própria expansão no deploy. Cada camada faz o seu trabalho.&lt;/p&gt;

&lt;p&gt;O único detalhe que o anúncio oficial não deixa explícito é que o suporte é opt-in via flag &lt;code&gt;--language-extensions&lt;/code&gt;. Sem ela, você vai continuar levando aquele erro de indentação que não tem nada a ver com indentação. Agora você sabe.&lt;/p&gt;

&lt;p&gt;Se quiser testar, os templates de exemplo desse artigo estão disponíveis no repositório linkado abaixo. Qualquer dúvida ou feedback, comenta aqui.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Repositório com os exemplos:&lt;/strong&gt; &lt;a href="https://github.com/diegobroetto/sam-language-extensions-demo" rel="noopener noreferrer"&gt;github.com/diegobroetto/sam-language-extensions-demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>cloudformation</category>
      <category>braziliandevs</category>
    </item>
  </channel>
</rss>
