titulo: "Terraform: Boas Práticas para Infrastructure as Code" descricao: "Aprenda as melhores práticas para escrever código Terraform limpo, seguro e manutenível em projetos de produção." data: "2025-11-28" autor: "Rany" tags: ["Terraform", "IaC", "DevOps", "Cloud"] tempoLeitura: "10 min"
Introdução
O Terraform se tornou a ferramenta padrão para Infrastructure as Code (IaC). Mas escrever código Terraform de qualidade vai além de apenas fazer funcionar. Vamos explorar as melhores práticas adotadas pela indústria.
Estrutura de Diretórios
Organização Recomendada
terraform/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ │ └── ...
│ └── prod/
│ └── ...
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── compute/
│ │ └── ...
│ └── database/
│ └── ...
└── global/
└── s3-backend/
└── main.tf
1. Use Remote State
NUNCA guarde o state file localmente em produção.
Configuração com S3 e DynamoDB
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-lock"
encrypt = true
}
}
Por que DynamoDB?
O DynamoDB fornece state locking, prevenindo que múltiplas pessoas executem terraform apply simultaneamente.
resource "aws_dynamodb_table" "terraform_state_lock" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "Terraform State Lock"
Environment = "global"
}
}
2. Use Módulos para Reusabilidade
Não duplique código! Crie módulos reutilizáveis.
Exemplo de Módulo de VPC
# modules/networking/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(
var.common_tags,
{
Name = "${var.project_name}-vpc"
}
)
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(
var.common_tags,
{
Name = "${var.project_name}-public-${count.index + 1}"
Type = "public"
}
)
}
Uso do Módulo
# environments/prod/main.tf
module "networking" {
source = "../../modules/networking"
project_name = "myapp"
vpc_cidr = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
availability_zones = ["us-east-1a", "us-east-1b"]
common_tags = {
Environment = "production"
ManagedBy = "Terraform"
Team = "DevOps"
}
}
3. Use Variáveis e Outputs Corretamente
variables.tf
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
validation {
condition = can(regex("^t3\\.", var.instance_type))
error_message = "Instance type must be from t3 family."
}
}
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "common_tags" {
description = "Common tags to apply to all resources"
type = map(string)
default = {}
}
outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "db_endpoint" {
description = "RDS instance endpoint"
value = aws_db_instance.main.endpoint
sensitive = true # Não mostra no output
}
4. Gerencie Secrets com Segurança
NUNCA coloque secrets diretamente no código!
Use AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/database/password"
}
resource "aws_db_instance" "main" {
identifier = "myapp-db"
engine = "postgres"
instance_class = "db.t3.micro"
allocated_storage = 20
username = "admin"
password = jsondecode(data.aws_secretsmanager_secret_version.db_password.secret_string)["password"]
skip_final_snapshot = false
final_snapshot_identifier = "myapp-db-final-${formatdate("YYYY-MM-DD-hhmm", timestamp())}"
}
Use .tfvars para Valores Sensíveis
# terraform.tfvars (não commite este arquivo!)
db_password = "super-secret-password"
api_key = "your-api-key"
Adicione ao .gitignore:
# .gitignore
*.tfvars
.terraform/
.terraform.lock.hcl
terraform.tfstate*
5. Use Data Sources para Recursos Existentes
Não hardcode valores. Use data sources!
# Buscar AMI mais recente do Ubuntu
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = {
Name = "web-server"
}
}
6. Use Lifecycle Rules
Controle o comportamento do Terraform em situações específicas.
resource "aws_instance" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
lifecycle {
# Previne destruição acidental
prevent_destroy = true
# Cria novo recurso antes de destruir o antigo
create_before_destroy = true
# Ignora mudanças em tags específicas
ignore_changes = [
tags["LastUpdated"],
]
}
}
7. Use Workspaces para Ambientes
# Criar e usar workspaces
terraform workspace new dev
terraform workspace new prod
# Listar workspaces
terraform workspace list
# Selecionar workspace
terraform workspace select prod
No código:
locals {
environment = terraform.workspace
instance_counts = {
dev = 1
staging = 2
prod = 5
}
}
resource "aws_instance" "app" {
count = local.instance_counts[local.environment]
ami = data.aws_ami.ubuntu.id
instance_type = local.environment == "prod" ? "t3.large" : "t3.micro"
tags = {
Name = "app-${local.environment}-${count.index + 1}"
Environment = local.environment
}
}
8. Use Formatação e Validação
Comandos Essenciais
# Formatar código
terraform fmt -recursive
# Validar configuração
terraform validate
# Planejar mudanças
terraform plan -out=tfplan
# Aplicar plano salvo
terraform apply tfplan
Pre-commit Hook
# .pre-commit-config.yaml
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.77.1
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_docs
- id: terraform_tflint
9. Use Count e For_Each Sabiamente
Count - Para Recursos Idênticos
resource "aws_instance" "web" {
count = 3
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name = "web-${count.index + 1}"
}
}
For_Each - Para Recursos Variados
variable "users" {
type = map(object({
role = string
groups = list(string)
}))
default = {
"john" = {
role = "developer"
groups = ["dev", "qa"]
}
"jane" = {
role = "admin"
groups = ["admin", "dev"]
}
}
}
resource "aws_iam_user" "users" {
for_each = var.users
name = each.key
tags = {
Role = each.value.role
}
}
10. Documente Seu Código
Use comentários e terraform-docs:
# Create VPC with public and private subnets across multiple AZs
# This module follows AWS best practices for network segmentation
#
# Example usage:
# module "vpc" {
# source = "./modules/networking"
# vpc_cidr = "10.0.0.0/16"
# }
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "${var.project_name}-vpc"
}
}
Gere documentação automática:
terraform-docs markdown table . > README.md
Checklist Final
✅ Remote state configurado com locking
✅ Módulos para código reutilizável
✅ Variáveis com validação
✅ Secrets gerenciados com segurança
✅ Data sources ao invés de hardcoded values
✅ Lifecycle rules onde apropriado
✅ Workspaces para ambientes
✅ Código formatado e validado
✅ Documentação atualizada
✅ .gitignore configurado
Conclusão
Seguir essas práticas vai te ajudar a:
- Manter código mais limpo e manutenível
- Evitar erros comuns
- Facilitar colaboração em equipe
- Aumentar segurança
- Reduzir tempo de troubleshooting
Terraform é poderoso, mas com grandes poderes vêm grandes responsabilidades. Código de infraestrutura mal escrito pode causar desastres em produção. Invista tempo em fazer certo desde o início!