Desafio 02: Rodei a BIA no ECS com ALB em 2 AZs, e aprendi a pegadinha do VITE_API_URL
Como configurei a aplicação BIA no ECS EC2 launch type com ALB em alta disponibilidade, e o problema que quase me fez perder horas: VITE_API_URL hardcoded no Dockerfile.
// ÍNDICE ▾
Desafio 02: Rodei a BIA no ECS com ALB em 2 AZs, e aprendi a pegadinha do VITE_API_URL
Este é o segundo de seis desafios do mês de Conectividade e Redes na AWS da Formação AWS 5.0. Se o desafio 01 foi sobre criar uma VPC manualmente e lançar a BIA em uma EC2 pública, o desafio 02 sobe o nível: ECS EC2 launch type + Application Load Balancer + Multi-AZ.
O que foi construído
A arquitetura final conta com:
- VPC customizada
bia-vpc-02(10.2.0.0/16) com 2 subnets públicas, uma emus-east-1ae outra emus-east-1b - 2 EC2 t3.small funcionando como ECS Container Instances (uma por AZ)
- ECS Cluster
bia-cluster-02com Capacity Provider + Auto Scaling Group - ECS Service
bia-svc-02comdesired_count=2e rolling deployment - ALB internet-facing recebendo na porta 80 e roteando para as tasks na porta 8080
- ECR
bia-repo-02com lifecycle policy (mantém as 5 últimas imagens) - RDS PostgreSQL 17.4
bia-rds-02Single-AZ (db.t3.micro) acessível apenas internamente - EC2 bia-dev t3.micro para build, push e execução de migrations via SSM
Todo provisionamento foi feito via Terraform, consumindo módulos compartilhados (vpc, bia-baseline, alb) do monorepo.
ECS EC2 launch type vs Fargate: por que escolhi EC2?
A escolha foi deliberada e educacional. O Fargate é serverless, você define a task e a AWS gerencia onde ela roda. Já o EC2 launch type exige que você configure:
- Capacity Provider: conecta o ECS ao ASG e gerencia o scaling das instâncias
- Launch Template: define AMI ECS-optimized, tipo de instância,
user_dataque registra a EC2 no cluster via/etc/ecs/ecs.config - Auto Scaling Group: mantém o mínimo de instâncias disponíveis para hospedar as tasks
No desafio 04, quando formos para subnet privada com NAT Gateway, o Fargate entra em cena. Aprender EC2 launch type agora faz sentido para entender o que o Fargate abstrai.
A pegadinha do VITE_API_URL (e como resolver)
Esse foi o problema mais importante do desafio, e o que mais vai fazer diferença em projetos reais.
O Dockerfile do repo henrylle/bia tem a seguinte linha:
RUN cd client && VITE_API_URL=http://localhost:3001 npm run build
O Vite compila o frontend React em arquivos estáticos. Isso significa que VITE_API_URL não é uma variável de runtime, ela é resolvida no momento do npm run build e gravada literalmente no bundle JavaScript que vai para o navegador.
Passei --build-arg VITE_API_URL=http://meu-alb.amazonaws.com no docker build, mas o Dockerfile não tinha a diretiva ARG, então o parâmetro foi ignorado silenciosamente e a imagem foi construída apontando para localhost.
A solução: aplicar dois patches antes do build:
# Declara o ARG (aceita o --build-arg externo)
sed -i '/RUN cd client && VITE_API_URL=http/i ARG VITE_API_URL=http://localhost:3001' Dockerfile
# Usa a variável em vez do valor hardcoded
sed -i 's|VITE_API_URL=http://localhost:3001 npm run build|VITE_API_URL=${VITE_API_URL} npm run build|' Dockerfile
Depois do patch, o build com ALB ficou assim:
docker build \
--build-arg VITE_API_URL=http://bia-02-alb-xxx.us-east-1.elb.amazonaws.com \
-t <ecr_url>:latest .
Automatizei isso no script scripts/build-push.sh, que verifica se o patch já foi aplicado antes de rodar, idempotente.
awsvpc no EC2 launch type: detalhe que pegou
O ECS Service usa network_mode = "awsvpc", cada task recebe uma ENI (Elastic Network Interface) própria. Isso tem uma consequência importante nos Security Groups:
O ALB precisa acessar diretamente o IP da task (não o IP da EC2 que a hospeda). Portanto, o Security Group das tasks (bia-ecs-tasks-02) precisa ter ingress do SG do ALB, e não do SG das instâncias EC2.
Se você deixar o ingress apontar para o SG das instâncias, o health check do ALB vai falhar com unhealthy.
Migrations via container efêmero
Uma das melhores práticas que aprendi neste desafio: rodar migrations sem SSH.
Em vez de entrar no container em execução, usei um docker run --rm efêmero direto na bia-dev:
docker run --rm \
-e DB_HOST=bia-rds-02.ccx4oksoqgoo.us-east-1.rds.amazonaws.com \
-e DB_PORT=5432 \
-e DB_USER=postgres \
-e DB_NAME=bia \
-e DB_PWD=<senha> \
<ecr_url>:latest \
npx sequelize db:migrate
O container conecta no RDS, roda as migrations e sai. Clean, rastreável, sem estado.
Custos: ~$0.23 em 3 horas
| Serviço | Custo |
|---|---|
| 2× EC2 t3.small (ECS instances) | ~$0.14 |
| RDS db.t3.micro | ~$0.05 |
| ALB | ~$0.02 |
| Outros (EBS, CloudWatch) | ~$0.02 |
| Total | ~$0.23 |
O maior custo foi as EC2 do ASG, e essa é a lição FinOps: no EC2 launch type, as instâncias ficam alocadas 24/7 independente de ter tasks rodando. Se o objetivo é economizar, Fargate é mais eficiente para workloads intermitentes.
Resultado
$ curl -I http://bia-02-alb-2133079818.us-east-1.elb.amazonaws.com
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
X-Powered-By: Express
BIA no ar, tarefa cadastrada, ALB balanceando entre 2 AZs.
Próximo desafio: EC2 + SSH + SSM + Instance Connect, foco em modelos de conectividade e acesso seguro a instâncias.
Código completo
Repositório: formacao-aws-desafios-conectividade-redes-aws
Parte da série Formação AWS 5.0, Mentoria Desafio Labs 2.0 · Mai/2026 · Henrylle Maia
// Relacionados