# LocalStack: Desenvolver e testar aplicações AWS localmente com eficiência

# Introdução

Desenvolver aplicações que utilizam serviços em nuvem como AWS pode ser desafiador. Cada teste feito diretamente na cloud provider pode gerar custos e demanda tempo extra. Nesse contexto, surge o LocalStack, uma ferramenta poderosa que permite replicar localmente serviços AWS, agilizando o ciclo de desenvolvimento e reduzindo custos.

# O que é o LocalStack e como funciona?

O LocalStack é uma ferramenta de código aberto que simula serviços essenciais da AWS como Lambda, S3, DynamoDB, SNS, SQS, API Gateway, entre outros. Tudo através de containers Docker. Ele emula a interface e comportamento desses serviços permitindo testes locais com alta fidelidade em relação ao ambiente real. Dessa forma, não é necessário que os desenvolvedores tenham conexão à nuvem real, proporcionando um ambiente de desenvolvimento mais rápido e seguro.

O LocalStack expõe interfaces que replicam as APIs da AWS, permitindo que ferramentas e bibliotecas que interagem com a AWS funcionem com o LocalStack com pouca ou nenhuma modificação. Por exemplo, ao usar a AWS CLI, é possível direcionar os comandos para o LocalStack especificando o endpoint local, como `--endpoint-url=`[`http://localhost:4566`](http://localhost:4566). Além disso, o LocalStack pode ser integrado em pipelines de CI/CD, permitindo testes automatizados de infraestrutura como código (IaC) usando ferramentas como Terraform. Essa integração facilita a validação de configurações e a detecção precoce de erros, melhorando a eficiência do ciclo de desenvolvimento.

# Instalação e setup inicial

Sem muita firula já vamos para parte prática, então para começar basta seguir os passos abaixo.

Primeiramente temos que ter instalado o [Docker](https://docs.docker.com/get-started/get-docker) e o [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).

O LocalStack pode ser utilizado de duas formas principais:

1. Instalando a ferramenta no sistema operacional
    
2. Rodando via Docker
    

Neste artigo vamos explorar apenas a execução via Docker Compose para melhor portabilidade. Então, no projeto onde queremos utilizar o LocalStack, vamos criar ou editar o arquivo docker-compose.yml para adicionar o serviço:

```yaml
 services:
  localstack:
    image: localstack/localstack:4
    ports:
      - "4566:4566"
      - "4510-4559:4510-4559"
    environment:
      - DEBUG=${DEBUG:-1}
      - PERSISTENCE=${PERSISTENCE:-1}
      - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR:-docker}
      - LAMBDA_REMOVE_CONTAINERS=true
      - DOCKER_HOST=unix:///var/run/docker.sock
      
      - SERVICES=s3,lambda,rds,sts,iam,logs,cloudformation
      
      - RDS_PORT_RANGE=5432-5433
      
      - LAMBDA_DOCKER_NETWORK=localstack_default
      
      - AWS_ENDPOINT_URL=http://localhost:4566
      
    volumes:
      - "./volume:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "./localstack-setup.sh:/etc/localstack/init/ready.d/init-aws.sh"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s
```

Deve-se atentar a variável de ambiente “SERVICES” que contém a lista dos serviços que será utilizado. Para saber sobre os serviços veja na [documentação oficial](https://docs.localstack.cloud/user-guide/aws/feature-coverage/).

Após a configuração correta do docker-compose.yml, rode o serviço com `docker compose up -d`.

Para testar se tudo está funcionando você deve ter configurado o AWS CLI corretamente. Digite `aws configure` e insira uma access key e secret key qualquer, pois não será utilizada pelo local stack, e a region deixe “us-east-1” (ou outra qualquer), já o output format deixe “json”, caso prefira pode ser utilizado o recurso de [profiles](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html) também. Após isso podemos executar o comando `aws --endpoint-url=`[`http://localhost:4566`](http://localhost:4566) `s3 ls` e esperar uma resposta vazia porém sem erros. Se preferir pode criar um bucket de teste e depois verificar na lista se criou corretamente: `aws --endpoint-url=`[`http://localhost:4566`](http://localhost:4566) `s3api create-bucket --bucket test-bucket`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748816519875/9c39dbfd-b242-4d55-9346-e2ae5487eecb.png align="center")

# Exemplo prático

Para exemplificar, criaremos uma função Lambda em Go. O repositório do projeto também conterá o código da infraestrutura como código (IaC) utilizando Terraform. O projeto se trata de uma API simples em Go com um endpoint para fazer upload de arquivos txt para um bucket S3. Sempre que um arquivo for enviado para o bucket, será automaticamente disparada uma função Lambda também escrita em Go, que realizará uma ação simples, como ler e imprimir os conteúdo do arquivo no log. Dessa forma teremos o código fonte e a infraestrutura de forma real no repositório. O projeto terá a seguinte arquitetura:

![PlantUML diagram](https://cdn-0.plantuml.com/plantuml/png/TPAnQiCm48PtFSNXfSxWXf8jGqaQGWW4Od1AXwR3HLA26rldId8eb3nCfpv1bwv-iPvjBMa9U93l_kyxdz74URI-BWiXP46rWcX54elCepn2F7tAe7iVHF0X0FOMImtHHbkBUx8bB77MTm0abHKPRJn43xKyQW_PQ6iEb62L9tLL42eN0JhOTTs_p0hBdS8MkBjzxt95L-7SMPo5CAHuyZHTGhoWy3dFSwCgoXbFrza6XlsKbpQEqfE5wD7LpQVDQJWhSXwZEx7ipaBSm99B5fklvbixqPSXIUwlYM4CStFA4T2-rVc9jYPk_qAYqED0Y6iK6yFIB4XhlzwzQkaX3XOcGvU3OMiERcn-E6ZBCznet2Cpgz5hnK18NPWN-hyv12_DgIMfdSEIr-KT584autNpmoT-0Yp4H1lLtenV align="left")

![PlantUML diagram](https://cdn-0.plantuml.com/plantuml/png/PP2nIiH048RxVOgVL4aX5kckaDCc20SNsBEpcUmkORqa4sTdVJXRNyp4tCc9tGpVnpSm-wGaciV1a5CMlAHC4jdC91fTd6bI58_jCnekG0dBQ3mfTPG22blZARjpK9G7TZHO9NUkVaHR_sqSQEmyeMnuuzjkpEKWxXxMzWxjqPvmd-U1oQCaUS_nWokpi5MnzM9aFNPlmUaDjlLAj-OE9ubz7-INmqaalsYRS0qruN_eQjpKMc4NKYB1nNgToZZE4iS0nvE6hq_Fc1W3zvNPXyclx_m6 align="left")

Primeiro vamos criar uma api com Go, utilizando Gin. Na api teremos uma rota POST “/upload” que receberá um arquivo e o enviará para o S3. Como o foco não é a construção de uma api, manterei detalhes apenas da comunicação com a AWS. Será utilizado o SDK oficial da AWS ([https://github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go)), e como qualquer SDK oficial da AWS, temos a opção “Endpoint” onde podemos alterar o valor para uma url local juntamente com as opções de region, credenciais, ssl, etc. Então nossa instância do S3 terá a seguinte configuração:

```go
s, _ := session.NewSession(&aws.Config{
	Region:           aws.String("us-east-1"),
	Endpoint:         aws.String("http://localhost:4566"),
	S3ForcePathStyle: aws.Bool(true),
})

uploader := s3.New(s)
```

Com isso temos o SDK apontando corretamente para o LocalStack, e é claro que essa configuração deve ser alterada para ambiente de produção ou ser controlada via variável de ambiente.

Para implantar os recursos AWS e conseguir replicar a mesma configuração em ambientes produtivos, irei utilizar Terraform. A utilização do terraform é exatamente a mesma que você teria no dia a dia, porém o provider da AWS a gente vai configurar para apontar para o LocalStack:

```yaml
provider "aws" {
  region                      = "us-east-1"
  access_key                  = "test"
  secret_key                  = "test"
  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_requesting_account_id  = true
  endpoints {
    s3     = "http://localhost:4566"
    lambda = "http://localhost:4566"
    iam    = "http://localhost:4566"
  }
}
```

O projeto ficou bem simples, com apenas um arquivo (main.tf) para o terraform e os arquivos “cmd/api/main.go” e “cmd/file-processor/main.go” contendo a lógica da api e lambda respectivamente. Foi criado também um Makefile para automatizar as nossas tarefas de subir ambiente, build, etc. Então podemos rodar um “make prepare” ou apenas “make” para:

1. Executar o build das aplicações Go
    
2. Zipar o arquivo binário que será executado pela lambda
    
3. Iniciar o LocalStack (Docker Compose)
    
4. Provisionar a infraestrutura no LocalStack através do Terraform
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1749165478569/9f8ea2ad-e864-4e78-9fed-b43223d30f96.png align="center")

Com isso já temos o projeto rodando com S3, Lambda e CloudWatch Logs completamente no LocalStack. Vamos executar um teste enviando um arquivo “teste.txt” para o endpoint da api contendo apenas um “hello world”. O código que a lambda vai executar será apenas exibir o conteúdo do arquivo nos logs. Assim como na API, o código da lambda deve adicionar a opção de endpoint apontando para o LocalStack:

```go
func handler(ctx context.Context, s3Event events.S3Event) (string, error) {
	sess, _ := session.NewSession(&aws.Config{
		Region:           aws.String("us-east-1"),
		Endpoint:         aws.String("http://localstack:4566"),
		S3ForcePathStyle: aws.Bool(true),
	})
...
```

Uma observação é que como a lambda é executada na rede criada pelo Docker Compose, ao invés de localhost devemos colocar apenas o nome do serviço. Com isso a lambda ao ser executada já pode obter o arquivo do S3 para exibir o conteúdo. Vamos executar um curl enviando o arquivo e em seguida o comando `make lambda-logs` para exibir os logs da execução da lambda através do AWS CLI, mas primeiro em outro terminal devemos rodar a api com `make run-api`:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1749166299018/d0cffe87-d333-4450-ae24-89aabc15430e.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1749166319377/1372de12-c9f8-441b-ad73-c4ca8b483e69.png align="center")

Perfeito! A API realizou o upload corretamente para o S3 e em seguida houve um trigger que executou a função lambda em um ambiente completamente local e muito parecido com ambiente real de produção.

Todo o código fonte deste projeto exemplo pode ser encontrado no Github: [http://github.com/davidpvilaca/localstack-basics-article](http://github.com/davidpvilaca/localstack-basics-article).

# Conclusão

Utilizar o LocalStack como ambiente de desenvolvimento local para aplicações que interagem com serviços da AWS oferece inúmeras vantagens:

* **Eficiência**: Reduz o tempo de desenvolvimento e testes, permitindo ciclos mais rápidos.
    
* **Custo:** Evita custos associados ao uso de recursos reais da AWS durante o desenvolvimento.
    
* **Segurança:** Permite testes em um ambiente isolado, evitando impactos em ambientes reais.
    
* **Produtividade:** Facilita a automação de testes e integrações contínuas, melhorando o fluxo de trabalho.
    

Ao integrar o LocalStack com ferramentas como Terraform, desenvolvedores podem simular com precisão o comportamento de serviços da AWS em um ambiente local. Isso não apenas acelera o desenvolvimento, mas também proporciona uma maior confiança na qualidade e estabilidade das aplicações antes de serem implantadas em ambientes de produção.

Em resumo, o LocalStack é uma ferramenta essencial para desenvolvedores que buscam eficiência, economia e segurança no desenvolvimento de aplicações baseadas em serviços da AWS.
