Exemplo prático de um serviço web simples na AWS provisionado com Terraform, com promoção do ambiente de desenvolvimento para produção usando workspaces.

Estrutura do projeto

infra/
├── backend.tf
├── main.tf
├── variables.tf
├── outputs.tf
├── dev.tfvars
└── prod.tfvars

Backend remoto (S3 + DynamoDB)

# backend.tf
terraform {
  backend "s3" {
    bucket         = "meu-projeto-tfstate"
    key            = "app/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
 
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
 
provider "aws" {
  region = var.regiao
}

Variáveis

# variables.tf
variable "regiao" {
  type    = string
  default = "us-east-1"
}
 
variable "ambiente" {
  type = string
}
 
variable "instance_type" {
  type = string
}
 
variable "projeto" {
  type    = string
  default = "meu-projeto"
}
# dev.tfvars
ambiente      = "dev"
instance_type = "t3.micro"
 
# prod.tfvars
ambiente      = "prod"
instance_type = "t3.medium"

Infraestrutura: EC2 com NGINX

# main.tf
locals {
  nome = "${var.projeto}-${var.ambiente}"
  tags = {
    Projeto   = var.projeto
    Ambiente  = var.ambiente
    ManagedBy = "terraform"
  }
}
 
resource "aws_security_group" "app" {
  name        = "${local.nome}-sg"
  description = "SG da aplicacao ${local.nome}"
  tags        = local.tags
 
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
 
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
 
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
 
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]
 
  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}
 
resource "aws_instance" "app" {
  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = var.instance_type
  vpc_security_group_ids = [aws_security_group.app.id]
 
  user_data = <<-EOF
    #!/bin/bash
    dnf install -y nginx
    systemctl enable nginx
    systemctl start nginx
    echo "<h1>${local.nome}</h1>" > /usr/share/nginx/html/index.html
  EOF
 
  tags = merge(local.tags, { Name = local.nome })
}
# outputs.tf
output "instance_ip" {
  value = aws_instance.app.public_ip
}
 
output "url" {
  value = "http://${aws_instance.app.public_ip}"
}

Fluxo dev → prod

# 1. Subir ambiente de dev
terraform workspace new dev
terraform workspace select dev
terraform init
terraform plan  -var-file="dev.tfvars"
terraform apply -var-file="dev.tfvars"
 
# Valida no IP exibido pelo output "url"...
 
# 2. Promover para prod (mesmo código, tfvars diferente)
terraform workspace new prod
terraform workspace select prod
terraform plan  -var-file="prod.tfvars"
terraform apply -var-file="prod.tfvars"
 
# 3. Destruir dev após validação
terraform workspace select dev
terraform destroy -var-file="dev.tfvars"

Cada workspace mantém um state independente no mesmo backend S3, evitando que recursos de dev e prod se misturem. O key no backend fica como env:/dev/app/terraform.tfstate e env:/prod/app/terraform.tfstate automaticamente.

Ver também: terraform-state | terraform-variaveis | terraform-providers | terraform