Índice

  1. O que é infraestrutura como código
  2. Por que Terraform
  3. Conceitos essenciais
  4. Sua primeira infraestrutura com Terraform
  5. Variáveis: reutilização e flexibilidade
  6. State: a memória do Terraform
  7. Módulos: organização em larga escala
  8. Erros comuns

O que é infraestrutura como código {#o-que-e}

Imagine que você precisa construir uma casa. Você tem duas opções:

  1. Ir comprando materiais no depósito e montando no sentindo, sem planta. Cada pedreiro faz do seu jeito. Se a casa cair, ninguém lembra exatamente como construiu.
  2. Contratar um arquiteto que desenha a planta completa, com cada medida, cada material, cada etapa. A construtora segue a planta. Se algo cair, você sabe exatamente o que refazer — é só seguir a planta de novo.

A opção 2 é obviamente melhor. E é exatamente isso que Infraestrutura como Código (em inglês, Infrastructure as Code, ou IaC) faz para seus servidores na nuvem.

Em vez de abrir o painel da AWS, clicar em "criar servidor", escolher configurações, clicar em "criar banco de dados", preencher mais formulários — tudo manualmente, sem rastro —, você escreve um arquivo de texto que descreve toda a infraestrutura. Um software lê esse arquivo e cria tudo automaticamente.

Infraestrutura como Código é a prática de gerenciar sua infraestrutura de TI (servidores, bancos de dados, redes, firewalls) através de arquivos de texto versionáveis, ao invés de configurações manuais em painéis.

Os problemas que IaC resolve

Problema (sem IaC)Solução (com IaC)
"Como criamos esse servidor?" — ninguém lembraTudo está no código, versionado no Git
Recriar o ambiente de teste leva 2 diasUm comando recria tudo em minutos
Um dev configura diferente do outroO código é a fonte da verdade, idempotente
Mudança manual quebra algo e ninguém sabe o quêMudanças passam por code review
Auditoria de compliance é impossívelO histórico do Git é a auditoria

Idempotente é uma palavra importante aqui: significa que executar o mesmo comando várias vezes produz o mesmo resultado. Se você pedir "crie um servidor chamado web-01" e ele já existe, o Terraform não cria outro — ele reconhece que já está lá e não faz nada. Isso evita duplicações acidentais.

Por que Terraform {#por-que-terraform}

Existem várias ferramentas de IaC no mercado. As principais são:

FerramentaCriadorEstiloCloud-agnostic?
TerraformHashiCorpDeclarativoSim
AWS CloudFormationAmazonDeclarativoSó AWS
PulumiPulumiImperativo (linguagens reais)Sim
AnsibleRed HatProcedural/DeclarativoSim (mais config mgmt)

Terraform é a ferramenta mais popular de IaC. Algumas razões:

  • Declarativo: você descreve o que quer (um servidor com 4GB de RAM), não como chegar lá (os 15 cliques no painel). O Terraform descobre o "como".
  • Cloud-agnostic (independente de nuvem): o mesmo Terraform gerencia AWS, Google Cloud, Azure, e mais de 3.000 outros provedores. Aprende uma vez, usa em qualquer nuvem.
  • Gratuito e open source: a versão OpenTofu (fork da comunidade) é 100% gratuita. A HashiCorp oferece versão paga com features enterprise.
  • Comunidade gigante: milhares de módulos prontos para usar.

Declarativo vs imperativo

Declarativo (Terraform): você diz o resultado final que quer.

# "Quero um servidor EC2 com esta configuração"
resource "aws_instance" "web" {
  ami           = "ami-123456"
  instance_type = "t3.micro"
}

Imperativo (scripts Bash, por exemplo): você diz cada passo.

# "Faça isto, depois aquilo, depois aquilo outro"
aws ec2 run-instances --image-id ami-123456 --instance-type t3.micro
aws ec2 create-tags --resources i-abc123 --tags Key=Name,Value=web
aws ec2 modify-instance-attribute --instance-id i-abc123 ...

O estilo declarativo é melhor para infraestrutura porque você não precisa rastrear o estado atual — apenas declara o desejado e a ferramenta calcula a diferença.

Conceitos essenciais {#conceitos}

Antes de escrever código, entenda estes quatro conceitos:

1. Provider (provedor)

Um provider é um plugin que permite ao Terraform falar com um serviço. Existe um provider para AWS, um para Google Cloud, um para GitHub, um para Kubernetes, etc. O provider traduz os comandos do Terraform em chamadas de API do serviço.

2. Resource (recurso)

Um resource é a unidade básica de infraestrutura. Um servidor, um banco de dados, uma regra de firewall, um bucket de armazenamento — cada um é um resource. A maioria dos seus arquivos Terraform será composta de resources.

3. State (estado)

O state é um arquivo (.tfstate) que registra o mapeamento entre o que está no seu código e o que existe na nuvem. É a "memória" do Terraform.

Por que o state é necessário? Porque o Terraform é declarativo e idempotente. Para saber se precisa criar, modificar ou destruir algo, ele compara:

  • O que está no seu código (estado desejado)
  • O que está no state (o que ele criou da última vez)
  • O que está na nuvem (estado real)

Sem o state, o Terraform não conseguiria saber se aquele servidor foi criado por ele ou por outra pessoa.

4. Plan e Apply

Terraform opera em dois passos principais:

  • terraform plan: mostra o que vai fazer, sem fazer nada. É uma simulação. "Vou criar 1 servidor, 1 banco de dados, e destruir 1 regra de firewall antiga." Você revisa antes de prosseguir.
  • terraform apply: executa de fato as mudanças mostradas no plan.

Analogia: plan é o orçamento que o arquiteto te mostra antes de construir. apply é quando você aprova e a construtora começa a obra.

Sua primeira infraestrutura com Terraform {#primeira-infra}

Vamos criar um servidor EC2 na AWS. EC2 (Elastic Compute Cloud) é o serviço de servidores virtuais da AWS — basicamente, um computador que você aluga na nuvem.

Pré-requisitos

  1. Terraform instalado (versão 1.0+)
  2. Conta na AWS com credenciais configuradas (variáveis de ambiente AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY)
  3. Um editor de texto

Passo 1: criar o arquivo main.tf

# main.tf — arquivo principal do Terraform

# Diz ao Terraform que vamos usar a AWS como provedor
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"  # Onde baixar o provider
      version = "~> 5.0"         # Versão 5.x (qualquer 5.algo)
    }
  }
}

# Configura o provider da AWS
provider "aws" {
  region = "us-east-1"  # Norte da Virgínia — a região mais barata da AWS
}

# Cria um servidor EC2
# "aws_instance" é o tipo do resource (uma instância EC2)
# "web" é o nome lógico que damos (para referenciar depois)
resource "aws_instance" "web" {
  ami           = "ami-0c7217cdde317cfec"  # ID da imagem (Amazon Linux 2023)
  instance_type = "t3.micro"               # Tamanho: 2 vCPUs, 1GB RAM

  tags = {
    Name        = "meu-primeiro-servidor"   # Nome que aparece no painel AWS
    Environment = "dev"                    # Tag para organizar recursos
    Owner       = "inicialize"
  }
}

Passo 2: inicializar o Terraform

terraform init

Este comando baixa o provider da AWS e prepara o diretório. Você verá algo como "Terraform has been successfully initialized!".

Passo 3: ver o plano (simulação)

terraform plan

O Terraform vai mostrar:

Terraform will perform the following actions:

  # aws_instance.web will be created
  + resource "aws_instance" "web" {
      + ami           = "ami-0c7217cdde317cfec"
      + instance_type = "t3.micro"
      + tags          = {
          + Environment = "dev"
          + Name        = "meu-primeiro-servidor"
          + Owner       = "inicialize"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

O símbolo + significa "criar". Se fosse ~ seria "modificar", e - seria "destruir". Revise com calma — especialmente o número de "destroy".

Passo 4: aplicar (executar de verdade)

terraform apply

O Terraform pede confirmação ("Do you want to perform these actions?"). Digite yes. Em segundos, seu servidor está rodando na AWS.

Passo 5: destruir (para não pagar)

terraform destroy

Importante: este comando remove tudo o que o Terraform criou. Use-o quando terminar de testar para não gerar custos desnecessários. Digite yes para confirmar.

Variáveis: reutilização e flexibilidade {#variaveis}

No exemplo anterior, a região e o tipo da instância estavam "fixos" no código. Para reutilizar o mesmo código em diferentes ambientes (dev, produção), usamos variáveis.

# variables.tf — declara as variáveis

variable "aws_region" {
  description = "Região da AWS onde os recursos serão criados"
  type        = string
  default     = "us-east-1"  # Valor padrão, se não for passado
}

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

variable "environment" {
  description = "Nome do ambiente (dev, staging, prod)"
  type        = string
  default     = "dev"
}
# main.tf — usando as variáveis

provider "aws" {
  region = var.aws_region  # Referência à variável com var.NOME
}

resource "aws_instance" "web" {
  ami           = "ami-0c7217cdde317cfec"
  instance_type = var.instance_type

  tags = {
    Name        = "web-${var.environment}"  # Interpolação: web-dev, web-prod
    Environment = var.environment
  }
}

Para passar valores diferentes na linha de comando:

# Cria um servidor maior em produção
terraform apply -var="instance_type=t3.large" -var="environment=prod"

Ou crie um arquivo terraform.tfvars:

# terraform.tfvars — valores específicos por ambiente
instance_type = "t3.large"
environment   = "prod"
aws_region    = "sa-east-1"  # São Paulo (mais caro, mas menor latência no Brasil)

State: a memória do Terraform {#state}

O arquivo de state (terraform.tfstate) é crítico. Se você o perder, o Terraform perde a noção do que criou — e pode duplicar recursos ou falhar ao destruir.

Regras de ouro do state

  1. Nunca edite o .tfstate manualmente. Sempre use comandos do Terraform.
  2. Sempre armazene o state remotamente, não localmente.
  3. Sempre trave o state durante uso (locking) para evitar corrupção.

Remote state (state remoto)

Para times, o state deve ficar em um local compartilhado com bloqueio (lock). Na AWS, usamos um bucket S3 com uma tabela DynamoDB para lock.

S3 é o serviço de armazenamento de arquivos da AWS. DynamoDB é um banco de dados NoSQL da AWS — neste caso, usado só para gerenciar locks.

# backend.tf — configura onde o state é guardado

terraform {
  backend "s3" {
    bucket         = "meu-terraform-state-dev"  # Bucket S3 que guarda o state
    key            = "infra/terraform.tfstate"  # Caminho do arquivo dentro do bucket
    region         = "us-east-1"                # Região do bucket
    dynamodb_table = "terraform-locks"          # Tabela para lock (evita edição simultânea)
    encrypt        = true                       # Criptografa o state em repouso
  }
}

Atenção: o bucket S3 e a tabela DynamoDB precisam existir antes de rodar terraform init com o backend remoto. Crie-os manualmente uma única vez (ou use um projeto Terraform separado para o backend).

Por que lock é importante?

Se dois desenvolvedores rodarem terraform apply ao mesmo tempo, ambos leem o state, fazem mudanças, e o segundo sobrescreve o primeiro — corrompendo o state. O lock (via DynamoDB) impede isso: o primeiro trava, o segundo espera.

Módulos: organização em larga escala {#modulos}

Quando sua infraestrutura cresce (10, 50, 100 resources), um único main.tf fica impossível de manter. Módulos são a solução: pacotes reutilizáveis de configuração Terraform.

Analogia: um módulo é como uma função em programação. Em vez de repetir o código do servidor em cada ambiente, você cria um módulo "servidor-web" e o chama com parâmetros diferentes.

Estrutura de um módulo

projeto/
├── main.tf          # Chama os módulos
├── variables.tf     # Variáveis do projeto
├── modules/
│   └── web-server/  # Módulo reutilizável
│       ├── main.tf       # Define os resources do módulo
│       ├── variables.tf  # Inputs do módulo
│       └── outputs.tf    # Outputs do módulo (valores que retorna)
# modules/web-server/main.tf — conteúdo do módulo

variable "instance_type" {
  type    = string
  default = "t3.micro"
}

variable "environment" {
  type = string
}

variable "app_name" {
  type = string
}

resource "aws_instance" "web" {
  ami           = "ami-0c7217cdde317cfec"
  instance_type = var.instance_type

  tags = {
    Name        = "${var.app_name}-${var.environment}"
    Environment = var.environment
  }
}

# Outputs: valores que o módulo retorna para quem o chamou
output "instance_id" {
  value       = aws_instance.web.id
  description = "ID da instância criada"
}

output "public_ip" {
  value       = aws_instance.web.public_ip
  description = "IP público do servidor"
}
# main.tf — usando o módulo no projeto principal

module "web_dev" {
  source        = "./modules/web-server"  # Caminho para o módulo
  app_name      = "blog"
  environment   = "dev"
  instance_type = "t3.micro"
}

module "web_prod" {
  source        = "./modules/web-server"  # Mesmo módulo, parâmetros diferentes
  app_name      = "blog"
  environment   = "prod"
  instance_type = "t3.large"              # Produção merece mais recursos
}

# Acessando os outputs do módulo
output "ip_producao" {
  value = module.web_prod.public_ip  # Pega o IP público do módulo de prod
}

Ao usar módulos, você roda terraform init novamente para baixar o código do módulo local:

terraform init  # Detecta e carrega o novo módulo
terraform plan
terraform apply

Módulos públicos (Terraform Registry)

O Terraform Registry é como um "NPM para Terraform" — um repositório de módulos que a comunidade compartilha. Em vez de criar do zero, você pode usar módulos prontos:

# Usa um módulo oficial da AWS para criar um VPC
# VPC (Virtual Private Cloud) = sua rede privada isolada dentro da AWS
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"  # Módulo do Registry
  version = "5.0.0"                          # Sempre fixe a versão!

  name = "meu-vpc"
  cidr = "10.0.0.0/16"  # Bloco de IPs da rede

  azs             = ["us-east-1a", "us-east-1b"]  # Zonas de disponibilidade
  public_subnets  = ["10.0.1.0/24", "10.0.2.0/24"]
  private_subnets = ["10.0.3.0/24", "10.0.4.0/24"]

  enable_nat_gateway = true  # Permite que subnets privadas acessem a internet
}

Dica: módulos prontos economizam muito tempo, mas sempre leia o código antes de usar. Um módulo mal feito pode criar recursos caros ou inseguros que você não esperava.

Erros comuns {#erros-comuns}

1. Commitar o arquivo .tfstate no Git

Problema: o state contém informações sensíveis (IPs, IDs internos) e é binário. Se duas pessoas editarem ao mesmo tempo, o Git não consegue mesclar — vira conflito impossível de resolver.

Solução: adicione *.tfstate ao .gitignore e use remote state (S3, Terraform Cloud, etc).

2. Commitar credenciais no código

Problema: colocar access_key e secret_key diretamente no provider "faz funcionar", mas vaza suas chaves da AWS para o mundo (se o repositório for público).

Solução: nunca coloque segredos no código. Use variáveis de ambiente, AWS CLI (aws configure), ou um gerenciador de segredos (AWS Secrets Manager, Vault). Para senhas de banco, use o resource aws_secretsmanager_secret e referencie via data block.

3. Não fixar versões de provider e módulos

Problema: sem versão fixada, terraform init pode baixar uma versão nova do provider com breaking changes (mudanças que quebram compatibilidade). Seu código para de funcionar do nada.

Solução: sempre fixe versões:

# Bom — fixa na major version
version = "~> 5.0"

# Melhor — fixa exata
version = "5.32.0"

# Ruim — pega qualquer versão (até betas)
version = ">= 0"

4. Rodar terraform apply sem revisar o plan

Problema: em infraestrutura grande, um apply cego pode destruir recursos críticos que você não percebeu no plan.

Solução: sempre leia o plan. Preste atenção especial em:

  • - destroy: recursos que serão apagados — é isso que você quer?
  • ~ in-place: recursos que serão modificados — pode causar downtime?

Em produção, salve o plan e aplique-o depois:

terraform plan -out=plan.tfplan    # Salva o plano
terraform apply plan.tfplan        # Aplica exatamente o que foi salvo

5. Um state para tudo

Problema: colocar dev, staging e produção no mesmo state. Uma mudança em dev pode acidentalmente afetar produção.

Solução: um state por ambiente. Use workspaces ou, melhor ainda, diretórios/separação de backend:

projeto/
├── environments/
│   ├── dev/
│   │   ├── backend.tf    # backend S3 com key="dev/terraform.tfstate"
│   │   └── main.tf
│   └── prod/
│       ├── backend.tf    # backend S3 com key="prod/terraform.tfstate"
│       └── main.tf

Assim cada ambiente é totalmente isolado — um terraform destroy em dev nunca tocará produção.


Precisa de ajuda com Terraform? A Inicialize Tec implementa infraestrutura como código, migra ambientes manuais para Terraform e estrutura pipelines de CI/CD para IaC. Conversamos sobre seu caso.