Deploy de aplicações na AWS com ECS Fargate

    Foto do autor Rafael Sotero
    Rafael Sotero
    Compartilhar
    Compartilhar no LinkedInCompartilhar no FacebookCompartilhar no XCompartilhar no WhatsApp
    Imagem banner do post

    Conhecer orquestradores de containers se tornou requisito mínimo para Engenheiros de Software. Ferramentas como o ECS (Elastic Container Service) resolvem os desafios de ambiente e deploy que máquinas virtuais tradicionais não conseguem entregar sozinhas.

    Como o AWS ECS transforma a maneira de enviar aplicações Spring Boot para produção? Qual a diferença dele para o Kubernetes? Vamos entender de perto como usar e responder essas dúvidas.

    Até o final desse artigo teremos criado uma aplicação Spring Boot, provisionado a infra na AWS e feito o deploy no ECS com Fargate. Você vai precisar de uma conta AWS. Caso ainda não possua, siga o passo a passo oficial.

    ℹ️ Cobranças podem ser geradas, porém seguindo o passo a passo iremos excluir a infraestrutura ao final para evitar custos extras.

    Orquestração de Containers

    Em ambientes tradicionais, o deploy acontecia diretamente em máquinas virtuais onde você configurava o sistema operacional, instalava dependências e subia a aplicação. Containers mudaram esse cenário ao empacotar a aplicação junto com tudo que ela precisa, garantindo consistência entre ambientes.

    Mas rodar containers em produção traz novos desafios: garantir disponibilidade, escalar sob demanda e substituir instâncias que falhem. O Orquestrador de Containers resolve isso, gerenciando o ciclo de vida de forma declarativa, cuidando de provisionamento, escalonamento e recuperação automática.

    O que é o AWS ECS?

    O AWS ECS (Elastic Container Service) é o orquestrador de containers nativo da AWS. Ele se integra diretamente com serviços como ALB (Application Load Balancer), CloudWatch e IAM, eliminando a necessidade de configurações extras para conectar essas peças. No ECS, você trabalha com cinco conceitos principais:

    1. Cluster: agrupamento lógico que reúne seus services e recursos.
    2. Service: garante que as tasks estejam sempre rodando na quantidade desejada.
    3. Task Definition: receita do container onde você define imagem, CPU, memória e variáveis de ambiente.
    4. Task: instância em execução de uma Task Definition.
    5. Container: processo que roda dentro de cada Task, empacotando sua aplicação.

    Ao criar um Service, você escolhe o tipo de infraestrutura: EC2 ou Fargate.

    ECS com EC2

    No modo EC2, você provisiona e gerencia as instâncias (máquinas virtuais). Isso dá controle total sobre o ambiente, mas você assume a responsabilidade de patching, dimensionamento e manutenção das máquinas. Útil se você precisa controlar o Sistema Operacional ou incluir máquinas virtuais fora da AWS para suportar seu cluster.

    ECS com Fargate

    No modo Fargate, a AWS cuida de toda a infraestrutura. Você define apenas CPU e memória da task, e o container roda sem que você precise saber em qual servidor ele está. Não há instâncias para gerenciar, não há sistema operacional para atualizar. Usaremos o Fargate neste artigo justamente por essa simplicidade, que nos permite focar na aplicação e não na infraestrutura.

    ECS vs K8S

    O Kubernetes (K8S) é o líder de mercado para orquestração multi-cloud. Ele oferece um ecossistema gigante e enorme flexibilidade, mas cobra seu preço em alta complexidade operacional e curva de aprendizado íngreme.

    Já o AWS ECS vai na direção oposta, priorizando a simplicidade. Sendo nativo da nuvem AWS, ele elimina a gestão do orquestrador. Combinado com Fargate, a equipe foca apenas em definir a imagem e os recursos da aplicação, enquanto a AWS cuida de toda infraestrutura subjacente.

    O AWS EKS (Elastic Kubernetes Service) é o meio-termo: gerencia o control plane pela AWS, mantendo a estrutura padrão do Kubernetes para quem não abre mão do ecossistema K8S (como Helm e operadores).

    CritérioAWS ECSAWS EKS (Kubernetes gerenciado)Kubernetes (K8S puro)
    Operação e GestãoBaixa (Foco total na aplicação)Média (Maior manutenção de componentes K8S)Alta (Gestão completa do ambiente e control plane)
    PortabilidadeRestrito à nuvem AWSAlta (Compatível com padrão K8S global)Total (Roda em qualquer infra ou cloud)
    Curva de aprendizadoBaixa (Ideal para desenvolvedores)AltaMuito Alta
    Integração AWSImediata e transparenteRequer configuração extraComplexa e manual
    ExtensibilidadeSoluções nativas da AWS (CloudWatch, Auto Scaling, CodeDeploy)Add-ons e operadores de terceiros (Prometheus, Karpenter, ArgoCD)Add-ons e operadores de terceiros (Prometheus, Karpenter, ArgoCD)

    Em resumo: Se o foco do time é Multi-Cloud ou casos de uso mais complexos, vá de EKS/K8S. Se o objetivo é resolver o problema e entregar rápido, sem dor de cabeça com gestão de infraestrutura e com integração nativa AWS, o ECS com Fargate é a decisão mais objetiva.

    Nosso projeto

    Para colocar o ECS em prática, vamos construir a FilePack API: uma aplicação Spring Boot onde o usuário envia vários arquivos, informa uma senha e solicita a geração do pacote. A API recebe os uploads, compacta tudo em um .zip protegido por senha e devolve o artefato para download.

    A aplicação não guarda estado: recebe uma requisição multipart/form-data junto com a senha, empacota e devolve para o consumidor. Cenário particularmente útil para transferir arquivos pela rede. Além disso, operações de compactação e criptografia têm custo computacional alto, o que vai permitir explorar uso de CPU e memória futuramente.

    Aplicação

    Nossa aplicação vai utilizar:

    • Java 25 como versão da JDK
    • Spring Boot 4.x como base do projeto
    • Spring Web para receber uploads e devolver o download do .zip
    • Spring Boot Actuator para expor health checks e métricas básicas
    • Zip4j para gerar arquivos .zip protegidos por senha
    • Docker para empacotar a aplicação em uma imagem de container

    Infraestrutura

    Usaremos IaC (Infrastructure as Code) com AWS CloudFormation para provisionar todos esses recursos de forma automatizada e replicável:

    • Amazon ECR para armazenar a imagem Docker da aplicação
    • Amazon ECS Fargate como orquestrador de containers
    • Application Load Balancer para expor a aplicação por HTTP na internet
    • Amazon CloudWatch Logs para centralizar os logs da aplicação
    • IAM para papéis e permissões mínimas entre ECS, ECR e CloudWatch

    AWS CLI

    Para fazer o deploy de novas versões e visualizar as configurações da AWS, usaremos o AWS CLI V2. Faça o download aqui.

    ⚠️ A partir daqui você precisa estar logado na AWS para continuar.

    Mão na Massa

    A aplicação está disponível no repositório do blog, clique aqui para acessar.

    Nosso foco não vai ser o código, todavia é importante que você saiba que essa classe de serviço é o coração da nossa aplicação. O FilePackService recebe os arquivos, aplica criptografia AES e gera o pacote final:

    Copiar
    private File createEncryptedZip(List<File> files, String password) throws IOException {
    	ZipParameters params = new ZipParameters();
    	params.setEncryptFiles(true);
    	// AES é a opção mais segura suportada pelo formato zip
    	params.setEncryptionMethod(EncryptionMethod.AES);
    
    	// arquivo de saída no diretório temporário
    	File target = File.createTempFile("archive-", ".zip");
    	ZipFile zipFile = new ZipFile(target, password.toCharArray());
    	zipFile.addFiles(files, params);
    	return target;
    }

    De forma simples:

    • Recebemos uma lista de arquivos
    • Compactamos em um Zip
    • Protegemos com senha AES
    • Retornamos o zip para download
    • Esvaziamos os arquivos temporários do disco

    A dependência que usamos para gerar o Zip com senha é uma das melhores dentro do Java, chama-se Zip4j, referência aqui.

    Lembre-se de realizar o clone do repositório e entrar na pasta do projeto, execute os comandos via GitBash:

    Copiar
    git clone https://github.com/devsuperior/blog.git
    cd articles/deploy-de-aplicacoes-na-aws-com-ecs-fargate/projects/filepack-api 

    ⚠️ Você precisa estar logado na AWS e com o AWS CLI instalado para continuar daqui em diante. Sempre execute os comandos no GitBash mais atualizado quando estiver no Windows.

    Obtendo as credenciais

    • Acesse o Console da AWS e faça login.
    • No GitBash, digite aws login:

    |697x224 Caso seja solicitado região, digite us-east-1.

    • Verifique o login:

    aws ec2 describe-regions --region-names us-east-1 --output table

    Criando a infraestrutura

    Agora que você está logado, podemos iniciar a criação da infraestrutura. Na pasta de infraestrutura do nosso projeto temos 2 arquivos .yml do CloudFormation:

    • infra/1-ecr.yml: Faz a criação da imagem ECR. ECR é similar ao Dockerhub, é onde versionamos as imagens Docker da nossa aplicação.
    • infra/2-ecs.yml: Faz a criação do restante da infraestrutura (ECS, LoadBalancer, etc).

    Entre o deploy do 1-ecr.yml e do 2-ecs.yml faremos a geração da imagem Docker e envio para o repositório remoto criado. Então, vamos iniciar a criação da nossa Infra em 3 etapas.

    Criando repositório ECR (1-ecr.yml)

    Inicie criando o repositório onde armazenaremos nossa imagem Docker:

    Copiar
    aws cloudformation create-stack \
      --stack-name filepack-ecr \
      --template-body file://infra/1-ecr.yml
    
    aws cloudformation wait stack-create-complete \
      --stack-name filepack-ecr

    Exportando os outputs da stack para variáveis locais. Em vez de depender de adivinhações manuais, vamos filtrar os Outputs que nossa infraestrutura enviou como resposta nativa da stack:

    Copiar
    # 1. Fazemos uma única chamada e trazemos os parâmetros como texto formatado, ganhando performance
    OUTPUTS_ECR=$(aws cloudformation describe-stacks --stack-name filepack-ecr --query "Stacks[0].Outputs[*].[OutputKey,OutputValue]" --output text)
    
    # 2. Extraímos as chaves localmente usando o 'awk', que é nativo no Linux, Mac e no Git Bash (Windows)
    export ACCOUNT_ID=$(echo "$OUTPUTS_ECR" | awk '$1 == "AccountId" {print $2}')
    export REPO_NAME=$(echo "$OUTPUTS_ECR" | awk '$1 == "RepositoryName" {print $2}')
    export REPO_URI=$(echo "$OUTPUTS_ECR" | awk '$1 == "RepositoryUri" {print $2}')
    
    # Conferindo a extração:
    echo "Injetando no repositório $REPO_NAME via DNS: $REPO_URI"
    Gerando e subindo imagem Docker para ECR

    O próximo passo é autenticar o Docker local no ECR recém-criado, fazer o build da aplicação e enviá-la para o repositório:

    Copiar
    # Autentica e loga o Docker no ECR
    aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com
    
    # Cria o build da imagem Docker
    docker build -t filepack-api .
    
    # Associa a tag remota e envia a aplicação
    docker tag filepack-api:latest ${REPO_URI}:latest
    docker push ${REPO_URI}:latest
    Criando restante da infraestrutura do ECS (2-ecs.yml)

    Com nossa imagem disponível no ECR, usaremos o CloudFormation para provisionar a infraestrutura em volta da aplicação. O arquivo 2-ecs.yml será o responsável por criar:

    • AWS::ECS::Cluster: Agrupamento lógico da infraestrutura ECS, onde ficam os serviços.
    • AWS::ECS::Service: Gerenciador que garante a quantidade desejada de Tasks Fargate sempre rodando.
    • AWS::ElasticLoadBalancingV2::LoadBalancer: Application Load Balancer que serve de entrada às requisições HTTP.
    • AWS::Logs::LogGroup: Centralizador de logs do CloudWatch.
    • AWS::IAM::Role: Criação das Task e Execution Roles com permissões essenciais.

    Como a stack está criando permissões de segurança (os IAM Roles), o comando exige o uso explícito da flag de --capabilities:

    Copiar
    aws cloudformation create-stack \
      --stack-name filepack-ecs \
      --template-body file://infra/2-ecs.yml \
      --capabilities CAPABILITY_NAMED_IAM
    
    aws cloudformation wait stack-create-complete \
      --stack-name filepack-ecs

    Extraindo a URL do Load Balancer. Após a conclusão do deploy, o CloudFormation retorna nas saídas a URL pública:

    Copiar
    # Extraindo o DNS público do nosso Load Balancer com query
    export ALB_URL=$(aws cloudformation describe-stacks --stack-name filepack-ecs --query "Stacks[0].Outputs[?OutputKey=='LoadBalancerUrl'].OutputValue" --output text)
    
    echo "Essa é a URL da noss aplicação: $ALB_URL"

    Testando a saúde do serviço. Com o endereço devidamente exportado em $ALB_URL, já podemos testar imediatamente invocando o endpoint /actuator/health do Spring Actuator:

    Copiar
    curl -k -v http://$ALB_URL/actuator/health

    Você deve ver "status": "UP". Seu back-end e infraestrutura AWS estão prontos para receber requisições diretamente da internet! 🎉

    Testando o FilePack

    O repositório do projeto conta com uma pasta data que possui arquivos de teste. Pelo terminal, vamos disparar uma requisição referenciando esses arquivos e gerando nosso pacote .zip protegido:

    Copiar
    curl -X POST $SERVICE_URL/api/filepack \
      -F "files=@data/infrastructure_config.xml" \
      -F "password=teste123" \
      --output validation_pack.zip

    O validation_pack.zip foi baixado. Verifique seu diretório e note que os arquivos internos só podem ser extraídos fornecendo a senha correta!

    Manuseando o ECS

    Saber como acompanhar a sua infraestrutura é essencial. Com o AWS CLI, conseguimos visualizar detalhes de execução no formato de tabela:

    Copiar
    # Verifica o status do Service: quantas tasks estamos pedindo x quantas estão rodando
    aws ecs describe-services \
      --cluster filepack-cluster \
      --services filepack-service \
      --query "services[*].{Status:status, Desired:desiredCount, Running:runningCount, TaskDefinition:taskDefinition}" \
      --output table
    
    # Identifica o status atual e a versão associada para todos os containers ativos no momento
    aws ecs describe-tasks \
      --cluster filepack-cluster \
      --tasks $(aws ecs list-tasks --cluster filepack-cluster --query "taskArns" --output text) \
      --query "tasks[*].{LastStatus:lastStatus, Version:taskDefinitionArn}" \
      --output table

    Atualizando a Aplicação (Deploy)

    A maior vantagem da orquestração é a fluidez na entrega contínua. Vamos testar adicionando um novo endpoint no controller Java:

    Copiar
    import org.springframework.web.bind.annotation.GetMapping;
    import java.util.Map;
    
    @GetMapping("/hello")
    public Map<String, String> hello() {
        return Map.of("hello", "world");
    }

    Faça o build da imagem contendo a nova funcionalidade e envie para o ECR substituindo a tag latest:

    Copiar
    docker build -t filepack-api .
    docker tag filepack-api:latest ${REPO_URI}:latest
    docker push ${REPO_URI}:latest

    Como mantivemos a mesma tag (latest) já mapeada em nossa Task Definition, não precisamos criar uma nova revisão. Basta ordenar ao ECS que force um novo deploy no nosso Service, fazendo o orquestrador baixar a imagem recém-atualizada:

    Copiar
    # Dispara o Rolling Update, matando containers da versão antiga e subindo a nova imagem gradualmente
    aws ecs update-service \
      --cluster filepack-cluster \
      --service filepack-service \
      --force-new-deployment

    Em poucos instantes após a atualização, sua nova versão responderá corretamente: curl http://$ALB_URL/hello.

    Limpeza dos Recursos (Clean Up)

    Para evitar surpresas nos custos, é imprescindível limpar o ambiente após os testes. Você precisa destruir o que foi criado operando de modo inverso à criação:

    • Desprovisionar a Stack Principal (ECS/ALB/Roles)
    • Forçar deleção das imagens no ECR via CLI (o CloudFormation protegerá e abortará a exclusão se existirem imagens residuais)
    • Desprovisionar o repositório base ECR
    Copiar
    # 1. Desprovisiona a Stack Principal
    aws cloudformation delete-stack --stack-name filepack-ecs --region us-east-1
    aws cloudformation wait stack-delete-complete --stack-name filepack-ecs --region us-east-1
    
    # 2. Limpa o repositório ECR das imagens associadas
    aws ecr delete-repository --repository-name ${REPO_NAME:-filepack-api} --force --region us-east-1
    
    # 3. Finaliza deletando o resíduo do ECR e sua stack
    aws cloudformation delete-stack --stack-name filepack-ecr --region us-east-1
    aws cloudformation wait stack-delete-complete --stack-name filepack-ecr --region us-east-1

    Ambiente limpo! Você executou com sucesso todo o ciclo de vida e usufruiu da flexibilidade nativa entre AWS Fargate e ECS.

    Conclusão

    Você já tem o básico para colocar seu serviço online, que tal continuar experimento a os serviços em Cloud da AWS?

    Embora conteinerizar e expor serviços imponha desafios, pudemos ver como o AWS ECS aliado ao modelo Fargate quebra barreiras e tira o fardo do provisionamento direto de máquinas. Ele absorve grande parte da complexidade operacional, entregando uma plataforma robusta e escalável, para que a engenharia de software seja o grande e único foco.

    Companhias com operações gigantescas no Brasil e no mundo adotam ativamente a nuvem AWS como base. Temos líderes nacionais de mercado como Nubank e iFood rodando workloads resilientes no ecossistema de infraestrutura AWS. Dominar essas ferramentas te posiciona fortemente em um excelente nicho de mercado.

    E não precisamos estacionar nesse tutorial. Você pode levar a mesma arquitetura para um novo patamar ativando o Application Auto Scaling para crescer a quantidade de instâncias quando a API ficar sob estresse, ou integrar o uso da aplicação com o Amazon S3 para armazenamento persistente. O avanço na nuvem agora depende apenas de você!

    Acesse o projeto completo aqui.

    Foto do autor Rafael Sotero
    Rafael Sotero
    Especialista em Engenharia de Software
    Tenho mais de 12 anos de experiência na concepção e implementação de soluções robustas e escaláveis. Atuei em grandes empresas do setor financeiro e telecom levando inovação e simplicidade em ambientes distribuídos com Java, Python, e Cloud AWS.