EC2 com Terraform, AWS CLI e console: o mesmo servidor três vezes
Quer começar no Terraform, mas sem saber como o que você já clica no console vira código? Aqui a mesma EC2 sai de três jeitos: pelo console, por um script da AWS CLI e por um main.tf do Terraform. Com os três lado a lado, dá para ver o que muda ao passar para infraestrutura como código.
O cenário: uma máquina de trabalho, três caminhos
A instância é uma máquina de trabalho na nuvem. Ela precisa de três coisas: um Security Group, uma role de instance profile e um User Data que deixe a máquina pronta quando ligar, com Docker, Git e outros itens já instalados.
Eu me conecto nela por SSM, um modelo mais seguro do que SSH. Por isso não uso par de chaves, e o Security Group só precisa de regra de saída.
Lancei três instâncias, uma por caminho: bia-manual, bia-cli e bia-terraform. Se você nunca lançou uma EC2 pelo console, comece por Lançar EC2 no novo console da AWS. O caminho completo é este:
1Preparar o Security Group e a role
Os dois recursos são criados no console e servem aos três caminhos.
- Security Group
bia-dev: removi a regra de entrada que existia (a da porta 3001) e deixei só a de saída. - Role
role-acesso-ssm: no IAM, uma role para EC2 com só a policyAmazonSSMManagedInstanceCore.
2Lançar pelo console
Em EC2, lançar instância: nome bia-manual, imagem x86, tipo t3.micro, sem par de chaves, a VPC padrão (a única que tenho), o Security Group bia-dev e, em Advanced details, o instance profile role-acesso-ssm.
Eu esqueci o User Data. Só percebi depois, ao abrir o User Data da instância e ver que estava vazio. É o ponto fraco do caminho manual: depende de lembrar de tudo, toda vez.
3Lançar pela AWS CLI
Para a CLI usei o CloudShell: clonei o projeto do GitHub, entrei na pasta scripts e rodei lancar_ec2_zona_a.sh. O script tem 14 linhas. Ele descobre a VPC padrão, a subnet e o Security Group, e só depois lança a instância:
vpc_id=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true --query "Vpcs[0].VpcId" --output text)
subnet_id=$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$vpc_id Name=availabilityZone,Values=us-east-1a --query "Subnets[0].SubnetId" ...)
security_group_id=$(aws ec2 describe-security-groups --group-names "bia-dev" --query "SecurityGroups[0].GroupId" --output text 2>/dev/null)
A segunda linha passa da borda da tela, e o resto dela não aparece no vídeo. Em seguida o script confere se o Security Group existe e aborta se não existir:
if [ -z "$security_group_id" ]; then
echo ">[ERRO] Security group bia-dev não foi criado na VPC $vpc_id"
exit 1
fi
Por fim, o comando que lança a instância:
aws ec2 run-instances --image-id ami-02f3f602d23f1659d --count 1 --instance-type t3.micro \
--security-group-ids $security_group_id --subnet-id $subnet_id --associate-public-ip-address \
--block-device-mappings '[{"DeviceName":"/dev/xvda","Ebs":{"VolumeSize":15,"VolumeType":"gp2"}}]' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=bia-dev}]' \
--iam-instance-profile Name=role-acesso-ssm --user-data file://user_data_ec2_zona_a.sh
A AMI é uma Amazon Linux 2023 da região da Virgínia. O script nomeia a instância bia-dev; eu renomeei para bia-cli no console.
O User Data
O --user-data aponta para um arquivo que roda quando a máquina liga. O conteúdo do user_data_ec2_zona_a.sh:
#!/bin/bash
#Instalar Docker e Git
sudo yum update -y
sudo yum install git -y
sudo yum install docker -y
sudo usermod -a -G docker ec2-user
sudo usermod -a -G docker ssm-user
id ec2-user ssm-user
sudo newgrp docker
#Ativar docker
sudo systemctl enable docker.service
sudo systemctl start docker.service
#Instalar docker compose 2
sudo mkdir -p /usr/local/lib/docker/cli-plugins
# (baixar o plugin do Compose: veja a documentação oficial)
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
#Adicionar swap
sudo dd if=/dev/zero of=/swapfile bs=128M count=32
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo echo "/swapfile swap swap defaults 0 0" >> /etc/fstab
#Instalar node e npm
curl -fsSL https://rpm.nodesource.com/setup_21.x | sudo bash -
sudo yum install -y nodejs
A linha que baixa o Docker Compose passa da borda da tela e o vídeo não mostra o destino do download inteiro, então não a reproduzo. Para essa etapa, siga a documentação oficial do Docker Compose para Linux. Na instância criada pelo script, o docker compose version mostrou a v2.23.3.
4Lançar com Terraform
Antes do código, dois pré-requisitos: o Terraform instalado e a AWS CLI com um profile configurado (o meu se chama bia-tf; o profile permite usar várias contas AWS na mesma máquina).
Crio um main.tf numa pasta vazia. No Terraform, tudo que você lança na AWS é um recurso (resource). Começo pelo bloco do provider, o destino da conexão:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "us-east-1"
profile = "bia-tf"
}
Depois o recurso da instância, com a mesma AMI, o mesmo tipo e os mesmos itens do script (o ID do Security Group está trocado por um placeholder):
resource "aws_instance" "bia-dev" {
ami = "ami-02f3f602d23f1659d"
instance_type = "t3.micro"
tags = {
Name = "bia-terraform"
}
vpc_security_group_ids = [ "sg-xxxxxxxxxxxxxxxxx" ]
user_data = file("userdata.sh")
iam_instance_profile = "role-acesso-ssm"
}
O userdata.sh é o conteúdo do arquivo acima, copiado para a raiz do projeto. O ID do Security Group você copia da tela de Security Groups do console.

Os comandos, na ordem:
terraform init
terraform plan
terraform apply
terraform initlê omain.tf, instala o provider e cria o arquivo.terraform.lock.hcl.terraform planmostra o que vai ser criado, sem criar. No meu caso: um recurso a adicionar, com AMI, tipo, instance profile, tag, referência ao User Data e Security Group.terraform applypede confirmação e cria. Respondiyes.
A bia-terraform apareceu no console com a mesma role e o mesmo Security Group das outras.

5Ver a diferença que o Terraform traz
Comparei o User Data das três em Edit user data: a manual estava vazia, a da CLI e a do Terraform tinham o script.
A grande diferença está no estado. Com a CLI, nada fica guardado localmente depois que o comando roda. Com o Terraform, ele guarda o estado do que foi criado (zona de disponibilidade, tipo, ID, role). Dá para ver com:
terraform show
Esse estado deixa a alteração simples. Troquei o Security Group da instância editando o ID em vpc_security_group_ids e rodei terraform apply. Ele atualizou o estado, comparou com o código e mostrou Plan: 0 to add, 1 to change, 0 to destroy: só o Security Group mudou. Confirmei, e no console o grupo já tinha trocado. Voltei ao grupo anterior do mesmo jeito.

Com a CLI, trocar o Security Group de uma instância existente exigiria um novo script.
6Conferir que as duas funcionam e destruir
Para ver se a máquina saiu pronta, me conectei na bia-cli por SSM, com o instance ID dela:
aws ssm start-session --target <instance-id> --profile bia-tf
Troquei para o ec2-user, e o Docker e o Docker Compose estavam instalados. Clonei o projeto (git clone https://github.com/henrylle/bia.git), entrei na pasta bia e rodei docker compose up -d. Repeti na instância do Terraform, que também tinha Docker e Docker Compose, e o build começou do mesmo jeito. Para entender melhor essa conexão sem SSH, veja AWS CloudShell e Session Manager.
Para apagar o que o Terraform criou:
terraform destroy
Ele lê o estado e destrói só o que lançou, no meu caso um recurso. A bia-manual e a bia-cli continuam; essas você termina à mão no console.
Checklist
- Instale o Terraform e configure um profile na AWS CLI.
- Crie o Security Group e a role com a policy
AmazonSSMManagedInstanceCore. - Escreva o bloco
providere oresource "aws_instance"nomain.tf. - Rode
terraform init,terraform plane só entãoterraform apply. - Confira no console a role, o Security Group e o User Data da instância.
- Teste a conexão por SSM e veja se o Docker subiu.
- Para trocar, por exemplo, o Security Group, edite o
main.tfe rodeterraform apply. - No fim,
terraform destroye termine à mão o que foi lançado fora dele.
O console serve para entender cada peça, a CLI repete o lançamento por script e o Terraform guarda o estado e muda só o que você alterou. É esse caminho que a Formação AWS percorre com um projeto inteiro, e aqui você viu só o primeiro recurso.
O que os alunos dizem

“Eu sempre fugi da AWS. Achava complicado, distante da minha rotina de dev. Mas só a primeira aula do Bootcamp já mudou tudo: usei o conteúdo para resolver um problema real de um cliente e fui contratado por causa disso.”
André Scherrer, Engenheiro de Software
Ver depoimento
“Só quando coloquei a mão na massa com a Formação AWS é que avancei de verdade. Essa prática me levou ao próximo nível”
Caio, Desenvolvedor (Dev)
Ver depoimento
“Eu trabalhava há mais de 20 anos com TI tradicional e atendia empresas da região, mas sentia minha carreira estagnada.”
Carlos Souza, Consultor Cloud
Ver depoimento
Formação AWS
Aprenda AWS e IA na prática, com projeto real e acompanhamento. Converse com o time sobre a Formação AWS.
Agende uma conversaPrefere assistir?
A aula completa (33 min) está no canal Domine AWS. O vídeo toca aqui mesmo.

