Study guide

Terraform, the 80/20.

The part of Terraform you will use every day, explained step by step and then applied to AWS: remote state, VPC, EC2, IAM and S3.

O que é e o 20% que importaWhat it is and the 20% that matters

Terraform é uma ferramenta de infraestrutura como código (IaC): em vez de clicar no console da nuvem, você descreve em arquivos de texto o que quer que exista (redes, servidores, buckets, permissões) e o Terraform descobre o que precisa criar, alterar ou apagar para a realidade ficar igual à descrição. A linguagem é a HCL, e o trabalho é feito por providers (plugins) que falam com a API de cada serviço: AWS, Azure, Google Cloud, Cloudflare, GitHub, Kubernetes e milhares de outros.Terraform is an infrastructure as code (IaC) tool: instead of clicking in the cloud console, you describe in text files what should exist (networks, servers, buckets, permissions) and Terraform works out what to create, change or delete so reality matches the description. The language is HCL, and the work is done by providers (plugins) that talk to each service’s API: AWS, Azure, Google Cloud, Cloudflare, GitHub, Kubernetes and thousands more.

A ideia central é ser declarativo: você diz o destino (“quero 2 subnets e 1 bucket”), não os passos. É como a planta de uma casa: o Terraform compara a planta com a obra e só executa a diferença. Rodar de novo sem mudar nada não faz nada (idempotência).The core idea is being declarative: you state the destination (“I want 2 subnets and 1 bucket”), not the steps. It is like a house blueprint: Terraform compares the blueprint with the building and only carries out the difference. Running it again with no changes does nothing (idempotence).

CódigoCode *.tf init plan apply EstadoState terraform.tfstate NuvemCloud API do providerprovider API apply cria/alteraapply creates/edits init baixa providers e módulos · plan mostra a diferença sem alterar nada.init downloads providers and modules · plan shows the difference without changing anything. apply executa as ações e grava o novo estado.apply carries out the actions and records the new state.
O ciclo do Terraform. O plan é o seu ponto de segurança: leia-o antes de aplicar.The Terraform cycle. plan is your safety checkpoint: read it before applying.

Terraform, OpenTofu, CloudFormation e PulumiTerraform, OpenTofu, CloudFormation and Pulumi

FerramentaToolLinguagemLanguageAlcanceScopeQuando escolherWhen to choose
Terraform (HashiCorp)HCLqualquer nuvem e serviço com providerany cloud and service with a providerpadrão de mercado, enorme ecossistema; licença BSL desde 2023market standard, huge ecosystem; BSL license since 2023
OpenTofuHCLigual ao Terraform (fork da Linux Foundation, licença MPL)same as Terraform (Linux Foundation fork, MPL license)você precisa de licença aberta; os comandos são quase iguais (tofu)you need an open license; commands are nearly identical (tofu)
CloudFormationYAML/JSONsó AWSAWS onlytudo na AWS e você quer o estado gerenciado pela própria AWSall-in on AWS and you want AWS-managed state
PulumiTypeScript, Python, Go…multi-nuvemmulti-cloudo time prefere linguagem de programação geral a uma DSLthe team prefers a general-purpose language to a DSL

Tudo que você aprende aqui vale para o OpenTofu também; só troque terraform por tofu no terminal.Everything you learn here also applies to OpenTofu; just swap terraform for tofu in the terminal.

O 20% que rende 80%The 20% that pays 80%

##ConceitoConceptPor que importaWhy it matters
1Ciclo init → plan → applyThe init → plan → apply cycleé 90% do uso diárioit is 90% of daily use
2Blocos resource e dataresource and data blockscriar vs. apenas ler o que já existecreate vs. just reading what already exists
3Referências entre recursosReferences between resourcesgeram o grafo de dependências automaticamentebuild the dependency graph automatically
4variable, locals, outputvariable, locals, outputentrada, valores internos e saída do móduloinput, internal values and module output
5Estado (state) e backend remotoState and remote backendé a “memória” do Terraform; perder o estado é o maior riscoit is Terraform’s “memory”; losing state is the biggest risk
6count, for_each e expressões forcount, for_each and for expressionsevitam copiar e colar blocosavoid copy-pasting blocks
7MódulosModulesreuso e organizaçãoreuse and organisation
8lifecycle e moved/importlifecycle and moved/importrefatorar e adotar recursos existentes sem destruir nadarefactor and adopt existing resources without destroying anything
9Trava de versões e .terraform.lock.hclVersion pinning and .terraform.lock.hclbuilds reprodutíveisreproducible builds
10CI com fmt, validate, planCI with fmt, validate, planninguém aplica da máquina pessoal em produçãonobody applies from a personal machine in production

Instalar e rodar o primeiro projetoInstall and run your first project

Vamos rodar o ciclo completo sem nuvem nenhuma, usando o provider local, que só cria arquivos. Assim você vê o que o Terraform faz antes de gastar um centavo.Let us run the full cycle with no cloud at all, using the local provider, which only creates files. That way you see what Terraform does before spending a cent.

InstalarInstall

# Linux (Debian/Ubuntu): repositório oficial da HashiCorp
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

# macOS
brew tap hashicorp/tap && brew install hashicorp/tap/terraform

terraform version
terraform -install-autocomplete    # completa comandos com Tab (reabra o terminal)
Para trocar de versão por projeto, use um gerenciador como tfenv ou mise. As instruções oficiais mais recentes estão na página de instalação da HashiCorp.To switch versions per project, use a manager such as tfenv or mise. The latest official instructions are on HashiCorp’s install page.

Primeiro projetoFirst project

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

variable "name" {
  type    = string
  default = "world"
}

resource "local_file" "greeting" {
  filename = "${path.module}/hello.txt"
  content  = "Hello, ${var.name}!\n"
}

output "file_path" {
  value = local_file.greeting.filename
}
terraform init       # baixa o provider e cria .terraform/ e .terraform.lock.hcl
terraform fmt        # formata o código no padrão oficial
terraform validate   # confere sintaxe e referências
terraform plan       # mostra o que vai acontecer
terraform apply      # pede "yes" e executa
cat hello.txt        # Hello, world!
terraform apply -var name=Ana   # muda uma variável e aplica de novo
terraform destroy    # apaga tudo o que ele criou

O plan usa quatro símbolos. Aprenda a ler:The plan uses four symbols. Learn to read them:

SímboloSymbolSignificaMeansExemploExample
+será criadowill be created+ resource "local_file" "greeting"+ resource "local_file" "greeting"
~será alterado no lugarwill be updated in placemudar uma tagchanging a tag
-/+será destruído e recriado (atenção!)will be destroyed and recreated (careful!)mudar o conteúdo de um local_file; mudar a AMI de uma EC2changing a local_file content; changing an EC2 AMI
-será destruídowill be destroyedremoveu o bloco do códigoyou removed the block from the code
Procure -/+ e - no plan antes de confirmar. Em recurso com dados (banco, disco, bucket), “recriar” significa perder os dados. O resumo no fim (Plan: 1 to add, 0 to change, 1 to destroy) é a primeira coisa a ler.Look for -/+ and - in the plan before confirming. On resources that hold data (database, disk, bucket), “recreate” means losing the data. The summary at the end (Plan: 1 to add, 0 to change, 1 to destroy) is the first thing to read.

O que fica na pastaWhat ends up in the folder

Arquivo/pastaFile/folderÉ o quêWhat it isGit?Git?
*.tfseu códigoyour codesimyes
.terraform.lock.hclversões exatas dos providers (hashes)exact provider versions (hashes)simyes
.terraform/providers e módulos baixadosdownloaded providers and modulesnãono
terraform.tfstateo estado local (pode ter segredos)the local state (may hold secrets)nuncanever
*.tfvarsvalores das variáveis (podem ter segredos)variable values (may hold secrets)só os sem segredoonly secret-free ones
# .gitignore para Terraform
.terraform/
*.tfstate
*.tfstate.*
crash.log
*.tfplan
override.tf
override.tf.json
# valores com segredos: use um nome próprio e ignore-o aqui
*.auto.tfvars

A linguagem HCLThe HCL language

HCL (HashiCorp Configuration Language) tem só duas peças: blocos e argumentos. Um bloco tem um tipo, zero ou mais rótulos e um corpo entre chaves; o corpo tem argumentos (nome = valor) e outros blocos.HCL (HashiCorp Configuration Language) has only two pieces: blocks and arguments. A block has a type, zero or more labels and a body in braces; the body holds arguments (name = value) and other blocks.

resource "aws_s3_bucket" "logs" {   # tipo do bloco, tipo do recurso, nome local
  bucket = "acme-logs-prod"         # argumento
  tags = {                          # argumento cujo valor é um mapa
    Team = "platform"
  }
  lifecycle {                       # bloco aninhado
    prevent_destroy = true
  }
}
BlocoBlockPara quêWhat forExemplo de usoUsage example
terraformversão do Terraform, providers e backendTerraform version, providers and backendrequired_version
providerconfigura um provider (região, credenciais)configures a provider (region, credentials)provider "aws" { region = … }
resourcealgo que o Terraform cria e gerenciasomething Terraform creates and managesaws_instance.web
datalê algo que já existe, sem gerenciarreads something that already exists, without managing itdata.aws_caller_identity.current
variableentrada configurávelconfigurable inputvar.region
localsvalores calculados reutilizáveisreusable computed valueslocal.tags
outputvalor exposto no fim ou para outro módulovalue exposed at the end or to another moduleterraform output ip
moduleusa um conjunto de código empacotadouses a packaged set of codemodule.vpc.vpc_id
moved · import · removedrefatorar e adotar/soltar recursosrefactor and adopt/release resourcesveja “Estado”see “State”
checkverificações que avisam sem bloquearchecks that warn without blocking“o site responde 200?”“does the site answer 200?”

Tipos, referências e expressõesTypes, references and expressions

variable "region"   { type = string }                     # "sa-east-1"
variable "count_n"  { type = number }                     # 3
variable "enabled"  { type = bool }                       # true
variable "azs"      { type = list(string) }               # ["a", "b"] (ordenada)
variable "cidrs"    { type = set(string) }                # sem ordem e sem repetição
variable "tags"     { type = map(string) }                # { Team = "x" }
variable "disk"     {
  type = object({ size = number, encrypted = bool })      # estrutura fixa
}

locals {
  env      = "dev"
  name     = "${var.project}-${local.env}"                 # interpolação
  size     = var.enabled ? 100 : 20                        # condicional
  upper    = [for s in var.azs : upper(s)]                 # lista -> lista
  by_az    = { for s in var.azs : s => "${var.region}${s}" }   # lista -> mapa
  ids      = aws_instance.web[*].id                        # splat: todos os ids
  merged   = merge(var.tags, { Env = local.env })          # junta mapas
  safe     = try(var.disk.size, 20)                        # valor padrão se falhar
}

Referências seguem o padrão tipo.nome.atributo: aws_vpc.main.id, var.region, local.tags, module.vpc.public_subnets, data.aws_region.current.name. Funções úteis para decorar: merge, lookup, join, format, toset, try, coalesce, cidrsubnet, jsonencode, file. Teste qualquer expressão no terraform console.References follow the type.name.attribute pattern: aws_vpc.main.id, var.region, local.tags, module.vpc.public_subnets, data.aws_region.current.name. Useful functions to memorise: merge, lookup, join, format, toset, try, coalesce, cidrsubnet, jsonencode, file. Test any expression in terraform console.

terraform console
> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"
> upper("abc")
"ABC"
A ordem dos blocos no arquivo não importa. O Terraform lê todos os *.tf da pasta como um só e monta o grafo pelas referências. Por convenção: main.tf, variables.tf, outputs.tf, versions.tf (ou providers.tf).The order of blocks in a file does not matter. Terraform reads every *.tf in the folder as one and builds the graph from references. By convention: main.tf, variables.tf, outputs.tf, versions.tf (or providers.tf).

O fluxo e os comandosThe workflow and commands

ComandoCommandO que fazWhat it does
terraform initbaixa providers/módulos e prepara o backend (rode após clonar ou mudar versões)downloads providers/modules and sets up the backend (run after cloning or changing versions)
terraform fmt -recursiveformata todos os .tfformats every .tf
terraform validateconfere sintaxe e coerência, sem tocar na nuvemchecks syntax and consistency, without touching the cloud
terraform plan -out=tfplancalcula a diferença e salva o planocomputes the difference and saves the plan
terraform apply tfplanaplica exatamente o plano salvo (sem perguntar)applies exactly the saved plan (no prompt)
terraform applyplaneja e pergunta yesplans and asks for yes
terraform destroydestrói tudo que o estado conhece (equivale a apply -destroy)destroys everything the state knows (same as apply -destroy)
terraform output [-raw] nomemostra saídas; -raw serve em scriptsshows outputs; -raw suits scripts
terraform show · terraform graphestado/plano legível · grafo em DOTreadable state/plan · graph in DOT
terraform plan -refresh-onlysó detecta drift (mudanças feitas fora do Terraform)only detects drift (changes made outside Terraform)
terraform plan -replace=ADDRforça recriar um recurso (substitui o antigo taint)forces recreating one resource (replaces the old taint)
terraform plan -target=ADDRlimita a um recurso; exceção, não rotinalimits to one resource; an exception, not routine
Código (desejado)Code (desired) *.tf Estado (conhecido)State (known) tfstate Nuvem (real)Cloud (actual) o que existe de fatowhat actually exists terraform plan = diferençaterraform plan = difference O plan atualiza o que sabe da nuvem (refresh), compara com o código e lista as ações necessárias.The plan refreshes what it knows of the cloud, compares it with the code and lists the needed actions.
Três versões da verdade: o que você quer (código), o que o Terraform lembra (estado) e o que existe (nuvem). O plan concilia as três.Three versions of the truth: what you want (code), what Terraform remembers (state) and what exists (cloud). plan reconciles the three.
Boa prática: em equipe, rode plan -out no CI, deixe alguém revisar e aplique aquele mesmo plano salvo. Assim o que foi revisado é exatamente o que roda, mesmo que a nuvem mude nesse meio-tempo.Good practice: in a team, run plan -out in CI, have someone review it and apply that very saved plan. That way what was reviewed is exactly what runs, even if the cloud changes in between.

Estado, backend e driftState, backend and drift

O estado é um arquivo JSON que mapeia cada bloco do código ao objeto real (por exemplo, aws_s3_bucket.logs ↔ o bucket acme-logs-prod) e guarda os atributos conhecidos. Sem ele o Terraform não sabe o que já criou. Três regras de ouro:The state is a JSON file mapping each code block to the real object (for example, aws_s3_bucket.logs ↔ the bucket acme-logs-prod) and storing the known attributes. Without it Terraform does not know what it already created. Three golden rules:

  1. Nunca edite à mão nem coloque no Git: ele pode conter senhas e chaves em texto puro.Never edit it by hand and never commit it: it may hold passwords and keys in plain text.
  2. Em equipe, use backend remoto com trava (S3, GCS, Azure Blob, HCP Terraform) para ninguém aplicar ao mesmo tempo.In a team, use a remote backend with locking (S3, GCS, Azure Blob, HCP Terraform) so nobody applies at the same time.
  3. Mude o estado só pelos comandos e blocos oficiais (terraform state, moved, import, removed).Change state only through the official commands and blocks (terraform state, moved, import, removed).
terraform state list                       # tudo que o estado conhece
terraform state show aws_s3_bucket.logs    # os atributos de um recurso
terraform state mv A B                     # renomeia no estado (prefira o bloco moved)
terraform state rm aws_s3_bucket.logs      # "esquece" sem apagar na nuvem (prefira removed)
terraform force-unlock LOCK_ID             # só se um apply morreu e deixou a trava

Refatorar e adotar sem destruirRefactor and adopt without destroying

# 1) Renomeou um recurso no código? Diga ao Terraform que é o MESMO objeto:
moved {
  from = aws_s3_bucket.logs
  to   = aws_s3_bucket.access_logs
}

# 2) Já existe na nuvem e foi criado no console? Adote no estado (Terraform >= 1.5):
import {
  to = aws_s3_bucket.legacy
  id = "bucket-criado-no-console"
}
# terraform plan -generate-config-out=generated.tf  escreve o código por você

# 3) Quer parar de gerenciar sem apagar o objeto? (Terraform >= 1.7)
removed {
  from = aws_s3_bucket.legacy
  lifecycle {
    destroy = false
  }
}
Renomear um recurso no código sem um bloco moved faz o Terraform planejar “destruir o antigo e criar o novo”. É o acidente mais comum de refatoração. Sempre leia o plan depois de renomear.Renaming a resource in code without a moved block makes Terraform plan “destroy the old, create the new”. It is the most common refactoring accident. Always read the plan after renaming.

DriftDrift

Drift é quando alguém muda a nuvem pelo console e o código fica desatualizado. O plan normal já mostra a diferença (o Terraform tenta desfazer a mudança manual). Use plan -refresh-only para só atualizar o estado com o que existe, ou aplique o código para corrigir a nuvem. Em times, o remédio é simples: permissões de escrita no console só para emergências.Drift is when someone changes the cloud in the console and the code gets out of date. A normal plan already shows the difference (Terraform tries to undo the manual change). Use plan -refresh-only to merely update state with what exists, or apply the code to fix the cloud. For teams the cure is simple: console write access only for emergencies.

Variáveis, locals e outputsVariables, locals and outputs

Três blocos separam o que muda do como é feito: variable (entrada de fora), locals (valor calculado dentro) e output (saída). Todo módulo, inclusive o diretório raiz, é uma função: entra variável, sai output.Three blocks separate what changes from how it is done: variable (input from outside), locals (computed value inside) and output (result). Every module, including the root directory, is a function: variables go in, outputs come out.

variable "environment" {
  description = "Nome do ambiente"
  type        = string
  default     = "dev"

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

variable "db_password" {
  type      = string
  sensitive = true          # some da saída do plan/apply (mas continua no state!)
}

locals {
  name_prefix = "${var.project}-${var.environment}"
  tags = {
    Project     = var.project
    Environment = var.environment
  }
}

output "name_prefix" {
  description = "Prefixo usado nos recursos"
  value       = local.name_prefix
}

Os valores chegam por vários caminhos. Em caso de conflito vence o de baixo da lista (o último):Values arrive through several paths. On conflict the one lower in the list (the last) wins:

  1. o default da variávelthe variable’s default
  2. variáveis de ambiente TF_VAR_nomeenvironment variables TF_VAR_name
  3. terraform.tfvars e terraform.tfvars.jsonterraform.tfvars and terraform.tfvars.json
  4. *.auto.tfvars (em ordem alfabética)*.auto.tfvars (in alphabetical order)
  5. -var-file=… e -var nome=valor na linha de comando, na ordem em que aparecem-var-file=… and -var name=value on the command line, in the order given
terraform plan -var-file=prod.tfvars
export TF_VAR_db_password="$(aws secretsmanager get-secret-value --secret-id app/db --query SecretString --output text)"
sensitive = true só esconde o valor na tela. Ele continua em texto puro no state. Por isso: backend remoto cifrado e com acesso restrito, e preferir não ter o segredo no Terraform. Exemplos: o RDS pode gerar e guardar a senha no Secrets Manager (manage_master_user_password = true); versões recentes do Terraform também têm valores efêmeros e atributos write-only que não vão para o state (veja a documentação da sua versão).sensitive = true only hides the value on screen. It stays in plain text in the state. Hence: an encrypted, access-restricted remote backend, and preferably not having the secret in Terraform at all. For example, RDS can generate and store the password in Secrets Manager (manage_master_user_password = true); recent Terraform versions also have ephemeral values and write-only attributes that never reach the state (see your version’s documentation).

Dependências, repetição e lifecycleDependencies, repetition and lifecycle

Dependências e o grafoDependencies and the graph

Cada vez que um recurso referencia outro (vpc_id = aws_vpc.main.id) o Terraform cria uma aresta no grafo: o referenciado nasce primeiro e morre por último. O que não depende entre si roda em paralelo (padrão: 10 ao mesmo tempo). Você quase nunca precisa de depends_on; ele serve só para dependências que o Terraform não enxerga (por exemplo, uma política IAM que precisa existir antes de uma aplicação funcionar).Each time a resource references another (vpc_id = aws_vpc.main.id) Terraform adds an edge to the graph: the referenced one is born first and dies last. Things that do not depend on each other run in parallel (default: 10 at a time). You almost never need depends_on; it is only for dependencies Terraform cannot see (for example, an IAM policy that must exist before an application works).

aws_vpc.main aws_iam_role.web aws_subnet.public aws_security_group.web aws_iam_instance_profile.web data.aws_ssm_parameter.ami aws_instance.web criada por últimocreated last Setas = “precisa de”. As três ramas à esquerda são independentes e rodam ao mesmo tempo.Arrows = “needed by”. The branches on the left are independent and run at the same time.
Grafo de dependências da pilha EC2 da seção AWS. Veja o seu com terraform graph | dot -Tsvg > graph.svg (precisa do Graphviz).Dependency graph of the EC2 stack in the AWS section. See yours with terraform graph | dot -Tsvg > graph.svg (needs Graphviz).

count, for_each e dynamiccount, for_each and dynamic

# for_each: um recurso por item, com CHAVE estável
variable "buckets" {
  type    = set(string)
  default = ["logs", "assets"]
}

resource "aws_s3_bucket" "this" {
  for_each = var.buckets
  bucket   = "${var.project}-${each.key}"     # each.key / each.value
}
# endereços: aws_s3_bucket.this["logs"], aws_s3_bucket.this["assets"]

# count: ligar/desligar (0 ou 1) ou N cópias idênticas
resource "aws_eip" "web" {
  count    = var.enable_eip ? 1 : 0
  instance = aws_instance.web.id
}

# dynamic: repete um BLOCO ANINHADO
resource "aws_security_group" "web" {
  name   = "web"
  vpc_id = var.vpc_id

  dynamic "ingress" {
    for_each = var.allowed_ports
    content {
      from_port   = ingress.value
      to_port     = ingress.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}
UseUseQuandoWhenCuidadoCareful
countliga/desliga (0 ou 1) ou cópias idênticason/off (0 or 1) or identical copiestirar um item do meio da lista muda os índices e recria os seguintesremoving an item from the middle of the list shifts indexes and recreates the following ones
for_eachitens com identidade (nome, chave)items with an identity (name, key)as chaves precisam ser conhecidas no plan (não podem vir de algo ainda não criado)keys must be known at plan time (cannot come from something not created yet)
dynamicblocos aninhados repetidosrepeated nested blocksnão abuse: blocos fixos são mais legíveisdo not overuse: fixed blocks are more readable

lifecycle: regras para casos delicadoslifecycle: rules for delicate cases

resource "aws_instance" "web" {
  # ...
  lifecycle {
    create_before_destroy = true         # sobe o novo antes de derrubar o velho
    prevent_destroy       = true         # erro se um plan tentar destruir (proteção de dados)
    ignore_changes        = [ami, tags["LastDeploy"]]   # ignora mudanças feitas por fora
    replace_triggered_by  = [aws_launch_template.web.latest_version]

    precondition {
      condition     = var.instance_type != "t2.nano"
      error_message = "Tipo de instância pequeno demais."
    }
  }
}

Módulos e ambientesModules and environments

Um módulo é uma pasta com arquivos .tf usada por outro código via bloco module. O diretório onde você roda terraform apply é o módulo raiz; os que ele chama são módulos filhos. Variáveis são as entradas e outputs, as saídas. Módulos vêm de uma pasta local, do Terraform Registry, do Git ou de um bucket.A module is a folder of .tf files used by other code through a module block. The directory where you run terraform apply is the root module; the ones it calls are child modules. Variables are the inputs and outputs the results. Modules come from a local folder, the Terraform Registry, Git or a bucket.

Módulo raizRoot module envs/dev/main.tf module "network" {…} module "web" {…} module "data" {…} modules/network rede: VPC, subnetsnetwork: VPC, subnets modules/web servidores e SGservers and SG modules/data banco e bucketdatabase and bucket variáveis →variables → ← outputs (ex.: module.network.public_subnets)← outputs (e.g. module.network.public_subnets)
O módulo raiz alimenta os filhos com variáveis e consome os outputs deles.The root module feeds the children with variables and consumes their outputs.
infra/
├── modules/
│   └── web/                 # módulo reutilizável
│       ├── main.tf          # recursos
│       ├── variables.tf     # entradas
│       ├── outputs.tf       # saídas
│       └── README.md        # gerado com terraform-docs
└── envs/
    ├── dev/
    │   ├── main.tf          # module "web" { source = "../../modules/web" ... }
    │   ├── backend.tf       # state em outro "key" do bucket
    │   └── dev.tfvars
    └── prod/
        └── ...
module "web" {
  source        = "../../modules/web"        # pasta local
  name          = "loja"
  instance_type = "t3.micro"
  subnet_id     = module.network.public_subnets[0]
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"  # Terraform Registry
  version = "~> 6.0"                         # SEMPRE fixe a versão
  # ...
}
Regra de móduloModule rulePor quêWhy
Faça pequeno e com uma responsabilidadeKeep it small, single-purposemódulos “faz-tudo” viram impossíveis de reusar e testar“do-everything” modules become impossible to reuse and test
Não configure provider dentro do móduloDo not configure provider inside the moduleo módulo herda do chamador; use providers = { aws = aws.east } para trocarthe module inherits from the caller; use providers = { aws = aws.east } to switch
Exponha só os outputs necessáriosExpose only the needed outputso que sai vira contrato; mudar depois quebra quem usawhat goes out becomes a contract; changing it later breaks users
Fixe versões de módulos de registry/GitPin versions of registry/Git modulesum init -upgrade não pode mudar a sua infra de surpresaan init -upgrade must not change your infra by surprise
Prefira composição a módulos aninhados fundoPrefer composition over deeply nested modulesé mais fácil de ler: o raiz “cola” os móduloseasier to read: the root “glues” the modules

Ambientes: pastas ou workspaces?Environments: folders or workspaces?

Pastas/estados separadosSeparate folders/statesWorkspacesWorkspaces
ComoHowenvs/dev, envs/prod, cada um com seu backendenvs/dev, envs/prod, each with its own backendterraform workspace new prod: mesmo código e mesmo backend, estado por workspaceterraform workspace new prod: same code and backend, one state per workspace
IsolamentoIsolationalto: contas AWS, permissões e estados diferenteshigh: different AWS accounts, permissions and statesbaixo: um engano de workspace select aplica na produçãolow: one wrong workspace select applies to production
Bom paraGood fordev/staging/prod de verdadereal dev/staging/prodcópias efêmeras e idênticas (testes, preview por PR)ephemeral, identical copies (tests, per-PR previews)

Times, CI e boas práticasTeams, CI and best practices

Em produção ninguém roda apply do notebook. O fluxo de equipe é: Pull Request → verificações automáticas + plan como comentário → revisão → merge → apply automático (ou aprovado) do mesmo plano.In production nobody runs apply from a laptop. The team flow is: Pull Request → automatic checks + plan as a comment → review → merge → automatic (or approved) apply of the same plan.

PR abertoPR opened validarvalidate tflint segurançasecurity trivy plan no PRon the PR revisãoreview humanahuman merge main apply o mesmo A autenticação com a nuvem usa OIDC (sem chaves fixas): veja a nota “GitHub para AWS com OIDC”.Cloud authentication uses OIDC (no long-lived keys): see the “GitHub to AWS with OIDC” note.
O pipeline típico. Todo passo antes do merge é barato e reversível; o apply é o único que muda o mundo.The typical pipeline. Every step before merge is cheap and reversible; apply is the only one that changes the world.
# .github/workflows/terraform.yml (trecho; confira as versões atuais das actions)
name: terraform
on:
  pull_request:
  push:
    branches: [main]
permissions:
  id-token: write       # OIDC
  contents: read
  pull-requests: write  # comentar o plan
jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/terraform-plan
          aws-region: sa-east-1
      - run: terraform fmt -check -recursive
      - run: terraform init -input=false
      - run: terraform validate
      - run: terraform plan -input=false -out=tfplan

Ferramentas que valem a penaTools worth having

FerramentaToolFazDoes
terraform fmt · validateformato e sintaxe (vêm no binário)format and syntax (built into the binary)
tflintlinter: tipos de instância inválidos, argumentos obsoletos, regras do provider AWSlinter: invalid instance types, deprecated arguments, AWS provider rules
trivy config . · checkovsegurança: bucket público, SG aberto, disco sem criptografia (o antigo tfsec foi incorporado ao Trivy)security: public bucket, open SG, unencrypted disk (the old tfsec was merged into Trivy)
terraform-docsgera o README de variáveis/outputs do módulogenerates the module’s variables/outputs README
infracostestima o custo mensal da mudança no PRestimates the monthly cost of the PR change
pre-commit (pre-commit-terraform)roda tudo isso antes do commitruns all of the above before commit
terraform testtestes nativos em .tftest.hcl (Terraform ≥ 1.6)native tests in .tftest.hcl (Terraform ≥ 1.6)
Atlantis · HCP Terraform · Terragruntautomação de PR · plataforma gerenciada · camada para reduzir repetição entre pastasPR automation · managed platform · layer to reduce repetition between folders
# tests/bucket.tftest.hcl
run "bucket_is_private" {
  command = plan

  assert {
    condition     = aws_s3_bucket_public_access_block.assets.block_public_acls == true
    error_message = "O bucket precisa bloquear ACLs públicas."
  }
}

Hábitos que evitam 90% dos incidentesHabits that prevent 90% of incidents

  1. Versões fixas: required_version, version = "~> X.Y" nos providers e módulos, e .terraform.lock.hcl no Git. Atualize de propósito (init -upgrade + revisar), de preferência com Renovate/Dependabot.Pinned versions: required_version, version = "~> X.Y" on providers and modules, and .terraform.lock.hcl in Git. Upgrade on purpose (init -upgrade + review), ideally with Renovate/Dependabot.
  2. Um estado por ambiente e por “raio de impacto”: um estado gigante deixa o plan lento e o risco enorme.One state per environment and per “blast radius”: one giant state makes plan slow and the risk huge.
  3. Nomes e tags padronizados (default_tags no provider AWS).Standard names and tags (default_tags on the AWS provider).
  4. prevent_destroy em bancos e buckets de dados; backups e versionamento ligados.prevent_destroy on databases and data buckets; backups and versioning on.
  5. Nunca -target como rotina e nunca apply sem ler o plan.Never -target as routine and never apply without reading the plan.
  6. O papel que o Terraform assume deve ter só as permissões necessárias (e papéis separados para plan e apply).The role Terraform assumes should have only the needed permissions (and separate roles for plan and apply).
  7. Tudo na nuvem pelo código; o console é para ler. Drift se detecta com plan agendado.Everything in the cloud through code; the console is for reading. Detect drift with a scheduled plan.

AWS: provider e credenciaisAWS: provider and credentials

A partir daqui o foco é a AWS. O provider hashicorp/aws usa as mesmas credenciais da AWS CLI e dos SDKs. A regra é simples: nunca escreva chaves no código. Para pessoas, use o IAM Identity Center (SSO); para pipelines, OIDC; para servidores na AWS, papéis de instância.From here on the focus is AWS. The hashicorp/aws provider uses the same credentials as the AWS CLI and SDKs. The rule is simple: never write keys in code. For people, use IAM Identity Center (SSO); for pipelines, OIDC; for servers on AWS, instance roles.

Quem executaWho runs itComo autenticarHow to authenticateDetalheDetail
Você, no notebookYou, on a laptopaws sso login --profile dev + export AWS_PROFILE=devaws sso login --profile dev + export AWS_PROFILE=devcredenciais temporárias; nada fica no disco além do cache do SSOtemporary credentials; nothing on disk except the SSO cache
GitHub Actions / CIGitHub Actions / CIOIDC: aws-actions/configure-aws-credentials assume um papelOIDC: aws-actions/configure-aws-credentials assumes a roleveja a nota “GitHub para AWS com OIDC”see the “GitHub to AWS with OIDC” note
Outra conta AWSAnother AWS accountbloco assume_role { role_arn = … } no providerassume_role { role_arn = … } block on the providerum papel por conta/ambiente (veja a nota AWS, Organizations)one role per account/environment (see the AWS note, Organizations)
Chaves de acesso fixasLong-lived access keyseviteavoidse existirem, vão só em variáveis de ambiente do CI, e devem ser trocadasif they exist, only in CI environment variables, and must be rotated
# versions.tf
terraform {
  required_version = ">= 1.10"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"          # confira a versão maior atual no Registry
    }
  }
}

# providers.tf
provider "aws" {
  region              = var.region
  allowed_account_ids = [var.account_id]     # TRAVA: recusa rodar na conta errada

  default_tags {
    tags = {
      Project     = var.project
      Environment = var.environment
      ManagedBy   = "terraform"
    }
  }
}

# um segundo provider para outra região (ex.: certificados do CloudFront só em us-east-1)
provider "aws" {
  alias  = "virginia"
  region = "us-east-1"
}

resource "aws_acm_certificate" "site" {
  provider          = aws.virginia
  domain_name       = "exemplo.com"
  validation_method = "DNS"
}
aws sts get-caller-identity     # SEMPRE confirme conta e papel antes de um apply
O erro mais caro de iniciantes é aplicar na conta errada (produção em vez de dev). allowed_account_ids no provider e o hábito de rodar aws sts get-caller-identity antes do apply evitam isso.The costliest beginner mistake is applying to the wrong account (production instead of dev). allowed_account_ids on the provider and the habit of running aws sts get-caller-identity before apply prevent it.

AWS: estado remoto no S3AWS: remote state on S3

O estado em equipe vai para um bucket S3. Desde o Terraform 1.10 o backend S3 tem trava nativa (use_lockfile = true): ele grava um arquivo .tflock ao lado do estado, sem precisar de DynamoDB (o bloqueio por DynamoDB ainda existe, mas está obsoleto). Existe um ovo-e-galinha: o bucket precisa existir antes do Terraform que o usa. A solução mais simples é criá-lo uma vez à mão (ou com um pequeno projeto de “bootstrap” de estado local).In a team, state goes into an S3 bucket. Since Terraform 1.10 the S3 backend has native locking (use_lockfile = true): it writes a .tflock file next to the state, with no DynamoDB needed (DynamoDB locking still exists but is deprecated). There is a chicken-and-egg problem: the bucket must exist before the Terraform that uses it. The simplest fix is to create it once by hand (or with a tiny local-state “bootstrap” project).

Você (SSO)You (SSO) CI (OIDC) terraform Bucket S3 do estadoState S3 bucket loja/dev/terraform.tfstate … .tfstate.tflock (trava)… .tfstate.tflock (lock) ✓ versionamento (recuperar)✓ versioning (recover) ✓ criptografia ✓ sem acesso público✓ encryption ✓ no public access estadostate API da AWSAWS API Quem aplica primeiro cria o .tflock; quem tentar ao mesmo tempo recebe “Error acquiring the state lock”.Whoever applies first creates the .tflock; anyone trying at the same time gets “Error acquiring the state lock”.
Estado remoto no S3: um arquivo por projeto/ambiente (key), versionado e travado.Remote state in S3: one file per project/environment (key), versioned and locked.
# Bootstrap (uma vez por conta). Troque nome e região; S3 exige nome GLOBALMENTE único.
BUCKET=acme-tfstate-123456789012
REGION=sa-east-1

aws s3api create-bucket --bucket $BUCKET --region $REGION \
  --create-bucket-configuration LocationConstraint=$REGION   # (em us-east-1 omita esta linha)
aws s3api put-bucket-versioning --bucket $BUCKET \
  --versioning-configuration Status=Enabled
aws s3api put-public-access-block --bucket $BUCKET \
  --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
# backend.tf  (o bloco backend NÃO aceita variáveis)
terraform {
  backend "s3" {
    bucket       = "acme-tfstate-123456789012"
    key          = "loja/dev/terraform.tfstate"   # um "key" por projeto e ambiente
    region       = "sa-east-1"
    encrypt      = true
    use_lockfile = true                           # trava nativa (Terraform >= 1.10)
  }
}
terraform init                    # detecta o backend novo e oferece migrar o estado local
terraform init -migrate-state     # (ou explicitamente)
terraform init -backend-config="key=loja/prod/terraform.tfstate"   # muda o key sem editar o código

Permissões mínimas do papel que roda o Terraform sobre o estado:Minimum permissions for the role that runs Terraform over the state:

{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow", "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::acme-tfstate-123456789012" },
    { "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::acme-tfstate-123456789012/loja/*" }
  ]
}
Se o estado for apagado ou corrompido, o versionamento do bucket permite voltar a uma versão anterior. Guarde o bucket de estado em uma conta de infraestrutura/“shared” e restrinja o acesso: ele contém segredos.If the state is deleted or corrupted, bucket versioning lets you go back to an earlier version. Keep the state bucket in an infrastructure/“shared” account and restrict access: it contains secrets.

AWS: VPC, EC2 e S3 passo a passoAWS: VPC, EC2 and S3 step by step

Vamos montar uma pilha pequena e realista: uma VPC com subnets públicas, um servidor EC2 com nginx acessível por HTTP, sem SSH (acesso pelo Session Manager do SSM, com papel IAM) e um bucket S3 privado. É o mesmo esqueleto de muitos ambientes reais, em ponto pequeno.Let us build a small, realistic stack: a VPC with public subnets, an EC2 server running nginx reachable over HTTP, no SSH (access through SSM Session Manager, with an IAM role) and a private S3 bucket. It is the skeleton of many real environments, in miniature.

Conta AWS · sa-east-1AWS account · sa-east-1 Internet Internet GW VPC 10.0.0.0/16 subnet pública apublic subnet a Security Group EC2 nginx :80 de entrada:80 inbound subnet pública bpublic subnet b (reserva)(spare) S3: tfstate S3: assets (privado)S3: assets (private) IAM role + profile acesso via SSMaccess via SSM SSM Session Manager
A pilha construída nesta seção. O Terraform (do seu notebook ou do CI) cria tudo; o estado vive no bucket tfstate.The stack built in this section. Terraform (from your laptop or CI) creates everything; the state lives in the tfstate bucket.

Variáveis e redeVariables and network

# variables.tf
variable "project" {
  type    = string
  default = "loja"
}

variable "environment" {
  type    = string
  default = "dev"
}

variable "region" {
  type    = string
  default = "sa-east-1"
}

variable "account_id" {
  type        = string
  description = "ID da conta onde é permitido aplicar"
}

# network.tf
data "aws_availability_zones" "available" {
  state = "available"
}

data "aws_caller_identity" "current" {}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 6.0"

  name = "${var.project}-${var.environment}"
  cidr = "10.0.0.0/16"

  azs            = slice(data.aws_availability_zones.available.names, 0, 2)
  public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]

  enable_nat_gateway = false   # NAT Gateway cobra por hora; só ligue se houver subnets privadas
}

Segurança, papel e servidorSecurity, role and server

# security.tf
resource "aws_security_group" "web" {
  name_prefix = "${var.project}-web-"
  description = "HTTP de entrada, tudo de saida"
  vpc_id      = module.vpc.vpc_id

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_vpc_security_group_ingress_rule" "http" {
  security_group_id = aws_security_group.web.id
  cidr_ipv4         = "0.0.0.0/0"
  from_port         = 80
  to_port           = 80
  ip_protocol       = "tcp"
}

resource "aws_vpc_security_group_egress_rule" "all" {
  security_group_id = aws_security_group.web.id
  cidr_ipv4         = "0.0.0.0/0"
  ip_protocol       = "-1"
}

# iam.tf  (acesso por SSM: sem chave SSH e sem porta 22)
data "aws_iam_policy_document" "ec2_assume" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "web" {
  name               = "${var.project}-${var.environment}-web"
  assume_role_policy = data.aws_iam_policy_document.ec2_assume.json
}

resource "aws_iam_role_policy_attachment" "ssm" {
  role       = aws_iam_role.web.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_instance_profile" "web" {
  name = "${var.project}-${var.environment}-web"
  role = aws_iam_role.web.name
}

# compute.tf
data "aws_ssm_parameter" "al2023" {
  name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"
}

resource "aws_instance" "web" {
  ami                         = data.aws_ssm_parameter.al2023.value
  instance_type               = "t3.micro"
  subnet_id                   = module.vpc.public_subnets[0]
  vpc_security_group_ids      = [aws_security_group.web.id]
  iam_instance_profile        = aws_iam_instance_profile.web.name
  associate_public_ip_address = true

  user_data = <<-EOT
    #!/bin/bash
    dnf install -y nginx
    systemctl enable --now nginx
  EOT

  metadata_options {
    http_tokens = "required"        # IMDSv2 obrigatório
  }

  root_block_device {
    encrypted = true
  }

  tags = {
    Name = "${var.project}-${var.environment}-web"
  }

  lifecycle {
    ignore_changes = [ami]          # a AMI "latest" muda com o tempo: não recriar o servidor por isso
  }
}

Bucket privado e saídasPrivate bucket and outputs

# storage.tf
resource "aws_s3_bucket" "assets" {
  bucket = "${var.project}-${var.environment}-assets-${data.aws_caller_identity.current.account_id}"
}

resource "aws_s3_bucket_public_access_block" "assets" {
  bucket                  = aws_s3_bucket.assets.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_versioning" "assets" {
  bucket = aws_s3_bucket.assets.id
  versioning_configuration {
    status = "Enabled"
  }
}

# outputs.tf
output "web_instance_id" {
  value = aws_instance.web.id
}

output "web_public_ip" {
  value = aws_instance.web.public_ip
}

output "assets_bucket" {
  value = aws_s3_bucket.assets.bucket
}
export AWS_PROFILE=dev
aws sts get-caller-identity                     # conta certa?
terraform init
terraform plan -var account_id=123456789012 -out=tfplan
terraform apply tfplan

curl http://$(terraform output -raw web_public_ip)           # página do nginx
aws ssm start-session --target $(terraform output -raw web_instance_id)   # shell sem SSH

terraform destroy -var account_id=123456789012  # ao terminar o estudo, para não gerar custo
Custo: uma t3.micro custa centavos por hora, mas endereço IPv4 público e NAT Gateway também cobram por hora, e volumes/IPs esquecidos continuam cobrando. Faça destroy ao terminar, use uma conta de estudo e configure um alerta de orçamento (AWS Budgets). Para o bucket, destroy falha se ele tiver objetos (BucketNotEmpty); em ambientes de teste use force_destroy = true.Cost: a t3.micro costs cents per hour, but a public IPv4 address and a NAT Gateway also bill by the hour, and forgotten volumes/IPs keep billing. Run destroy when finished, use a study account and set a budget alert (AWS Budgets). For the bucket, destroy fails if it has objects (BucketNotEmpty); in test environments use force_destroy = true.
Para aprender, o papel do Terraform pode ser administrador numa conta de estudo isolada. Em ambientes reais, use um papel com as permissões estritamente necessárias (e um papel só de leitura para o plan dos Pull Requests).For learning, Terraform’s role can be admin in an isolated study account. In real environments, use a role with strictly the needed permissions (and a read-only role for pull-request plans).

AWS: padrões, erros e roteiroAWS: patterns, errors and roadmap

Padrões que se repetem em toda pilha AWSPatterns that repeat in every AWS stack

PadrãoPatternComo no TerraformHow in Terraform
Saber “onde estou”Know “where I am”data.aws_caller_identity.current.account_id, data.aws_region.current
Políticas IAM sem JSON na mãoIAM policies without hand-written JSONdata "aws_iam_policy_document" (valida e interpola)data "aws_iam_policy_document" (validates and interpolates)
Tags em tudoTags on everythingdefault_tags no provider + tags só onde for específicodefault_tags on the provider + tags only where specific
Usar o que outro time criouUse what another team createddata "aws_vpc", data "aws_subnets", ou ler outputs de outro estado com terraform_remote_statedata "aws_vpc", data "aws_subnets", or read another state’s outputs with terraform_remote_state
SegredosSecretsSecrets Manager/SSM Parameter Store; o recurso lê em tempo de execução, o código não guarda o valorSecrets Manager/SSM Parameter Store; the resource reads at run time, the code does not hold the value
Várias contasSeveral accountsum provider por conta (alias + assume_role) ou uma pasta/estado por contaone provider per account (alias + assume_role) or one folder/state per account
Mais de uma regiãoMore than one regionprovider "aws" { alias = "x" region = "…" } e provider = aws.x no recursoprovider "aws" { alias = "x" region = "…" } and provider = aws.x on the resource

Erros comuns e o que fazerCommon errors and what to do

MensagemMessageCausa provávelLikely causeSoluçãoFix
Error acquiring the state lockoutro apply rodando, ou um que morreuanother apply running, or one that diedespere; se for um lock órfão, terraform force-unlock IDwait; for an orphan lock, terraform force-unlock ID
AccessDenied / UnauthorizedOperationo papel não tem a permissãothe role lacks the permissionleia a ação na mensagem (ec2:RunInstances…), confira sts get-caller-identityread the action in the message (ec2:RunInstances…), check sts get-caller-identity
BucketAlreadyExistsnome de bucket é globalbucket names are globalinclua o ID da conta ou um sufixo aleatórioinclude the account ID or a random suffix
EntityAlreadyExists / “already exists”o objeto existe mas não está no estadothe object exists but is not in the stateimport (bloco) e rode o plan de novoimport (block) and re-run the plan
Invalid IAM Instance Profileo IAM é eventualmente consistenteIAM is eventually consistentrode o apply de novo; mantenha as referências (não use nomes fixos como texto)re-run apply; keep references (do not hard-code names as text)
BucketNotEmpty / DependencyViolationdestroy de algo ainda em usodestroying something still in useesvazie o bucket (ou force_destroy em teste); apague antes o que usa a VPC/SGempty the bucket (or force_destroy in test); delete first whatever uses the VPC/SG
Invalid for_each argument … known only after applyas chaves dependem de algo ainda não criadokeys depend on something not yet createduse chaves estáticas (nomes que você escreve) e valores dinâmicosuse static keys (names you write) and dynamic values
Cycle:dois recursos se referenciamtwo resources reference each otherquebre o ciclo (ex.: regra de SG separada, como aws_vpc_security_group_*_rule)break the cycle (e.g. separate SG rule, such as aws_vpc_security_group_*_rule)

Ordem sugerida para aprender AWS com TerraformSuggested order for learning AWS with Terraform

  1. Conta de estudo, SSO e aws sts get-caller-identity.Study account, SSO and aws sts get-caller-identity.
  2. Um bucket S3 privado (o “hello world” da AWS), com plan/apply/destroy.A private S3 bucket (AWS’s “hello world”), with plan/apply/destroy.
  3. Backend remoto no S3 e migração do estado local.Remote backend on S3 and migrating local state.
  4. A pilha VPC + EC2 + IAM desta nota.The VPC + EC2 + IAM stack from this note.
  5. Extrair para um módulo e criar envs/dev e envs/prod (contas diferentes).Extract into a module and create envs/dev and envs/prod (different accounts).
  6. Pipeline com OIDC: plan no PR, apply no merge.Pipeline with OIDC: plan on PRs, apply on merge.
  7. Serviços de dados e contêineres: RDS (manage_master_user_password), ECS/Fargate, ALB, Route 53, ACM.Data and container services: RDS (manage_master_user_password), ECS/Fargate, ALB, Route 53, ACM.

Cola e perguntas de revisãoCheat sheet and review questions

Cola (cheat sheet)Cheat sheet

# bloco              # forma mínima
terraform { required_version = ">= 1.10" }
provider "aws"       { region = "sa-east-1" }
resource "T" "N"     { arg = valor }              # criar: T.N.attr
data "T" "N"         { arg = valor }              # ler:   data.T.N.attr
variable "v"         { type = string }            # var.v
locals               { x = "${var.v}-x" }         # local.x
output "o"           { value = T.N.attr }
module "m"           { source = "./m" }           # module.m.out
Quero…I want…ComandoCommand
começar/atualizar providersstart/update providersterraform init · init -upgrade
ver o que muda e aplicarsee what changes and applyplan -out=tfplan → apply tfplan
listar/ver o que está no estadolist/see what is in the statestate list · state show ADDR
recriar um recursorecreate one resourceapply -replace=ADDR
detectar driftdetect driftplan -refresh-only
testar uma expressãotest an expressionterraform console
migrar o estado para o backend novomove state to the new backendinit -migrate-state
apagar tudodelete everythingdestroy (com cuidado)

Perguntas de revisão (estilo Terraform Associate)Review questions (Terraform Associate style)

PerguntaQuestionRespostaAnswer
Qual a diferença entre resource e data?Difference between resource and data?resource cria e gerencia; data só lê algo existente.resource creates and manages; data only reads something existing.
O que o terraform init faz?What does terraform init do?Baixa providers e módulos, prepara o backend e cria o lock file.Downloads providers and modules, sets up the backend and creates the lock file.
Por que o plan mostra -/+?Why does the plan show -/+?O recurso será destruído e recriado porque o argumento alterado não pode ser mudado no lugar.The resource will be destroyed and recreated because the changed argument cannot be updated in place.
O que é o state e por que não vai para o Git?What is state and why is it not committed?É o mapa código↔realidade; pode ter segredos em texto puro. Use backend remoto, cifrado e com trava.It is the code↔reality map; it may hold plain-text secrets. Use a remote, encrypted, locked backend.
Como o Terraform decide a ordem de criação?How does Terraform decide creation order?Pelo grafo de dependências formado pelas referências; depends_on só para o que não é visível.By the dependency graph formed by references; depends_on only for what is not visible.
count ou for_each para uma lista de nomes?count or for_each for a list of names?for_each: tem chave estável; com count, remover um item do meio recria os seguintes.for_each: it has a stable key; with count, removing a middle item recreates the following ones.
Como renomear um recurso sem destruí-lo?How to rename a resource without destroying it?Bloco moved { from = … to = … } (ou terraform state mv).moved { from = … to = … } block (or terraform state mv).
Como trazer para o Terraform algo criado no console?How to bring something created in the console into Terraform?Bloco import (ou terraform import) e escrever o código correspondente.import block (or terraform import) and write the matching code.
O que protege contra dois apply simultâneos?What protects against two simultaneous applys?A trava do backend (no S3: use_lockfile = true).The backend lock (on S3: use_lockfile = true).
sensitive = true protege o segredo no state?Does sensitive = true protect the secret in state?Não; só o oculta na saída. O state continua com o valor.No; it only hides it in output. The state still holds the value.
Para que serve o .terraform.lock.hcl?What is .terraform.lock.hcl for?Fixa as versões exatas dos providers (com hashes) para todos usarem as mesmas; vai para o Git.Pins exact provider versions (with hashes) so everyone uses the same; commit it.
Como usar duas regiões AWS no mesmo código?How to use two AWS regions in the same code?Dois blocos provider "aws", um com alias, e provider = aws.alias no recurso.Two provider "aws" blocks, one with alias, and provider = aws.alias on the resource.
Workspaces ou pastas para dev/prod?Workspaces or folders for dev/prod?Pastas/estados (e contas) separados: isolamento forte. Workspaces são para cópias efêmeras.Separate folders/states (and accounts): strong isolation. Workspaces are for ephemeral copies.