Pular para o conteúdo
CloudIniciante

Terraform: primeiros passos para criar infra como código

Aprenda Terraform do zero: init, plan, apply e destroy, state remoto, variáveis e outputs, com um exemplo de servidor na AWS descrito em código.

Por Equipe IAUAI Estudos · 8 de julho de 2026 · 9 min de leitura

Nesta página

Ao final deste artigo você vai conseguir descrever uma pequena infraestrutura na AWS em código, ver o que vai mudar antes de aplicar, guardar o state de forma segura e derrubar tudo quando acabar. Os conceitos valem para qualquer nuvem, porque mudam só os providers.

Por que infra como código

Quando você cria servidores clicando no console, a infraestrutura vira memória de quem clicou. Staging e produção divergem sem ninguém notar, a documentação envelhece e um recurso apagado por engano deixa a equipe sem saber o que existia.

Com Terraform a infraestrutura fica em arquivos no Git. Mudanças passam por pull request, terraform plan mostra o efeito antes de qualquer alteração, e reproduzir um ambiente é rodar o mesmo código com outras variáveis. Reverter costuma ser um git revert seguido de terraform apply, com a ressalva de que nem toda mudança é reversível (apagar um banco de dados, por exemplo, não volta com o revert).

Uma observação sobre o ecossistema. O Terraform é mantido pela HashiCorp (hoje parte da IBM) sob licença BUSL, e existe o OpenTofu, um fork mantido pela Linux Foundation com licença aberta. Os comandos e a linguagem HCL são praticamente os mesmos, então tudo aqui funciona trocando terraform por tofu.

Como o Terraform pensa

Terraform é declarativo. Você descreve o estado desejado e ele calcula o que criar, alterar ou remover para chegar lá. Um exemplo enxuto, com rede e um servidor:

# main.tf
terraform {
  required_version = ">= 1.10"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = var.region
}

# Busca a AMI mais recente do Ubuntu 24.04 em vez de fixar um ID
data "aws_ssm_parameter" "ubuntu" {
  name = "/aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id"
}

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags                 = { Name = "demo-vpc" }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags   = { Name = "demo-igw" }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true
  tags                    = { Name = "demo-public" }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

resource "aws_security_group" "app" {
  name_prefix = "demo-app-"
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "SSH só do seu IP"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.allowed_ssh_cidr]
  }

  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "app" {
  ami                    = data.aws_ssm_parameter.ubuntu.value
  instance_type          = var.instance_type
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.app.id]

  user_data = <<-EOF
    #!/bin/bash
    apt-get update
    apt-get install -y docker.io
    systemctl enable --now docker
  EOF

  root_block_device {
    volume_size = 30
    volume_type = "gp3"
  }

  tags = { Name = "demo-app" }
}

resource "aws_eip" "app" {
  instance   = aws_instance.app.id
  domain     = "vpc"
  depends_on = [aws_internet_gateway.main]
}

São 8 recursos: VPC, internet gateway, subnet, tabela de rotas, associação da rota, security group, instância e IP elástico. Repare que nenhum ID aparece fixo. Cada recurso referencia o atributo do outro (aws_vpc.main.id), e é isso que permite ao Terraform montar o grafo de dependências e criar tudo na ordem certa.

Em produção você não deixaria a porta 22 aberta, nem mesmo para um IP. O caminho mais seguro é o AWS Systems Manager Session Manager, que dispensa SSH. Para um primeiro estudo, restringir ao seu IP com /32 é aceitável.

As variáveis usadas acima ficam em outro arquivo:

# variables.tf
variable "region" {
  description = "Região AWS"
  type        = string
  default     = "sa-east-1"
}

variable "instance_type" {
  description = "Tipo da instância EC2"
  type        = string
  default     = "t3.medium"
}

variable "allowed_ssh_cidr" {
  description = "CIDR com acesso SSH, por exemplo 203.0.113.10/32"
  type        = string
}

variable "environment" {
  description = "Ambiente"
  type        = string
  default     = "dev"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment deve ser dev, staging ou prod."
  }
}

O allowed_ssh_cidr não tem default, então o Terraform vai exigir o valor. Você passa por -var, por um arquivo terraform.tfvars ou pela variável de ambiente TF_VAR_allowed_ssh_cidr.

O ciclo: init, plan, apply, destroy

init

terraform init

Baixa os providers, configura o backend e cria a pasta .terraform/. Gera também o .terraform.lock.hcl, que trava as versões dos providers. Esse arquivo deve ir para o Git, para que todos usem as mesmas versões.

plan

terraform plan -out=tfplan

O Terraform compara o seu código com o state e com a infraestrutura real e mostra o que vai fazer. Cada linha traz um símbolo: + cria, ~ altera no lugar, - destrói e -/+ recria. O resumo final diz algo como Plan: 8 to add, 0 to change, 0 to destroy. Leia com atenção, principalmente os -/+, porque recriar um banco de dados ou um disco pode significar perda de dados.

apply

terraform apply tfplan

Executa exatamente o plano salvo. Sem o arquivo de plano, terraform apply gera um plano novo e pede confirmação. Em pipelines de CI/CD o padrão é salvar o plano na etapa de revisão e aplicá-lo na etapa de deploy.

destroy

terraform destroy

Remove tudo o que o state controla, depois de pedir confirmação. É ótimo para ambientes de estudo, para não pagar por recursos esquecidos, e perigoso em produção. Em recursos críticos, use lifecycle { prevent_destroy = true } como trava de segurança.

State: o arquivo que liga código à realidade

O Terraform guarda no terraform.tfstate quais recursos reais correspondem a cada bloco do código. Sem ele, não saberia o que já criou.

Dois cuidados essenciais. O state pode conter dados sensíveis em texto claro, como senhas de banco geradas pelo próprio Terraform, então nunca vá para o Git. E, em equipe, duas pessoas aplicando ao mesmo tempo corrompem o state, por isso se usa um backend remoto com lock.

printf 'terraform.tfstate*\n.terraform/\n*.tfplan\n' >> .gitignore

Um backend S3 com lock nativo fica assim:

# backend.tf
terraform {
  backend "s3" {
    bucket       = "meu-time-tf-state"
    key          = "prod/terraform.tfstate"
    region       = "sa-east-1"
    encrypt      = true
    use_lockfile = true
  }
}

O use_lockfile = true, disponível a partir do Terraform 1.10, grava o lock como um arquivo no próprio bucket. Ele substitui a tabela DynamoDB que tutoriais mais antigos recomendam, e que hoje está marcada como obsoleta. Se você usa uma versão anterior, confira a documentação do backend para a sua versão.

O bucket precisa existir antes do init, com versionamento e criptografia ligados:

aws s3api create-bucket \
  --bucket meu-time-tf-state \
  --region sa-east-1 \
  --create-bucket-configuration LocationConstraint=sa-east-1

aws s3api put-bucket-versioning \
  --bucket meu-time-tf-state \
  --versioning-configuration Status=Enabled

Os nomes de bucket são globais na AWS, então troque meu-time-tf-state por um nome seu.

Outputs: pegar valores depois do apply

# outputs.tf
output "instance_id" {
  description = "ID da instância EC2"
  value       = aws_instance.app.id
}

output "public_ip" {
  description = "IP público do servidor"
  value       = aws_eip.app.public_ip
}

Depois do apply, terraform output lista todos e terraform output -raw public_ip devolve só o valor, útil em scripts. Para valores secretos, marque o output com sensitive = true.

Armadilhas comuns

Drift. Alguém altera um recurso pelo console e o código passa a mentir. Para ver a divergência sem alterar nada, use terraform plan -refresh-only. O comando terraform refresh isolado ainda existe, mas está obsoleto, porque atualiza o state sem mostrar o que mudou antes. Depois de detectar o drift, decida se o código ou a realidade é o certo.

Segredos no código. Não escreva senhas em .tf nem em .tfvars versionados. Use variáveis de ambiente, um cofre (AWS Secrets Manager, Vault) ou deixe o serviço gerenciar a credencial.

Aplicar sem ler o plan. O plano existe para ser lido. Em time, comente-o no pull request antes do merge.

Versões soltas. Sem required_version e sem a restrição de versão do provider, atualizações podem mudar o comportamento. Fixe com ~> e versione o lock file.

Quando não usar Terraform

Para uma única máquina que você nunca vai recriar, o custo de manter código e state pode não compensar. E o Terraform é ruim para configurar o interior do servidor (pacotes, arquivos de configuração), onde Ansible ou imagens prontas funcionam melhor. O uso típico é provisionar a infraestrutura e entregar a aplicação por imagem de container.

Próximos passos

Com a infraestrutura em código, o passo seguinte é decidir o que roda nela. Veja Serverless ou containers para cargas de IA e, se o destino for um cluster, Kubernetes para quem vem do Docker Compose. Para controlar o que a infraestrutura custa, leia FinOps para IA e use tags em todos os recursos.

Quer aplicar isso na sua empresa? Marque uma conversa de 45 minutos em https://iauaicloud.com.br/consultoria

Aprenda jogando

Datacenter

Percorra o corredor de racks e resolva falhas antes que o uptime despenque.

Jogar Datacenter

Teste seu conhecimento

Teste o que você aprendeu sobre Terraform

Pergunta 1 de 5

Qual é a ordem do ciclo básico de trabalho com Terraform?

Um conteúdo prático por quinzena

Deixe o seu e-mail para receber um conteúdo prático de IA e cloud a cada quinzena. Sem spam, e você pede a exclusão quando quiser.

Continue lendo