Ir para o conteúdo principal
  1. Posts/

Terraform: Guia Completo — Sintaxe, Providers, Ambientes, Estado e Workflows

·2074 palavras·10 minutos
Abraao Rocha de Paula
Autor
Abraao Rocha de Paula
Comparo ferramentas DevOps pra te auxiliar a tomar a melhor decisão.
Tabela de conteúdos
IaC Tools - Este artigo faz parte de uma série de artigos.
Parte 2: Esse Artigo

Resumo para os apressados
#

TópicoResposta curta
SintaxeHCL declarativa: resource, variable, output, module
ProvidersPlugins que traduzem HCL para chamadas de API (AWS, GCP, Azure, etc.)
MódulosReuso de código — próprios ou do Terraform Registry
tfvarsArquivos .tfvars separam valores de ambiente da lógica de infra
AmbientesUse workspaces (simples) ou file structure (recomendado para times)
Trunk-basedCommits frequentes na main, feature flags para controlar ambientes
Branch-basedBranches por ambiente (staging, prod), risco de merge hell
Estado.tfstate + remote backend (S3+GCS+Azure) com locking
Driftterraform plan detecta; terraform apply -refresh-only reconcilia

1. Sintaxe do Terraform em 5 minutos
#

Terraform usa HCL (HashiCorp Configuration Language), uma linguagem declarativa onde você descreve o estado desejado da infraestrutura, não os passos para chegar lá.

Os quatro blocos que você vai usar todo santo dia:

# 1. Provider — com quem o Terraform vai falar
provider "aws" {
  region = "us-east-1"
}

# 2. Variable — valores parametrizáveis
variable "instance_type" {
  description = "Tipo da instância EC2"
  type        = string
  default     = "t2.micro"
}

# 3. Resource — o que você quer criar
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.instance_type

  tags = {
    Name = "servidor-web"
  }
}

# 4. Output — valores exportados após apply
output "instance_ip" {
  value = aws_instance.web.public_ip
}

Algumas expressões que salvam tempo:

# Interpolação de variáveis
name = "app-${var.environment}"

# if ternário (sim, HCL tem)
instance_type = var.env == "prod" ? "t2.large" : "t2.micro"

# Loop com for_each
resource "aws_s3_bucket" "buckets" {
  for_each = toset(["logs", "backups", "media"])
  bucket   = "${each.key}-${var.project}"
}

E os comandos que você vai digitar toda hora:

terraform init       # Baixa providers e módulos
terraform plan       # Preview do que será alterado (sem aplicar)
terraform apply      # Aplica as mudanças
terraform fmt        # Formata código HCL
terraform validate   # Valida sintaxe

O fluxo básico é init → plan → apply. Nunca dê apply sem antes ver o plan. Já vi gente derrubar produção porque confiou no -auto-approve. Não seja essa pessoa.


2. Providers: a ponte entre código e cloud
#

O Terraform por si só não cria nada. Ele delega a execução para providers,plugins que traduzem HCL em chamadas de API reais.

Ou seja, quando você declara provider "aws", o Terraform baixa o binário do provider AWS e usa ele pra autenticar e chamar a API. Cada resource pertence a um provider (aws_instance → provider aws).

Os providers que mais aparecem no dia a dia:

ProviderUse case
awsEC2, S3, RDS, Lambda, VPC, IAM
azurermAzure VMs, Storage, AKS, CosmosDB
googleGCP Compute, GKE, Cloud Storage
kubernetesDeployments, Services, ConfigMaps no K8s
helmCharts Helm gerenciados como código
cloudflareDNS, Workers, Pages
githubRepositórios, teams, branch protection
datadogDashboards, monitores, alerts

O Terraform Registry tem mais de 3.000 providers. Dificilmente você vai precisar de algo que não exista lá.
#

3. Módulos: pare de copiar e colar
#

Módulos são como funções para infraestrutura. Em vez de repetir 50 linhas de VPC em todo projeto, você chama um módulo.

Módulos do Registry
#

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"

  name = "meu-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-1a", "us-east-1b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
}

Três cuidados que eu tomaria com módulos de terceiros:

  • Sempre fixe a versão (version = "5.0.0"), nunca use latest. Breaking change em módulo popular é mais comum do que parece.
  • Leia o código fonte do módulo antes de usar em produção. Popular não significa bem escrito.
  • Se o módulo for crítico, faça fork. Melhor depender do seu próprio repositório do que acordar domingo com o módulo deprecado.

Módulos próprios
#

Convenção que funciona bem:

modules/
├── networking/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
├── compute/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
└── database/
    ├── main.tf
    ├── variables.tf
    └── outputs.tf
module "database" {
  source = "./modules/database"

  engine         = "postgres"
  instance_class = "db.t3.medium"
  environment    = var.environment
}

A separação main.tf (recursos), variables.tf (inputs) e outputs.tf (retornos) não é obrigatória, mas times com mais de duas pessoas agradecem.


4. tfvars: código é lógica, tfvars é configuração
#

.tfvars são arquivos que atribuem valores a variáveis sem mexer no código. Isso separa o que é lógica de infra do que é valor de ambiente.

# variables.tf — declara as variáveis e defaults
variable "environment" {
  type    = string
  default = "dev"
}

variable "instance_count" {
  type    = number
  default = 1
}
# staging.tfvars
environment    = "staging"
instance_count = 2
# production.tfvars
environment    = "prod"
instance_count = 5

Uso no terminal:

terraform plan  -var-file="staging.tfvars"
terraform apply -var-file="staging.tfvars"
terraform plan  -var-file="production.tfvars"
terraform apply -var-file="production.tfvars"

Nunca commite secrets em .tfvars. Use sensitive = true na variável e passe valores via variável de ambiente (TF_VAR_db_password) ou secrets manager. Crie um terraform.tfvars.example com valores dummy pro time se orientar. E coloque *.tfvars (exceto o .example) no .gitignore.


5. Ambientes no Terraform: qual estratégia usar?
#

Gerenciar dev, staging e produção com Terraform tem três abordagens principais.

Workspaces (a mais simples)
#

terraform workspace new staging
terraform workspace new production

terraform workspace select staging
terraform apply -var-file="staging.tfvars"

Um backend, múltiplos state files. Rápido e nativo, mas todos os ambientes compartilham o mesmo backend e as mesmas credenciais. Um terraform destroy no workspace errado e você deletou produção. Já vi acontecer.

File Structure (a que eu recomendo pra times)
#

terraform/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── terraform.tfvars
│   │   └── backend.tf
│   ├── staging/
│   │   ├── main.tf
│   │   ├── terraform.tfvars
│   │   └── backend.tf
│   └── prod/
│       ├── main.tf
│       ├── terraform.tfvars
│       └── backend.tf
└── modules/
    ├── networking/
    ├── compute/
    └── database/

Cada ambiente tem seu próprio diretório, seu próprio state e suas próprias credenciais. Isolamento de verdade. O preço é um pouco de duplicação de código, mas pra times de 2 a 10 pessoas vale muito a pena.

Terragrunt (pra times grandes)
#

Terragrunt é um wrapper que elimina a duplicação mantendo o isolamento:

# terragrunt.hcl
terraform {
  source = "git::git@github.com:org/modules.git//networking?ref=v1.0.0"
}

inputs = {
  environment = "staging"
  cidr_block  = "10.1.0.0/16"
}

Resumo rápido:

EstratégiaTamanho do timeRiscoComplexidade
Workspaces1-2 pessoasMédioBaixa
File Structure2-10 pessoasBaixoMédia
Terragrunt10+ pessoasBaixoAlta

Minha sugestão: comece com file structure. Se o time crescer a ponto da duplicação doer, migre pra Terragrunt.


6. Trunk-Based Development com Terraform
#

Trunk-based development (TBD) é commitar direto na main várias vezes ao dia, usando feature flags e proteção de branches pra controlar deploys.

main ──●──●──●──●──●──●──●── (commits frequentes)
       │       │
       ▼       ▼
    [plan]  [plan]
       │       │
       ▼       ▼
    staging  production

Na prática:

# 1. Altera o tfvars do staging
vim environments/staging/terraform.tfvars

# 2. Commit direto na main
git add . && git commit -m "staging: sobe instance_count pra 3"
git push origin main

# 3. O CI detecta o push, roda plan no staging
# 4. Plan limpo → CI dá apply no staging
# 5. Validou em staging → promove pra prod (mesmo código, outro tfvars)

Uma pipeline de exemplo com GitHub Actions:

name: Terraform CI
on:
  push:
    branches: [main]

jobs:
  terraform:
    strategy:
      matrix:
        environment: [staging, production]
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3

      - name: Terraform Plan
        working-directory: environments/${{ matrix.environment }}
        run: terraform plan -input=false

      - name: Terraform Apply (prod só com aprovação manual)
        if: matrix.environment == 'staging' || github.event_name == 'workflow_dispatch'
        working-directory: environments/${{ matrix.environment }}
        run: terraform apply -auto-approve -input=false

As vantagens do TBD são claras: sem branches long-lived, sem merge hell, a main sempre reflete o que está (ou estará) em produção, e toda mudança pequena é revisada e deployada rápido.

Só toma cuidado com três coisas:

  • feature flags: se a feature está incompleta, controle com variável condicional, não com branch separada.
  • Proteção de produção: exija aprovação manual antes do apply em prod.
  • testes no CI: terraform validate e terraform fmt -check(No mínimo, o correto é ter SAST, secret scan, linters mais avançados, etc..) em TODO commit.

7. Branch-Based Development: o modelo tradicional
#

No modelo branch-based, cada ambiente tem uma branch de longa duração:

main ──────●────────────●────────────●── (produção)
            \\          \\          \\
staging ────●───●──●────●────────────●── (homologação)
                  \\
dev ──────────────●──●──●──●──●──●──●── (desenvolvimento)

Fluxo típico:

git checkout dev
# ... altera recursos ...
git commit -m "adiciona cache redis"

git checkout staging
git merge dev
terraform plan && terraform apply

git checkout main
git merge staging
terraform plan && terraform apply

Parece organizado, mas na prática traz uns problemas sérios:

ProblemaPor que dói
Merge hellAs branches dev e staging divergem e resolver conflito em HCL não é como resolver em Python.
State vs BranchO state file não sabe que branches existem. Se dev e staging apontam pro mesmo backend, o caos é certo.
Promoção manualDepende de alguém lembrar de mergear. Automação frágil e propensa a erro humano.
Ilusão de isolamentoA branch tá separada mas o state não. Sem file structure, branches são cosméticas.

Branch-based ainda faz sentido em times pequenos (1-3 pessoas) sem CI/CD maduro ou em monorepos onde Terraform é só uma parte. Mas a real é que a maioria dos times está migrando pra trunk-based com file structure, isolamento real e deploy contínuo.


8. Estado e Drift: onde a mágica acontece (e os problemas também)
#

O state file
#

O .tfstate é um JSON que mapeia recursos declarados no .tf para recursos reais na cloud. Sem ele, o Terraform não sabe o que destruir ou alterar.

main.tf ──► terraform apply ──► aws_instance.web (i-0a1b2c3d4e5f)
                                   └── terraform.tfstate
                                       resource "aws_instance" "web" {
                                         id = "i-0a1b2c3d4e5f"
                                         ...
                                       }

Remote Backend (obrigatório se tem mais de uma pessoa)
#

State nunca deve ser local. Use backend remoto com locking:

terraform {
  backend "s3" {
    bucket         = "meu-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
  }
}
BackendLockingCusto
S3Sim (nativo)Baixo
GCSSim (nativo)Baixo
Azure StorageSim (lease)Baixo
HCP TerraformSim (nativo)Free tier

Regra de ouro: duas pessoas nunca podem dar apply ao mesmo tempo no mesmo state. O locking impede isso, mas só funciona se você configurar direito.

Drift: quando a realidade sai do código
#

Drift acontece quando alguém muda um recurso fora do Terraform (console, CLI, outro time). O state fica desatualizado e você só descobre quando algo quebra.

terraform plan -detailed-exitcode
# exit 0 = tudo em sync
# exit 1 = erro
# exit 2 = drift detectado

Pra corrigir:

Reconciliar (importar a mudança):

terraform apply -refresh-only   # Atualiza o state sem mexer na infra
terraform plan                   # Confere se bateu — se não, ajusta o .tf e roda apply

Sobrescrever (forçar o código):

terraform apply   # Altera a infra de volta pro que está no código

Pra prevenir, o combo que funciona: bloqueie acesso de escrita nos consoles (IAM read-only pra humanos), toda mudança passa pelo Terraform, e rode plan no CI periodicamente, um cron job diário que alerta se detectar drift. Simples e eficaz.


9. Estrutura que escala
#

Juntando tudo que falamos, essa estrutura funciona de 1 a 50 pessoas:

terraform/
├── environments/
│   ├── dev/
│   │   ├── backend.tf
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   │   ├── backend.tf
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── backend.tf
│       ├── main.tf
│       ├── variables.tf
│       ├── outputs.tf
│       └── terraform.tfvars
├── modules/
│   ├── networking/
│   ├── compute/
│   ├── database/
│   └── monitoring/
├── .github/
│   └── workflows/
│       ├── terraform-plan.yml
│       ├── terraform-apply.yml
│       └── drift-detection.yml
├── .gitignore
├── .terraform-version
└── README.md

Para fechar
#

Terraform é enorme, mas a essência tá em seis conceitos:

  1. HCL descreve: você declara o que quer, o Terraform resolve como chegar lá
  2. Providers executam: o motor que transforma código em recurso real
  3. Módulos organizam: reuso de código, seus ou do Registry
  4. tfvars separam: código é lógica, tfvars é configuração de ambiente
  5. Ambientes isolam: file structure > workspaces pra times sérios
  6. State é a verdade: proteja com backend remoto + locking, monitore drift

Se tem uma dica que eu daria pra quem tá começando: comece pequeno e itere. Cria uma instância EC2 simples, depois adiciona um módulo, depois separa ambientes. Não tenta montar a estrutura perfeita no dia 1, ela vai evoluir conforme o time cresce.


Este artigo faz parte da série IaC Tools. O anterior foi Terraform vs Pulumi: Qual Escolher em 2026?.

Dúvidas ou sugestões? Me encontra no LinkedIn.

IaC Tools - Este artigo faz parte de uma série de artigos.
Parte 2: Esse Artigo