Todo recurso de cloud que você já criou clicando em um console web (uma VM, um load balancer, um registro DNS) também pode ser descrito em um arquivo de texto e criado com um único comando. O arquivo vive no git, passa por uma pull request e reproduz o mesmo setup em uma segunda conta sem que ninguém precise lembrar quais das nove configurações foram alteradas na mão. É isso que o Terraform faz.

O que o Terraform realmente faz

Terraform é uma ferramenta open-source da HashiCorp que transforma infraestrutura em arquivos de configuração, escritos em HCL (HashiCorp Configuration Language), e os aplica contra a API de um provedor de cloud. Você escreve um bloco resource descrevendo o bucket S3, a instância EC2 ou o registro DNS da Cloudflare que quer, roda terraform apply, e o Terraform calcula as chamadas de API necessárias para que esse recurso exista — depois passa a rastreá-lo, de modo que um segundo apply só muda o que realmente mudou.

O modelo é declarativo, não procedural: você descreve o estado final, não os passos para chegar até ele. Você não escreve “crie um bucket, depois defina sua policy, depois ative o versioning”. Você escreve como o bucket deve ficar, e o Terraform calcula a ordem das operações, incluindo o que precisa existir primeiro quando um recurso depende de outro.

O Terraform sozinho não sabe nada sobre AWS, Azure ou Cloudflare. Esse conhecimento vive nos providers: plugins que traduzem blocos HCL em chamadas contra uma API específica. O Terraform Registry lista milhares deles, oficiais e mantidos pela comunidade, cobrindo desde as três grandes clouds até configurações de repositório do GitHub e monitores do Datadog. O mesmo mecanismo cria um cluster Kubernetes gerenciado (google_container_cluster, aws_eks_cluster) ou um único registro DNS na frente de um CDN. Se um serviço tem uma API, é bem provável que alguém já tenha escrito um provider para ele.

Como funcionam terraform plan e apply

Um diretório de trabalho mínimo tem pelo menos dois arquivos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# terraform.tf — quais providers esta configuração precisa
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  required_version = ">= 1.5"
}

provider "aws" {
  region = "eu-west-1"
}
1
2
3
4
5
6
7
8
9
# main.tf — o recurso propriamente dito
resource "aws_s3_bucket" "reports" {
  bucket = "acme-monthly-reports"

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

Quatro comandos cobrem o ciclo de trabalho do dia a dia:

1
2
3
4
terraform init     # baixa o plugin do provider aws, configura o backend
terraform plan     # mostra o que mudaria, sem tocar em nada
terraform apply    # pede confirmação, depois faz as chamadas de API
terraform destroy  # desmonta tudo que esta configuração gerencia

terraform initrequired_providers e baixa os binários dos plugins correspondentes para dentro de .terraform/. Você roda uma vez por diretório, e de novo sempre que adicionar um provider ou um módulo.

terraform plan é o comando que você vai usar com mais frequência. Ele compara seus arquivos .tf, o state registrado e a infraestrutura real como reportada pela API do provider, e então imprime um diff: recursos a criar (+), alterar (~) ou destruir (-). Nada acontece ainda. É isso que torna seguro apontar o Terraform para produção — você lê o plan antes de qualquer coisa ser tocada.

terraform apply roda o plan de novo, mostra ele outra vez e espera você digitar yes antes de chamar as APIs do provider na ordem de dependência correta. No CI você passa -auto-approve ou, melhor, aplica um arquivo de plan que já revisou: terraform apply tfplan. O mesmo pipeline que roda Construir e Publicar uma Imagem Docker com GitHub Actions a cada merge pode rodar terraform plan em uma pull request e terraform apply assim que ela é mergeada na main.

Para que serve o state file

Depois do primeiro apply, o Terraform escreve terraform.tfstate — um arquivo JSON que mapeia cada recurso da sua configuração ao objeto real que criou, ID incluído. Isso não é burocracia opcional. É assim que o plan sabe o que já existe sem re-escanear toda a sua conta AWS a cada execução, e é assim que o Terraform conecta aws_s3_bucket.reports no seu código ao bucket acme-monthly-reports-a8f3 na realidade.

Daí seguem duas consequências diretas.

O state pode conter segredos. Uma senha de banco de dados definida como argumento de um recurso acaba em texto puro no arquivo de state, porque o Terraform precisa dos atributos completos do recurso para detectar drift. Nunca faça commit de terraform.tfstate no git.

O state precisa ser compartilhado e travado. O padrão é um arquivo local, que funciona sozinho e quebra no momento em que uma segunda pessoa roda apply do próprio laptop: agora há duas fontes de verdade. A solução é um backend remoto, com o state guardado em S3, Azure Blob, GCS ou Terraform Cloud, com um lock que impede que dois apply corram ao mesmo tempo.

1
2
3
4
5
6
7
8
9
terraform {
  backend "s3" {
    bucket       = "acme-terraform-state"
    key          = "reports/terraform.tfstate"
    region       = "eu-west-1"
    use_lockfile = true
    encrypt      = true
  }
}

use_lockfile é o locking nativo do backend S3, disponível de forma estável desde o Terraform 1.11 — ele escreve um arquivo de lock direto no bucket, então nas versões atuais não é mais preciso ter uma tabela DynamoDB dedicada ao locking.

Assim que o time aponta para o mesmo backend, o plan de cada um reflete o último apply de todos os outros.

Como reutilizar uma configuração com variáveis e módulos

Deixar eu-west-1 e um nome de bucket fixos no código funciona para um exemplo de cinco linhas, não para um ambiente real. O Terraform separa a forma da infraestrutura dos valores que mudam por ambiente.

1
2
3
4
5
6
7
8
9
# variables.tf
variable "environment" {
  type    = string
  default = "staging"
}

variable "bucket_name" {
  type = string
}
1
2
3
4
5
6
7
8
# main.tf, referenciando a variável
resource "aws_s3_bucket" "reports" {
  bucket = var.bucket_name

  tags = {
    Environment = var.environment
  }
}
1
terraform apply -var="bucket_name=acme-prod-reports" -var="environment=production"

Ou coloque os valores em um arquivo terraform.tfvars para não redigitar as flags a cada execução. O outputs.tf faz o caminho inverso. Ele expõe um valor calculado pelo Terraform, como o IP público de uma instância ou uma ARN gerada, para que outra ferramenta ou outra configuração Terraform possa consumi-lo:

1
2
3
output "bucket_arn" {
  value = aws_s3_bucket.reports.arn
}

Quando um conjunto de recursos se repete entre projetos (uma VPC padrão, uma stack de web-app padrão), você o encapsula em um módulo: um diretório de arquivos .tf referenciado com um argumento source, que recebe inputs e devolve outputs como uma função. A maioria dos times acaba com um punhado de módulos internos e muitas configurações enxutas que os chamam com variáveis diferentes.

Quando o Terraform é a ferramenta errada

O Terraform provisiona recursos; ele não configura o que roda neles. Ele vai criar uma instância EC2, mas instalar pacotes, gerenciar usuários e manter a configuração sincronizada nessa instância é um trabalho diferente — tradicionalmente do Ansible, de um script cloud-init, ou de uma imagem de container que a instância baixa e executa. Passar a configuração da aplicação pelo Terraform é a direção errada. Criar a VPC pelo Ansible é a outra direção errada. A maioria dos setups reais usa os dois, e o Terraform repassa o IP da instância direto para o inventory do Ansible via um output.

Para um bucket que você vai apagar em uma hora, ou uma VM de teste descartável, o arquivo de state e o ciclo escrever-plan-apply custam mais do que economizam. A CLI da AWS ou o console são mais rápidos para algo que você não pretende manter nem reproduzir.

Terraform, OpenTofu e Pulumi

O Terraform não é mais a única ferramenta que lê HCL. A HashiCorp o moveu da licença open-source MPL para a Business Source License em 2023, e a Linux Foundation agora mantém o OpenTofu, um fork que manteve a licença MPL e ficou compatível com a sintaxe HCL e o formato de state do Terraform. Trocar depois é uma mudança de configuração, não uma reescrita.

O Pulumi vai na direção oposta: em vez de HCL você escreve a infraestrutura em TypeScript, Python ou Go. Isso importa se o seu time quer controle de fluxo de verdade e testes unitários sobre a lógica de infraestrutura em vez das expressões mais limitadas do HCL.

Como testar o Terraform sem uma conta cloud

A rede de segurança do Terraform é o plan, e você pode exercitar o ciclo inteiro de graça antes de apontá-lo para algo cobrável. Os providers random e local não pedem nenhuma credencial de cloud.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
terraform {
  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
  }
}

resource "random_pet" "name" {
  length = 2
}

output "generated_name" {
  value = random_pet.name.id
}

Você roda terraform init e terraform apply em um diretório vazio e ganha um state file real e um recurso gerenciado real, sem nada a pagar. Faça isso uma vez, leia a saída do plan linha por linha até os símbolos +, ~ e - fazerem sentido para você, e então aponte os mesmos quatro comandos para um provider que cobra por hora.

Artigos relacionados