# EC2 com Terraform, AWS CLI e console: o mesmo servidor três vezes

> A mesma EC2 com Security Group, role SSM e User Data lançada pelo console, por um script da AWS CLI e por um main.tf do Terraform, lado a lado.

- Autor: Henrylle Maia, consultor AWS
- Publicado em: 2026-10-15
- Assuntos: Carreira, Segurança
- URL: https://pages.formacaoaws.com.br/blog/ec2-com-terraform-cli-e-console/
- Aula de origem: "Como lançar uma EC2 completa na AWS com Terraform, AWS CLI e manualmente" (https://www.youtube.com/watch?v=Ohro_hF7-rU)

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](https://pages.formacaoaws.com.br/blog/novo-console-ec2-lancar-instancia/). O caminho completo é este:

**Fluxo:** Console (passo a passo na tela) → AWS CLI (script de 14 linhas) → Terraform (main.tf, plan e apply) → Mesma EC2 (Security Group, role SSM e User Data)

## Passo 1: preparar 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 policy `AmazonSSMManagedInstanceCore`.

> **Importante:** no laboratório, o usuário que o Terraform usa tem permissão de administrador. Num ambiente real, dê permissões especializadas para o seu cenário.

## Passo 2: lanç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.

## Passo 3: lanç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:

```bash
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:

```bash
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:

```bash
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`:

```bash
#!/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](https://docs.docker.com/compose/install/linux/). Na instância criada pelo script, o `docker compose version` mostrou a v2.23.3.

## Passo 4: lanç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:

```hcl
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):

```hcl
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.

![O main.tf com o provider e o resource aws_instance: AMI, t3.micro, tag bia-terraform, Security Group, user_data por arquivo e role-acesso-ssm (ID do Security Group borrado)](https://pages.formacaoaws.com.br/blog/assets/posts/ec2-com-terraform-cli-e-console/main-tf-resource-aws-instance.webp)

Os comandos, na ordem:

```bash
terraform init
terraform plan
terraform apply
```

- `terraform init` lê o `main.tf`, instala o provider e cria o arquivo `.terraform.lock.hcl`.
- `terraform plan` mostra 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 apply` pede confirmação e cria. Respondi `yes`.

A `bia-terraform` apareceu no console com a mesma role e o mesmo Security Group das outras.

![A lista de instâncias com bia-cli, bia-terraform e bia-manual em execução, todas t3.micro; as duas linhas bia-dev aparecem como terminadas](https://pages.formacaoaws.com.br/blog/assets/posts/ec2-com-terraform-cli-e-console/tres-instancias-console.webp)

## Passo 5: ver 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:

```bash
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.

![O plan do Terraform ao trocar o Security Group: o vpc_security_group_ids muda e o resumo diz 0 to add, 1 to change, 0 to destroy (IDs borrados)](https://pages.formacaoaws.com.br/blog/assets/posts/ec2-com-terraform-cli-e-console/terraform-plan-troca-sg.webp)

Com a CLI, trocar o Security Group de uma instância existente exigiria um novo script.

## Passo 6: conferir 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:

```bash
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](https://pages.formacaoaws.com.br/blog/aws-cloudshell-terminal-seguro/).

Para apagar o que o Terraform criou:

```bash
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 `provider` e o `resource "aws_instance"` no `main.tf`.
- Rode `terraform init`, `terraform plan` e só então `terraform 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.tf` e rode `terraform apply`.
- No fim, `terraform destroy` e 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.
