Terraform no Azure com Terragrunt: menos repetição, mais previsibilidade
No Azure, criar recursos “na mão” pelo portal parece rápido no começo. Você abre o portal, cria uma VNET, sobe uma VM, ajusta uma NSG… e em…
Terraform no Azure com Terragrunt: menos repetição, mais previsibilidade
No Azure, criar recursos “na mão” pelo portal parece rápido no começo. Você abre o portal, cria uma VNET, sobe uma VM, ajusta uma NSG… e em poucos minutos tudo está “de pé”.
O problema aparece quando essa infraestrutura vira rotina.
Quando você precisa repetir o mesmo desenho em outro ambiente (dev/hml/prod), manter padrões, auditar “quem mudou o quê” e recriar tudo com segurança (especialmente em POCs que nascem e morrem o tempo todo), o portal começa a virar um gargalo. A cada ajuste feito “só aqui”, os ambientes inevitavelmente divergem. E, quando você percebe, a diferença entre dev e prod não é mais “configuração”: virou um conjunto de decisões implícitas que ninguém consegue explicar direito.
É aqui que Terraform muda o jogo. E, quando o projeto cresce, Terragrunt entra como uma camada que resolve um problema muito específico: reduzir repetição, organizar estados e padronizar ambientes sem você copiar e colar providers.tf, backend.tf, versions.tf em todo lugar.
Infraestrutura como código (de verdade)
A ideia continua simples: infraestrutura vira código.
Em vez de clicar no portal, você descreve o que precisa existir rede, AKS, Key Vault, ACR, VMs, discos e aplica isso de forma padronizada e rastreável.
O ganho real de IaC não é “criar mais rápido”. É:
- eliminar drift (a divergência silenciosa entre ambientes)
- dar previsibilidade (a mesma entrada gera o mesmo resultado)
- criar histórico confiável (git + revisão + rastreabilidade)
- reduzir risco em mudanças (plan antes do apply, revisão antes do merge)
O ponto é que, com o tempo, o desafio deixa de ser “escrever Terraform” e passa a ser “manter dezenas de stacks consistentes”. E é justamente aí que Terragrunt brilha.
Por que Terragrunt entra na conversa
Terragrunt não substitui Terraform. Ele organiza o uso do Terraform.
Na prática, ele resolve três dores comuns em ambientes Azure que crescem rápido:
- DRY de verdade: você para de repetir provider/backend/versions em toda stack.
- Estados bem organizados: um state por componente, com naming consistente.
- Orquestração por dependências: você aplica infraestrutura em ordem (e com segurança) usando outputs como contratos.
O resultado é uma estrutura que escala melhor quando você começa a ter fundação, plataforma e workloads evoluindo ao mesmo tempo.
Exemplo prático: DefectDojo e Dependency-Track (infra + banco)
Ao implantar DefectDojo e Dependency-Track, existe uma base mínima no Azure antes da aplicação em si:
- Resource Group
- Rede (VNET, subnets, NSGs)
- VM Linux dedicada para PostgreSQL
- Public IP / DNS (quando necessário)
- Storage
- AKS
Com Terraform + Terragrunt, isso vira um fluxo claro:
- Provisiona a infraestrutura base (rede, compute, storage, AKS).
- Entra o Ansible para instalar e configurar o PostgreSQL (extensões, parâmetros, acesso, bancos e usuários).
- A camada de aplicação vem depois (Helm, manifests, pipelines), consumindo outputs e secrets de forma padronizada.
A divisão de responsabilidades continua objetiva:
- Terraform: ciclo de vida da infraestrutura e estado.
- Terragrunt: organização, padronização, backends, inputs por ambiente e dependências.
- Ansible: configuração do sistema e do software de forma idempotente.
Organização do repositório: módulos e stacks (com Terragrunt)
Uma organização prática e escalável separa:
- o que é reutilizável (módulos Terraform)
- do que é específico de ambiente (stacks com Terragrunt)
A lógica lembra a separação entre “código reutilizável” e “orquestração”.
Estrutura base (exemplo)
infra/
├── catalog/
│ └── modules/
│ ├── azurerm_resource_group/
│ ├── azurerm_virtual_network/
│ ├── azurerm_subnet/
│ ├── azurerm_public_ip/
│ ├── azurerm_network_interface/
│ ├── azurerm_linux_virtual_machine/
│ ├── azurerm_managed_disk/
│ ├── azurerm_key_vault/
│ ├── azurerm_storage_account/
│ ├── azurerm_container_registry/
│ └── azurerm_kubernetes/
│
└── stacks/
└── nde/
├── root.hcl
├── env.hcl
├── dev/
│ ├── foundation/
│ │ └── network/
│ │ └── terragrunt.hcl
│ ├── platform/
│ │ └── keyvault/
│ │ └── terragrunt.hcl
│ └── workloads/
│ └── defectdojo/
│ ├── compute/
│ │ └── terragrunt.hcl
│ └── aks/
│ └── terragrunt.hcl
├── hml/
│ └── ...
└── prd/
└── ...
Como ler essa estrutura
catalog/modules/ Módulos Terraform reutilizáveis. Pequenos, com contrato claro (inputs/outputs), fáceis de versionar e reaproveitar.
stacks/ O “live” do Terragrunt: cada pasta representa uma unidade de execução com seu próprio terragrunt.hcl apontando para um módulo e definindo inputs, dependências e estado.
root.hcl / env.hcl Arquivos de “configuração base” que evitam repetição: backend, provider, tags, naming, subscription/tenant, padrões de rede e qualquer coisa compartilhada.
O coração do Terragrunt: herança e padrão de state
Uma prática que funciona muito bem é ter um root.hcl com:
- remote_state centralizado (Azure Storage)
- generate para provider e versões
- padrões comuns (tags, naming, features)
- convenções para o key do state baseado no caminho
Exemplo de root.hcl:
locals {
project = "nde"
}
remote_state {
backend = "azurerm"
config = {
resource_group_name = "rg-tfstate"
storage_account_name = "sttfstateprod001"
container_name = "tfstate"
key = "${path_relative_to_include()}/terraform.tfstate"
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "azurerm" {
features {}
}
EOF
}
generate "versions" {
path = "versions.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
terraform {
required_version = ">= 1.6.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 3.0.0"
}
}
}
EOF
}
E um env.hcl para variáveis do ambiente:
locals {
location = "eastus"
environment = "dev"
tags = {
env = "dev"
project = "nde"
}
}
Uma stack por componente (e estados pequenos)
No Terragrunt, cada pasta com terragrunt.hcl normalmente representa:
- um módulo Terraform
- um state isolado
- uma unidade que faz sentido evoluir e aplicar separadamente
Isso ajuda muito quando você precisa:
- reaplicar só um pedaço
- dar rollback com menor blast radius
- evoluir rede e compute em ritmos diferentes
- reduzir tempo de plan/apply
Dependências como contratos (sem acoplamento implícito)
Quando uma stack depende de outra, Terragrunt permite consumir outputs como contrato explícito.
Exemplo: o AKS depende da rede.
stacks/nde/dev/foundation/network/terragrunt.hcl:
include "root" {
path = find_in_parent_folders("root.hcl")
}
locals {
env = read_terragrunt_config(find_in_parent_folders("env.hcl")).locals
}
terraform {
source = "${get_repo_root()}//catalog/modules/azurerm_virtual_network"
}
inputs = {
location = local.env.location
tags = local.env.tags
}
stacks/nde/dev/workloads/defectdojo/aks/terragrunt.hcl:
include "root" {
path = find_in_parent_folders("root.hcl")
}
locals {
env = read_terragrunt_config(find_in_parent_folders("env.hcl")).locals
}
dependency "network" {
config_path = "../../../foundation/network"
}
terraform {
source = "${get_repo_root()}//catalog/modules/azurerm_kubernetes"
}
inputs = {
location = local.env.location
tags = local.env.tags
subnet_id = dependency.network.outputs.snet_aks_id
}
Isso evita o “estado mental” de lembrar IDs na mão, evita terraform_remote_state espalhado e deixa a ordem de execução explícita.
Executando por camadas com segurança
Com Terragrunt, você consegue aplicar por nível:
- foundation primeiro
- depois platform
- depois workloads
E consegue fazer isso com:
- terragrunt run-all plan
- terragrunt run-all apply
Cada componente com seu state, e as dependências guiando a ordem quando necessário.
Ambientes (dev / hml / prd) sem workspaces
O padrão mais previsível continua sendo:
- pastas separadas por ambiente
- mesmo código, inputs diferentes
- state isolado por ambiente
- backend padronizado e automático
Isso reduz muito o risco de “aplicar no lugar errado” e facilita auditoria e rollback.
O que foi configurado para DefectDojo e Dependency-Track
Para essas duas aplicações, normalmente precisamos de:
- bancos dedicados (ex.: defectdojo, dependencytrack)
- usuários dedicados para cada aplicação
- permissões corretas
- ajustes e acesso (ex.: pg_hba.conf, listen_addresses, parâmetros)
Com Terraform + Terragrunt, a infraestrutura que suporta isso vira padrão. Com Ansible, a configuração do PostgreSQL vira código versionado. Se amanhã precisar recriar tudo, o processo deixa de depender de “memória” e vira execução.
Ganhos na prática
Os ganhos mais claros ao organizar Terraform com Terragrunt foram:
- menos repetição de arquivos e configurações entre stacks
- estados menores, mais rápidos de planejar e aplicar
- padronização forte entre ambientes
- dependências explícitas via outputs (contratos claros)
- execução por camadas, com menos risco
- reuso real: mesmos módulos, inputs por ambiente
Resumo final
Terraform resolve o “o que” da infraestrutura. Terragrunt resolve o “como manter isso organizado” quando o ambiente cresce.
No caso de DefectDojo e Dependency-Track, essa combinação ajudou a criar uma base consistente no Azure rede, compute, storage e AKS e permitiu recriar ambientes em minutos, algo essencial em POCs e ciclos de teste frequentes. No fim, a infraestrutura fica mais previsível, o estado fica mais controlado e o repositório deixa de virar uma coleção de arquivos repetidos.
메타데이터
- post_id
- c87f5e4d4c65
- slug
- terraform-no-azure-com-terragrunt-menos-repetição-mais-previsibilidade-c87f5e4d4c65
- url
- https://making.ndd.tech/terraform-no-azure-com-terragrunt-menos-repeti%C3%A7%C3%A3o-mais-previsibilidade-c87f5e4d4c65
- canonical_url
- https://making.ndd.tech/terraform-no-azure-com-terragrunt-menos-repeti%C3%A7%C3%A3o-mais-previsibilidade-c87f5e4d4c65
- author_url
- https://medium.com/@wallacemaiadev
- status
- ok
- fetched_at
- 2026-06-10 08:17:25