DevOps & CI/CD

O que Todo Dev Deve Saber sobre Terraform Avançado: Módulos, Workspaces e Remote State

16 min de leitura

O que Todo Dev Deve Saber sobre Terraform Avançado: Módulos, Workspaces e Remote State

Entendendo Módulos no Terraform Um módulo no Terraform é um diretório contendo um conjunto de arquivos de configuração que funcionam juntos. Ele encapsula lógica de infraestrutura reutilizável, permitindo que você organize código complexo em componentes menores e gerenciáveis. A verdadeira potência dos módulos está na abstração: você define a interface (inputs e outputs) e a implementação fica encapsulada, possibilitando reutilização sem duplicação de código. A estrutura básica de um módulo segue um padrão simples. Imagine que você quer criar um módulo para provisionar uma instância EC2 com segurança básica. Você criará um diretório chamado com arquivos de configuração. O módulo precisa expor inputs via variáveis e outputs para que o código chamador acesse informações importantes, como o ID da instância ou o IP público. Estrutura e Organização de Módulos Exemplo Prático: Módulo EC2 Vamos criar um módulo reutilizável para provisionar uma instância EC2. Primeiro, o arquivo de variáveis do módulo ( ): Agora a implementação ( ): E os outputs

<h2>Entendendo Módulos no Terraform</h2>

<p>Um módulo no Terraform é um diretório contendo um conjunto de arquivos de configuração que funcionam juntos. Ele encapsula lógica de infraestrutura reutilizável, permitindo que você organize código complexo em componentes menores e gerenciáveis. A verdadeira potência dos módulos está na abstração: você define a interface (inputs e outputs) e a implementação fica encapsulada, possibilitando reutilização sem duplicação de código.</p>

<p>A estrutura básica de um módulo segue um padrão simples. Imagine que você quer criar um módulo para provisionar uma instância EC2 com segurança básica. Você criará um diretório chamado <code>modules/ec2</code> com arquivos de configuração. O módulo precisa expor inputs via variáveis e outputs para que o código chamador acesse informações importantes, como o ID da instância ou o IP público.</p>

<h3>Estrutura e Organização de Módulos</h3>

<pre><code>projeto-terraform/

├── main.tf

├── variables.tf

├── outputs.tf

├── terraform.tfvars

└── modules/

└── ec2_instance/

├── main.tf

├── variables.tf

└── outputs.tf</code></pre>

<h3>Exemplo Prático: Módulo EC2</h3>

<p>Vamos criar um módulo reutilizável para provisionar uma instância EC2. Primeiro, o arquivo de variáveis do módulo (<code>modules/ec2_instance/variables.tf</code>):</p>

<pre><code class="language-hcl">variable &quot;instance_name&quot; {

description = &quot;Nome da instância EC2&quot;

type = string

}

variable &quot;instance_type&quot; {

description = &quot;Tipo da instância&quot;

type = string

default = &quot;t3.micro&quot;

}

variable &quot;ami_id&quot; {

description = &quot;ID da AMI a usar&quot;

type = string

}

variable &quot;security_group_id&quot; {

description = &quot;ID do grupo de segurança&quot;

type = string

}

variable &quot;tags&quot; {

description = &quot;Tags para a instância&quot;

type = map(string)

default = {}

}</code></pre>

<p>Agora a implementação (<code>modules/ec2_instance/main.tf</code>):</p>

<pre><code class="language-hcl">resource &quot;aws_instance&quot; &quot;this&quot; {

ami = var.ami_id

instance_type = var.instance_type

vpc_security_group_ids = [var.security_group_id]

tags = merge(

var.tags,

{

Name = var.instance_name

}

)

}</code></pre>

<p>E os outputs do módulo (<code>modules/ec2_instance/outputs.tf</code>):</p>

<pre><code class="language-hcl">output &quot;instance_id&quot; {

description = &quot;ID da instância&quot;

value = aws_instance.this.id

}

output &quot;public_ip&quot; {

description = &quot;IP público da instância&quot;

value = aws_instance.this.public_ip

}

output &quot;private_ip&quot; {

description = &quot;IP privado da instância&quot;

value = aws_instance.this.private_ip

}</code></pre>

<p>Agora, para usar este módulo na configuração principal (<code>main.tf</code>):</p>

<pre><code class="language-hcl">module &quot;app_server&quot; {

source = &quot;./modules/ec2_instance&quot;

instance_name = &quot;servidor-aplicacao&quot;

instance_type = &quot;t3.small&quot;

ami_id = &quot;ami-0c55b159cbfafe1f0&quot;

security_group_id = aws_security_group.app.id

tags = {

Environment = &quot;production&quot;

Project = &quot;meu-projeto&quot;

}

}

output &quot;app_server_ip&quot; {

value = module.app_server.public_ip

}</code></pre>

<p>A vantagem aqui é clara: você pode chamar o módulo múltiplas vezes com diferentes parâmetros, e toda a lógica está centralizada. Se precisar ajustar como as instâncias são criadas, faz apenas uma vez no módulo.</p>

<p>---</p>

<h2>Workspaces: Gerenciando Múltiplos Ambientes</h2>

<p>Workspaces no Terraform permitem manter múltiplos estados separados dentro da mesma configuração. Eles são úteis quando você precisa de ambientes distintos (desenvolvimento, staging, produção) usando a mesma lógica de código. Sem workspaces, você precisaria duplicar toda a configuração ou usar variáveis complexas para diferenciar ambientes — workspaces oferecem uma solução elegante.</p>

<p>Cada workspace tem seu próprio arquivo de estado (<code>.tfstate</code>), isolando completamente a infraestrutura de um ambiente. Quando você alterna entre workspaces, o Terraform carrega o estado correspondente, aplicando as mudanças apenas àquele ambiente. Isso é particularmente poderoso porque sua configuração permanece idêntica; apenas os valores das variáveis mudam por ambiente.</p>

<h3>Funcionamento Básico de Workspaces</h3>

<p>Por padrão, o Terraform começa com um workspace chamado <code>default</code>. Você pode criar novos workspaces e alternar entre eles. Vamos criar um exemplo prático onde deployamos a mesma infraestrutura em diferentes ambientes.</p>

<pre><code class="language-hcl"># variables.tf

variable &quot;environment&quot; {

description = &quot;Ambiente de deployment&quot;

type = string

default = terraform.workspace

}

variable &quot;instance_count&quot; {

description = &quot;Número de instâncias por ambiente&quot;

type = number

default = (

terraform.workspace == &quot;production&quot; ? 3 :

terraform.workspace == &quot;staging&quot; ? 2 : 1

)

}

variable &quot;instance_type&quot; {

description = &quot;Tipo de instância por ambiente&quot;

type = string

default = (

terraform.workspace == &quot;production&quot; ? &quot;t3.large&quot; :

terraform.workspace == &quot;staging&quot; ? &quot;t3.medium&quot; : &quot;t3.micro&quot;

)

}</code></pre>

<pre><code class="language-hcl"># main.tf

resource &quot;aws_instance&quot; &quot;web&quot; {

count = var.instance_count

ami = &quot;ami-0c55b159cbfafe1f0&quot;

instance_type = var.instance_type

tags = {

Name = &quot;web-server-${terraform.workspace}-${count.index + 1}&quot;

Environment = var.environment

}

}</code></pre>

<p>Para usar workspaces na prática:</p>

<pre><code class="language-bash"># Listar workspaces existentes

terraform workspace list

Criar novo workspace

terraform workspace new production

terraform workspace new staging

Alternar para um workspace

terraform workspace select production

Verificar workspace atual

terraform workspace show

Aplicar configuração no workspace atual

terraform apply

Voltar para default

terraform workspace select default</code></pre>

<p>Observe que o estado é mantido separado. Quando você faz <code>terraform apply</code> no workspace <code>production</code>, o arquivo de estado é específico daquele workspace (geralmente armazenado como <code>terraform.tfstate.d/production/terraform.tfstate</code>). Isso significa que você pode ter 3 instâncias em produção, 2 em staging e 1 em desenvolvimento, tudo gerenciado pelo mesmo código.</p>

<p>---</p>

<h2>Remote State: Compartilhando Estado entre Equipes</h2>

<p>Remote state resolve o maior problema do Terraform em equipes: o estado não fica mais em um arquivo local. Quando múltiplas pessoas trabalham no mesmo projeto, alguém precisa gerenciar a versão &quot;correta&quot; do estado. Remote state armazena esse estado em um backend centralizado (como AWS S3, Terraform Cloud, ou Azure Storage), permitindo que toda a equipe trabalhe sobre a mesma versão.</p>

<p>Além de compartilhamento, remote state oferece locking automático — quando alguém está aplicando mudanças, outros são impedidos de fazer o mesmo simultaneamente, evitando corrupção do estado. Também fornece melhor segurança: você pode usar encryption em repouso e em trânsito, além de controlar acesso via políticas de permissão.</p>

<h3>Configurando Remote State com AWS S3</h3>

<p>O backend mais comum é AWS S3 com DynamoDB para locking. Você precisa criar esses recursos previamente (uma única vez):</p>

<pre><code class="language-hcl"># Arquivo separado: backends.tf (criar manualmente na AWS ou via outro Terraform)

resource &quot;aws_s3_bucket&quot; &quot;terraform_state&quot; {

bucket = &quot;meu-projeto-terraform-state&quot;

}

resource &quot;aws_s3_bucket_versioning&quot; &quot;terraform_state&quot; {

bucket = aws_s3_bucket.terraform_state.id

versioning_configuration {

status = &quot;Enabled&quot;

}

}

resource &quot;aws_s3_bucket_server_side_encryption_configuration&quot; &quot;terraform_state&quot; {

bucket = aws_s3_bucket.terraform_state.id

rule {

apply_server_side_encryption_by_default {

sse_algorithm = &quot;AES256&quot;

}

}

}

resource &quot;aws_dynamodb_table&quot; &quot;terraform_locks&quot; {

name = &quot;terraform-locks&quot;

billing_mode = &quot;PAY_PER_REQUEST&quot;

hash_key = &quot;LockID&quot;

attribute {

name = &quot;LockID&quot;

type = &quot;S&quot;

}

}</code></pre>

<p>Após criar S3 e DynamoDB, configure o backend no seu projeto Terraform (<code>backend.tf</code>):</p>

<pre><code class="language-hcl">terraform {

backend &quot;s3&quot; {

bucket = &quot;meu-projeto-terraform-state&quot;

key = &quot;prod/terraform.tfstate&quot;

region = &quot;us-east-1&quot;

dynamodb_table = &quot;terraform-locks&quot;

encrypt = true

}

}</code></pre>

<p>Quando você executa <code>terraform init</code> com essa configuração, o Terraform migra o estado local para S3:</p>

<pre><code class="language-bash">terraform init

Output: Backend has been successfully initialized!</code></pre>

<h3>Integrando Remote State com Workspaces</h3>

<p>Remote state funciona perfeitamente com workspaces. Você pode organizar o estado por ambiente usando diferentes chaves S3:</p>

<pre><code class="language-hcl"># backend.tf com dynamic path

terraform {

backend &quot;s3&quot; {

bucket = &quot;meu-projeto-terraform-state&quot;

key = &quot;${terraform.workspace}/terraform.tfstate&quot; # NOTA: isso não funciona literalmente

region = &quot;us-east-1&quot;

dynamodb_table = &quot;terraform-locks&quot;

encrypt = true

}

}</code></pre>

<p>Na realidade, você precisa usar <code>-backend-config</code> durante o init ou arquivo separado de configuração:</p>

<pre><code class="language-bash"># Inicializar com configuração dinâmica

terraform init -backend-config=&quot;key=dev/terraform.tfstate&quot; -backend-config=&quot;bucket=meu-projeto-terraform-state&quot;</code></pre>

<p>Ou use arquivo de configuração backend por workspace:</p>

<pre><code class="language-hcl"># backend-dev.tf

terraform {

backend &quot;s3&quot; {

bucket = &quot;meu-projeto-terraform-state&quot;

key = &quot;dev/terraform.tfstate&quot;

region = &quot;us-east-1&quot;

dynamodb_table = &quot;terraform-locks&quot;

encrypt = true

}

}</code></pre>

<h3>Acessando Outputs de Remote State em Outro Projeto</h3>

<p>Um caso de uso poderoso é compartilhar outputs entre projetos Terraform diferentes via remote state:</p>

<pre><code class="language-hcl"># Em outro projeto que precisa do VPC ID criado em outro projeto

data &quot;terraform_remote_state&quot; &quot;networking&quot; {

backend = &quot;s3&quot;

config = {

bucket = &quot;meu-projeto-terraform-state&quot;

key = &quot;networking/terraform.tfstate&quot;

region = &quot;us-east-1&quot;

dynamodb_table = &quot;terraform-locks&quot;

}

}

resource &quot;aws_security_group&quot; &quot;app&quot; {

name = &quot;app-sg&quot;

vpc_id = data.terraform_remote_state.networking.outputs.vpc_id

ingress {

from_port = 80

to_port = 80

protocol = &quot;tcp&quot;

cidr_blocks = [&quot;0.0.0.0/0&quot;]

}

}</code></pre>

<p>---</p>

<h2>Padrão Integrado: Módulos + Workspaces + Remote State</h2>

<p>A verdadeira força emerge quando você combina esses três conceitos. Vamos construir uma arquitetura realista com todos os três elementos trabalhando juntos.</p>

<h3>Estrutura do Projeto Integrado</h3>

<pre><code>infraestrutura-producao/

├── terraform.tf

├── backend.tf

├── variables.tf

├── main.tf

├── terraform.tfvars

├── environments/

│ ├── dev.tfvars

│ ├── staging.tfvars

│ └── prod.tfvars

└── modules/

├── vpc/

│ ├── main.tf

│ ├── variables.tf

│ └── outputs.tf

├── rds/

│ ├── main.tf

│ ├── variables.tf

│ └── outputs.tf

└── app_server/

├── main.tf

├── variables.tf

└── outputs.tf</code></pre>

<h3>Backend com Isolamento por Workspace</h3>

<pre><code class="language-hcl"># backend.tf

terraform {

required_version = &quot;&gt;= 1.0&quot;

backend &quot;s3&quot; {

bucket = &quot;meu-projeto-terraform-state&quot;

region = &quot;us-east-1&quot;

dynamodb_table = &quot;terraform-locks&quot;

encrypt = true

}

}

Note: a chave será configurada por workspace dinamicamente</code></pre>

<p>Inicializar para cada workspace com chaves diferentes:</p>

<pre><code class="language-bash"># Development

terraform workspace new dev

terraform init -backend-config=&quot;key=dev/terraform.tfstate&quot;

Staging

terraform workspace new staging

terraform init -backend-config=&quot;key=staging/terraform.tfstate&quot;

Production

terraform workspace new prod

terraform init -backend-config=&quot;key=prod/terraform.tfstate&quot;</code></pre>

<h3>Configuração Principal com Módulos</h3>

<pre><code class="language-hcl"># variables.tf

variable &quot;aws_region&quot; {

type = string

}

variable &quot;vpc_cidr&quot; {

type = string

}

variable &quot;database_allocated_storage&quot; {

type = number

}

variable &quot;app_instance_count&quot; {

type = number

}

main.tf

terraform {

required_providers {

aws = {

source = &quot;hashicorp/aws&quot;

version = &quot;~&gt; 5.0&quot;

}

}

}

provider &quot;aws&quot; {

region = var.aws_region

}

module &quot;vpc&quot; {

source = &quot;./modules/vpc&quot;

cidr_block = var.vpc_cidr

environment = terraform.workspace

}

module &quot;rds&quot; {

source = &quot;./modules/rds&quot;

db_subnet_group_name = module.vpc.db_subnet_group_name

allocated_storage = var.database_allocated_storage

environment = terraform.workspace

}

module &quot;app_servers&quot; {

source = &quot;./modules/app_server&quot;

count = var.app_instance_count

vpc_id = module.vpc.vpc_id

subnet_id = module.vpc.public_subnets[count.index % length(module.vpc.public_subnets)]

database_endpoint = module.rds.endpoint

environment = terraform.workspace

}

output &quot;database_endpoint&quot; {

value = module.rds.endpoint

}

output &quot;app_server_ips&quot; {

value = [for server in module.app_servers : server.public_ip]

}</code></pre>

<h3>Arquivos de Variáveis por Ambiente</h3>

<pre><code class="language-hcl"># environments/dev.tfvars

aws_region = &quot;us-east-1&quot;

vpc_cidr = &quot;10.0.0.0/16&quot;

database_allocated_storage = 20

app_instance_count = 1</code></pre>

<pre><code class="language-hcl"># environments/prod.tfvars

aws_region = &quot;us-east-1&quot;

vpc_cidr = &quot;10.0.0.0/16&quot;

database_allocated_storage = 100

app_instance_count = 3</code></pre>

<h3>Executando com Tudo Integrado</h3>

<pre><code class="language-bash"># Alternar para workspace de produção

terraform workspace select prod

Aplicar com arquivo de variáveis específico

terraform apply -var-file=&quot;environments/prod.tfvars&quot;

Para desenvolvimento

terraform workspace select dev

terraform apply -var-file=&quot;environments/dev.tfvars&quot;

Verificar qual workspace está ativo

terraform workspace show</code></pre>

<p>---</p>

<h2>Conclusão</h2>

<p>Você aprendeu que <strong>módulos encapsulam lógica reutilizável</strong>, permitindo organizar código complexo em componentes gerenciáveis que definem uma interface clara via inputs e outputs. Isso elimina duplicação e facilita manutenção centralizada da infraestrutura.</p>

<p><strong>Workspaces isolam estados completamente</strong>, permitindo múltiplos ambientes (dev, staging, prod) usando o mesmo código com valores diferentes. Cada workspace mantém seu próprio estado separado, e variáveis condicionais adaptam a infraestrutura conforme o ambiente selecionado.</p>

<p><strong>Remote state compartilha o estado entre equipes de forma segura</strong>, armazenando em backend centralizado (S3 + DynamoDB) com encryption e locking automático. Isso previne conflitos simultâneos e oferece auditoria de mudanças via versionamento. A integração com remote state data sources permite compartilhar outputs entre projetos diferentes, criando dependências limpas entre stacks de infraestrutura.</p>

<p>---</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.terraform.io/language/modules" target="_blank" rel="noopener noreferrer">Terraform Modules - Documentação Oficial</a></li>

<li><a href="https://www.terraform.io/language/state" target="_blank" rel="noopener noreferrer">State Management with Terraform - Hashicorp</a></li>

<li><a href="https://www.terraform.io/cloud-docs/workspaces" target="_blank" rel="noopener noreferrer">Terraform Workspaces - Official Documentation</a></li>

<li><a href="https://www.terraform.io/language/settings/backends/s3" target="_blank" rel="noopener noreferrer">AWS S3 Backend Configuration - Terraform</a></li>

<li><a href="https://www.terraformbestpractices.com/" target="_blank" rel="noopener noreferrer">Terraform Best Practices - Yevgeniy Brikman</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em DevOps & CI/CD

Pulumi: Infraestrutura como Código com Linguagens Reais: Do Básico ao Avançado
Pulumi: Infraestrutura como Código com Linguagens Reais: Do Básico ao Avançado

O Que é Pulumi e Por Que Abandona a Sintaxe Declarativa Pulumi é uma platafor...

Como Usar Monorepos com Git: Subtrees, Submodules e Ferramentas como Nx em Produção
Como Usar Monorepos com Git: Subtrees, Submodules e Ferramentas como Nx em Produção

Entendendo Monorepos: O Problema que Você Precisa Resolver Um monorepo é um r...

Boas Práticas de SAST e DAST em Pipelines: Trivy, Snyk, SonarQube e OWASP ZAP para Times Ágeis
Boas Práticas de SAST e DAST em Pipelines: Trivy, Snyk, SonarQube e OWASP ZAP para Times Ágeis

SAST e DAST: Fundamentos e Diferenças A segurança de aplicações é um dos pila...