AWS Emulator no localhost: desenvolvimento local com TypeScript
A pergunta que aparece quando a AWS solta um emulador local é direta: dá para usar o AWS Emulator no localhost para desenvolvimento? Dá. Você sobe um processo na sua máquina, aponta a AWS CLI e o AWS SDK para https://127.0.0.1:9297 e roda a API Node.js/TypeScript contra S3, DynamoDB, SQS e até Lambda, sem conta AWS e sem desdobrar nada na nuvem.
Em 11/11/2026 a AWS lançou o AWS Emulator como um programa local que serve as mesmas APIs que os SDKs e a CLI já conhecem. O código está no repositório aws/aws-emulator. O guia oficial, a começar por What is AWS Emulator?, deixa o recorte explícito: o emulador é para construir e testar, não para produção e não para validar segurança.
Este tutorial monta o fluxo do dia a dia, não um pipeline de CI. Você instala, verifica a assinatura do pacote, inicia o emulador, configura as variáveis, faz um seed porque o estado mora só na memória e sobe uma API Express em TypeScript com tsx watch. Também documento o que quebrou no laboratório: um nome de bucket do próprio guia, o PutItem antes da tabela ficar ACTIVE e o SQSClient tentando a AWS real pelo host da QueueUrl.
O que é o AWS Emulator e como ele funciona
O AWS Emulator é um programa de linha de comando, sem interface gráfica, que escuta HTTPS no seu computador. No Linux você instala um pacote RPM ou Debian e roda aws-emulator. No macOS e no Windows o caminho oficial é Docker, a partir de um Dockerfile curto do guia. Para testes em Rust existe ainda o modo embedded, dentro do processo de teste, que fica fora do recorte deste artigo.
Quando o processo sobe, ele gera credenciais novas, um arquivo config e um certificado, e grava tudo em ~/.aws/emulator//. O padrão é 127.0.0.1:9297, acessível só da própria máquina. Os pedidos da API precisam de Signature Version 4 com as chaves daquela execução. A conta que o emulador expõe é sempre 000000000000, e o ARN de GetCallerIdentity é arn:aws:iam::000000000000:user/emulator. As access key IDs começam com EMULATOR, o que ajuda a não confundir com chave real.
O estado dos recursos fica na memória. O guia é claro: o quanto cabe depende da sua máquina, e tudo some quando o emulador para. Não há rota administrativa para resetar o estado; o jeito de começar limpo é reiniciar o processo, ou usar --idle-timeout para ele encerrar sozinho depois de um tempo sem chamadas. Por isso o desenvolvimento local precisa de um seed, não de um volume persistente que a documentação não descreve.
A cobertura das APIs vem dos modelos publicados no repositório de API models da AWS, os mesmos que alimentam os SDKs. Onde a operação está implementada, o emulador busca o mesmo resultado e o mesmo erro da AWS. Onde ainda não está, a resposta é HTTP 501 com o código NotImplemented. A fidelidade varia por serviço e por plano de controle versus plano de dados; as pontuações saem com cada release no GitHub, no arquivo scores.json.gz, e o guia recomenda conferir se o seu fluxo está coberto antes de confiar no emulador.
Pré-requisitos e versões
Você não precisa de conta AWS para baixar ou rodar o emulador. No Linux, o guia pede um sistema 64 bits (x86_64 ou aarch64) com glibc 2.17 ou posterior. Neste laboratório, em 12/11/2026, usei Debian 13 x86_64 com glibc 2.41, Node.js 24.22.1 e a AWS CLI 2.40.2, instalada pelo instalador oficial da AWS. O emulador testado foi a versão 1.0.322 (release date 2026.11.10, pacote aws-emulator 1.0.322-1 do canal LTS/latest).
O emulador recebe atualizações muitas vezes por dia. A documentação de versões recomenda fixar a versão quando você quiser resultados repetíveis, e lembra que correções de segurança saem só no release mais recente. Se um patch mudar o resultado de um teste, a explicação oficial é que a fidelidade pode ter se aproximado da AWS, ou que é um bug a reportar.
Instalação no Linux e verificação da assinatura
No Debian ou Ubuntu, o guia oficial baixa o pacote e instala com apt-get. O comando abaixo instala a última versão do canal LTS/latest. Para uma versão específica, a página Versions and compatibility mostra o padrão da URL e o arquivo versions.json.
curl -fsSLo aws-emulator.deb
https://downloads.aws.com/aws-emulator/packages/aws-emulator/LTS/latest/aws-emulator_amd64.deb
sudo apt-get install -y ./aws-emulator.deb
aws-emulator -v
Antes de instalar, vale conferir a assinatura. A AWS publica um arquivo .sig ao lado do pacote e a chave pública em https://downloads.aws.com/keys/gpg/current.asc. No laboratório eu reconfiri o .deb já baixado da 1.0.322. A impressão digital da chave ssm-publishing@amazon.com foi exatamente a que o guia exige, 98BC F336 A3BE 53B7 34E9 67C5 2E5B F49B D940 F624, e o gpg --verify imprimiu Good signature from "SSM Publishing ssm-publishing@amazon.com". Também apareceu o aviso de que a chave não está certificada com uma assinatura confiável; o guia diz que esse aviso pode ser ignorado depois de conferir a impressão digital. Se a saída for BAD signature, não instale o pacote.
PACKAGE_FILE=aws-emulator_amd64.deb
curl -fsSLO https://downloads.aws.com/aws-emulator/packages/aws-emulator/LTS/latest/$PACKAGE_FILE
curl -fsSLO https://downloads.aws.com/aws-emulator/packages/aws-emulator/LTS/latest/$PACKAGE_FILE.sig
curl -fsSLo aws-publishing.asc https://downloads.aws.com/keys/gpg/current.asc
gpg --import aws-publishing.asc
gpg --fingerprint ssm-publishing@amazon.com
gpg --verify $PACKAGE_FILE.sig PACKAGE_FILE
Iniciar o emulador e apontar o shell
O comando aws-emulator roda em primeiro plano. O padrão é a porta 9297. Se ela já estiver ocupada, use -p com outra porta, ou -p 0 para o sistema escolher. Neste laboratório subi na 9400, com a telemetria desligada só nesta execução. A telemetria vem ligada por padrão; ela não inclui o conteúdo dos seus pedidos, respostas, código ou credenciais, e o guia documenta como desligar com --telemetry off, com EMULATOR_TELEMETRY=off ou no config.toml.
aws-emulator -p 9400 --telemetry off
O log de subida é JSON Lines. A linha estável, com message igual a emulator server information, traz endpoint_url, health_check_url, config_file e ca_file. Para esperar o processo num script, o guia usa o health check na mesma porta, por HTTPS, com o certificado recém-gerado. A resposta é HTTP 200 e o corpo ok, o que conferi neste laboratório.
for i in $(seq 30); do
curl -sf --cacert ~/.aws/emulator/9400/ca.pem
https://127.0.0.1:9400/__admin/health && break
sleep 1
done
No outro terminal, aponte a CLI e o SDK com as variáveis oficiais e tire do ambiente qualquer credencial real. Variáveis como AWS_ACCESS_KEY_ID vencem o arquivo de credenciais. Se AWS_ENDPOINT_URL não estiver definida no shell que roda os testes, o código chama a AWS com a credencial que encontrar. AWS_EC2_METADATA_DISABLED=true impede o SDK de buscar chave no serviço de metadados de uma instância EC2. AWS_DISABLE_HOST_PREFIX_INJECTION=true evita que a CLI e o Boto3 prefixem o host e mandem o pedido para um nome que não existe, como data-127.0.0.1.
export AWS_ENDPOINT_URL=https://127.0.0.1:9400
export AWS_SHARED_CREDENTIALS_FILE=/.aws/emulator/9400/emulator_credentials/.aws/emulator/9400/config
export AWS_CONFIG_FILE=
export AWS_CA_BUNDLE=~/.aws/emulator/9400/ca.pem
export AWS_EC2_METADATA_DISABLED=true
export AWS_DISABLE_HOST_PREFIX_INJECTION=true
export AWS_REGION=us-east-1
export AWS_DEFAULT_REGION=us-east-1
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_PROFILE
A primeira checagem é aws sts get-caller-identity. Se você está no emulador, a conta é 000000000000. Se aparecer a sua conta, os pedidos foram para a AWS. No laboratório a resposta foi exatamente a do guia: conta 000000000000 e ARN arn:aws:iam::000000000000:user/emulator. No Node.js, além desse bloco, defina NODE_EXTRA_CA_CERTS para o mesmo ca.pem. O SDK JS v3 no Node não trata AWS_CA_BUNDLE como a CLI; ele precisa dessa variável extra, documentada na tabela de certificados do guia.
Use 127.0.0.1 no endpoint, não localhost. O guia explica o motivo: com um endereço IP, o SDK coloca o nome do bucket no caminho. Se você usa localhost, os SDKs para JavaScript, Java e Go passam a colocar o bucket no host, e o emulador não aceita esse formato. A trava do laboratório recusou https://localhost:9400 antes de qualquer chamada.
O fluxo diário: emulador, seed e a API no localhost
A rotina que eu montei cabe em quatro passos que você repete no começo do dia, ou sempre que reinicia o emulador. Primeiro, sobe o processo e espera o health check. Segundo, carrega as variáveis no shell da aplicação e confere a identidade. Terceiro, roda o seed, que cria o bucket, a tabela e a fila e grava um pedido e um arquivo de exemplo. Quarto, sobe a API com tsx watch e trabalha no código. Reiniciar só o app não apaga os recursos emulados. Reiniciar o emulador apaga tudo.
O projeto do laboratório é propositalmente pequeno: uma API Express na porta 3000, escutando só em 127.0.0.1, com rotas para saúde, pedidos no DynamoDB, um recibo no S3 e uma mensagem na fila SQS. As versões foram as que o npm resolveu em 12/11/2026: AWS SDK for JavaScript v3 3.1168.0, Express 5.3.0, TypeScript 7.0.2 e tsx 4.23.15. O TypeScript compilou com tsc --noEmit sem erros.
lab-local/
package.json
tsconfig.json
.env.example
scripts/emulator-env.sh
scripts/start-emulator.sh
src/config.ts
src/clients.ts
src/guard.ts
src/seed.ts
src/server.ts
src/whoami.ts
O arquivo .env.example não leva segredo. Ele só lista as variáveis e lembra que as chaves do emulador mudam a cada start. O script scripts/emulator-env.sh preenche os caminhos a partir da porta, exporta NODE_EXTRA_CA_CERTS e faz o unset das credenciais reais. A aplicação lê APP_TARGET=emulator ou APP_TARGET=aws para decidir qual trava aplicar na subida.
Clients TypeScript: o que o SDK precisa além do endpoint
Apontar AWS_ENDPOINT_URL não basta no JavaScript. Cada client do AWS SDK v3 precisa de disableHostPrefix: true, como o guia pede para esse SDK. O SQSClient ainda precisa de useQueueUrlAsEndpoint: false. Sem essa opção, o client usa o host que veio na QueueUrl. No laboratório, depois de criar a fila orders-demo, a URL devolvida foi https://sqs.us-east-1.amazonaws.com/000000000000/orders-demo. O próprio SDK avisou que essa URL difere do endpoint resolvido https://127.0.0.1:9400/ e que, no padrão, ele usaria o host da QueueUrl.
Isso é o tipo de detalhe que transforma um ambiente local num incidente. Se o AWS_ENDPOINT_URL estiver certo mas o SendMessage for para sqs.us-east-1.amazonaws.com, o pedido sai da máquina. No laboratório eu acrescentei um middleware que, com APP_TARGET=emulator, recusa qualquer request cujo host não seja 127.0.0.1. No teste propositalmente inseguro, o GetQueueUrl foi para 127.0.0.1 e o SendMessage seria para sqs.us-east-1.amazonaws.com; a trava interrompeu o envio. Com useQueueUrlAsEndpoint: false, o POST da API que grava no DynamoDB, manda a mensagem e salva o recibo no S3 passou nas checagens.
// src/clients.ts (trecho)
const sqs = new SQSClient({
region: config.region,
disableHostPrefix: true,
useQueueUrlAsEndpoint: false,
});
Há duas outras pegadinhas que o seed precisa tratar. A primeira é o nome do bucket. O exemplo de create-bucket no guia do emulador usa amzn-s3-demo-bucket. Neste laboratório essa chamada falhou com InvalidBucketName. As regras oficiais de nome de bucket do S3 reservam o prefixo amzn-s3-demo-. O post de lançamento da AWS usa local-getting-started, e esse nome funcionou: o bucket foi criado, o aws s3 cp enviou 24 bytes, o download devolveu o texto hello from the emulator e o GetObject do SDK leu o mesmo conteúdo.
A segunda é o ciclo de vida da tabela no DynamoDB. O exemplo oficial de nome curto ab foi recusado com a mesma ValidationException do guia, o que é o comportamento desejado. Já uma tabela válida, criada com PAY_PER_REQUEST, voltou com TableStatus=CREATING. O PutItem imediatamente depois falhou com ResourceNotFoundException. Depois que o DescribeTable passou a reportar ACTIVE, o PutItem e o GetItem funcionaram. O seed do laboratório espera a tabela ficar ACTIVE antes de gravar o pedido o-1001. Se você pular essa espera, o primeiro acesso do dia vai falhar mesmo com o emulador saudável.
Seed, persistência e hot reload
Como o emulador não persiste estado entre reinícios, o seed é parte do fluxo, não um extra. O script src/seed.ts cria o bucket local-getting-started se ele ainda não existir, grava seed/hello.txt, cria a tabela orders se precisar, espera ela ficar ACTIVE, insere o pedido o-1001 e garante a fila orders-demo. Rodar o seed de novo no mesmo processo do emulador é idempotente o bastante para o dia a dia: ele reutiliza bucket, tabela e fila já criados e regrava os dados de exemplo.
No laboratório eu confirmei o que o guia descreve sobre memória. Com o emulador ainda no ar, o tsx watch recarregou a API depois que eu alterei o banner de /health; o log mostrou change in ./src/server.ts Restarting..., o health passou a devolver o novo texto e o GET /orders/o-1001 continuou encontrando o pedido semeado. Ou seja: recarregar o app no localhost não derruba o estado do emulador. Depois enviei SIGTERM ao aws-emulator. O list-buckets voltou uma lista vazia e o DescribeTable da tabela orders falhou com ResourceNotFoundException. Um novo seed recriou o bucket e o pedido. Não existe, na documentação, um volume ou um snapshot para “continuar de ontem”.
O hot reload, neste recorte, é do seu processo Node, não do emulador. O comando tsx watch src/server.ts basta para o ciclo de editar e salvar. O emulador continua no outro terminal, com o mesmo certificado e as mesmas chaves, até você pará-lo. Se você reinicia o emulador no meio do expediente, precisa recarregar as variáveis: as chaves e o ca.pem são gerados de novo a cada start, e as chaves da execução anterior deixam de funcionar.
Travas para alternar entre localhost e a AWS real
O risco prático do desenvolvimento local com AWS não é esquecer o emulador ligado. É rodar o mesmo código com o endpoint errado, ou com uma credencial real no ambiente, e descobrir tarde demais que o pedido foi para a conta de verdade. O guia insiste nisso: defina as variáveis no mesmo script que roda a aplicação, tire as credenciais reais e confirme a identidade antes de trabalhar.
No laboratório a API recusa subir se essas regras não forem cumpridas. Com APP_TARGET=emulator, ela exige AWS_ENDPOINT_URL em https://127.0.0.1, arquivos de config e credencial do emulador, NODE_EXTRA_CA_CERTS apontando para o ca.pem daquela porta, e a ausência de AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN e AWS_PROFILE. Depois chama GetCallerIdentity e só aceita a conta 000000000000 com o ARN do usuário emulator. Com APP_TARGET=aws, ela recusa subir se AWS_ENDPOINT_URL ainda estiver definida, ou se os caminhos ainda apontarem para ~/.aws/emulator/.
Testei quatro falhas de propósito, todas antes de qualquer chamada duvidosa à nuvem. Sem AWS_ENDPOINT_URL, a subida termina com a mensagem de que o SDK chamaria a AWS real. Com APP_TARGET=aws e o endpoint do emulador ainda no ambiente, a subida recusa o alvo trocado. Com AWS_ACCESS_KEY_ID definido, a subida pede o unset. Com localhost no endpoint, a subida recusa o host. Nenhuma dessas checagens substitui um teste na AWS de verdade antes de produção. Elas só evitam o erro mais barato: desenvolver no localhost e acertar a conta errada.
Quando você for à nuvem, use outro shell, ou outro arquivo de ambiente, sem AWS_ENDPOINT_URL, sem NODE_EXTRA_CA_CERTS do emulador e sem o arquivo de chaves EMULATOR. Não misture os dois alvos no mesmo processo. O guia também avisa que a maior parte das formas de confiar no certificado do emulador substitui a cadeia padrão; NODE_EXTRA_CA_CERTS é a exceção, porque adiciona o ca.pem às autoridades que o Node já confia. Mesmo assim, o caminho mais seguro no dia a dia é um shell só para o emulador e outro só para a AWS.
Lambda no emulador, se o seu fluxo local precisar
O guia de segurança menciona que, quando o emulador executa código de função, esse código não fica isolado da sua máquina: roda com a permissão do seu usuário e pode ler e alterar arquivos. Eu testei o caminho feliz com a CLI, não como parte da API Express. Criei um papel IAM no próprio emulador, fiz o create-function com runtime nodejs22.x e um handler que ecoa o evento, esperei o estado passar de Pending para Active e chamei aws lambda invoke. A resposta teve StatusCode 200 e o corpo {"statusCode":200,"body":"{"ok":true,"echo":{"ping":"pong"}}"}.
Isso mostra que o Lambda está no recorte do emulador para um eco simples, mas não autoriza usar o localhost para auditar políticas da função, VPC, event source mappings ou isolamento. Passe só código de teste, nunca payload ou dependências que você não aceitaria rodar no seu usuário. Se a operação que você precisa ainda não estiver implementada, espere o 501 e trate isso como falha visível, não como “a AWS faria o mesmo”.
Docker no macOS e no Windows
O emulador nativo é Linux. No macOS, no Windows, ou no Linux se você preferir contêiner, o guia Getting started with Docker pede para construir a imagem a partir de um Dockerfile curto. A AWS não publica imagem pronta. O ENTRYPOINT oficial é aws-emulator -b 0.0.0.0, para o processo escutar em todos os endereços dentro do contêiner, e o docker run oficial publica a porta só em 127.0.0.1. O volume em (id -u):$(id -g)" e, para desligar a telemetria no contêiner, -e EMULATOR_TELEMETRY=off ou --telemetry off depois do nome da imagem.
Neste ambiente não havia Docker, então eu não construí a imagem e não executei Compose. Se você for traduzir o docker run oficial para um Compose, mantenha o bind em 127.0.0.1, o limite de memória de 2g do exemplo do guia e o volume das credenciais, e trate o arquivo como a sua adaptação, não como um artefato publicado pela AWS. Depois que o contêiner sobe, as variáveis no host continuam as mesmas deste artigo, com a porta que você publicou.
Limitações que importam no desenvolvimento local
O AWS Emulator aceita qualquer região e qualquer account ID no pedido, mas não separa recursos por região ou por conta. O guia pede uma região e uma conta por processo. Para testar mais de uma região, suba um emulador por região, cada um na sua porta. A fidelidade é conferida pela AWS principalmente em us-west-2, e nos serviços globais em us-east-1; diferenças de disponibilidade entre regiões não aparecem.
Ele não avalia políticas IAM, políticas de bucket, políticas de chave do KMS nem acesso entre contas ou entre regiões do jeito que a AWS avalia. Uma requisição que a AWS negaria pode passar no emulador, e o contrário também pode acontecer. O emulador nunca recusa um pedido assinado com a chave dele. Não use o localhost para “validar o IAM” e depois promover o resultado à produção.
O guia também avisa que o emulador não foi testado para uso prolongado nem para teste de carga. Comece-o para a sessão de desenvolvimento e pare-o no fim, ou use --idle-timeout. Ele usa cerca de 160 MiB ocioso e cresce conforme você cria recursos; no Linux, o guia mostra como limitar a memória com um systemd scope de 2 GiB. Ferramentas de infraestrutura como código têm fidelidade variável; se o seu dia a dia inclui Terraform, o rascunho complementar AWS Emulator + Terraform: testes de infra sem conta AWS (slug aws-emulator-terraform-testes-infra-sem-conta-aws) cobre o provider, o terraform test e duas pegadinhas de S3 Control e SQS. Este artigo não republica esse recorte e não inventa URL pública, porque esse texto ainda não foi publicado no blog.
Conclusão
Sim: dá para desenvolver no localhost com o AWS Emulator. O fluxo que se sustenta no dia a dia é estreito e repetível. Você sobe o emulador, aponta o shell, confere a conta 000000000000, roda o seed, trabalha na API com hot reload e trata a reinicialização do emulador como começo limpo, não como continuação de ontem.