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

A diferença central em relação à AWS é que na Azure todo recurso precisa pertencer a um Resource Group, e a rede é declarada explicitamente (VNet → Subnet → Network Interface → VM).

Estrutura do projeto

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

Backend remoto (Azure Blob Storage)

# backend.tf
terraform {
  backend "azurerm" {
    resource_group_name  = "tfstate-rg"
    storage_account_name = "meuprojtfstate"
    container_name       = "tfstate"
    key                  = "app/terraform.tfstate"
  }
 
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}
 
provider "azurerm" {
  features {}
}

A autenticação usa o az login (CLI) em desenvolvimento ou uma Service Principal via variáveis de ambiente em CI/CD:

export ARM_CLIENT_ID="<app-id>"
export ARM_CLIENT_SECRET="<secret>"
export ARM_SUBSCRIPTION_ID="<subscription-id>"
export ARM_TENANT_ID="<tenant-id>"

Variáveis

# variables.tf
variable "localizacao" {
  type    = string
  default = "brazilsouth"
}
 
variable "ambiente" {
  type = string
}
 
variable "vm_size" {
  type = string
}
 
variable "projeto" {
  type    = string
  default = "meu-projeto"
}
# dev.tfvars
ambiente     = "dev"
vm_size      = "Standard_B1s"
 
# prod.tfvars
ambiente     = "prod"
vm_size      = "Standard_B2s"

Infraestrutura: VM Linux com NGINX

# main.tf
locals {
  nome = "${var.projeto}-${var.ambiente}"
  tags = {
    Projeto   = var.projeto
    Ambiente  = var.ambiente
    ManagedBy = "terraform"
  }
}
 
# Resource Group, obrigatório na Azure
resource "azurerm_resource_group" "app" {
  name     = "${local.nome}-rg"
  location = var.localizacao
  tags     = local.tags
}
 
# Rede virtual
resource "azurerm_virtual_network" "app" {
  name                = "${local.nome}-vnet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.app.location
  resource_group_name = azurerm_resource_group.app.name
  tags                = local.tags
}
 
resource "azurerm_subnet" "app" {
  name                 = "${local.nome}-subnet"
  resource_group_name  = azurerm_resource_group.app.name
  virtual_network_name = azurerm_virtual_network.app.name
  address_prefixes     = ["10.0.1.0/24"]
}
 
# IP público
resource "azurerm_public_ip" "app" {
  name                = "${local.nome}-pip"
  location            = azurerm_resource_group.app.location
  resource_group_name = azurerm_resource_group.app.name
  allocation_method   = "Static"
  tags                = local.tags
}
 
# Network Security Group, equivalente ao Security Group da AWS
resource "azurerm_network_security_group" "app" {
  name                = "${local.nome}-nsg"
  location            = azurerm_resource_group.app.location
  resource_group_name = azurerm_resource_group.app.name
  tags                = local.tags
 
  security_rule {
    name                       = "HTTP"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
 
  security_rule {
    name                       = "SSH"
    priority                   = 101
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}
 
# Network Interface
resource "azurerm_network_interface" "app" {
  name                = "${local.nome}-nic"
  location            = azurerm_resource_group.app.location
  resource_group_name = azurerm_resource_group.app.name
  tags                = local.tags
 
  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.app.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.app.id
  }
}
 
resource "azurerm_network_interface_security_group_association" "app" {
  network_interface_id      = azurerm_network_interface.app.id
  network_security_group_id = azurerm_network_security_group.app.id
}
 
# VM Linux
resource "azurerm_linux_virtual_machine" "app" {
  name                = "${local.nome}-vm"
  location            = azurerm_resource_group.app.location
  resource_group_name = azurerm_resource_group.app.name
  size                = var.vm_size
  admin_username      = "adminuser"
  tags                = local.tags
 
  network_interface_ids = [azurerm_network_interface.app.id]
 
  admin_ssh_key {
    username   = "adminuser"
    public_key = file("~/.ssh/id_rsa.pub")
  }
 
  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }
 
  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts"
    version   = "latest"
  }
 
  custom_data = base64encode(<<-EOF
    #!/bin/bash
    apt-get update -y
    apt-get install -y nginx
    systemctl enable nginx
    systemctl start nginx
    echo "<h1>${local.nome}</h1>" > /var/www/html/index.html
  EOF
  )
}
# outputs.tf
output "ip_publico" {
  value = azurerm_public_ip.app.ip_address
}
 
output "url" {
  value = "http://${azurerm_public_ip.app.ip_address}"
}

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
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"

Diferenças em relação à AWS

ConceitoAWSAzure
Agrupamento lógicoTags / AccountsResource Group (obrigatório)
Backend de stateS3 + DynamoDBAzure Blob Storage
VMaws_instanceazurerm_linux_virtual_machine
FirewallSecurity Group (inline)NSG + associação separada
ImagemAMI (data source)source_image_reference inline

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