Happy Dussehra! May good always win. Happy Diwali! Wishing you a bright year ahead. Merry Christmas! Warm wishes from Vizag. Happy New Year! Here’s to a great year ahead.

Archive · Azure

Azure Setup: Terraform azurerm and Bicep

Two ways to describe Azure infrastructure as code, with remote state, a reviewed pipeline, what-if checks and the Terraform licence change.

4 min read

  • Terraform 1.7
  • azurerm 3.x
  • Bicep
  • OpenTofu 1.6
  • GitHub Actions

Clicking in the portal does not scale and cannot be reviewed. Infrastructure as code (IaC) puts the definition in Git, so changes are reviewed, repeatable and reversible. Two common choices on Azure are Terraform with the azurerm provider and Bicep, Azure’s native language.

Versions in use on this date. Terraform 1.7 was released on 17 January 2024 (adding mock providers for terraform test, the removed block and for_each on import blocks). The azurerm provider is in its 3.x series. HashiCorp moved Terraform to the Business Source License in August 2023, and the community fork OpenTofu released 1.6, its first stable version, on 10 January 2024 as a drop-in replacement for Terraform 1.5.x.

Architecture of a reviewed IaC workflow

flowchart TB
  DEV[Engineer] -->|pull request| GIT[Git repository]
  GIT --> CI[CI pipeline<br/>fmt validate plan or what-if]
  CI -->|plan as PR comment| REV[Reviewer approves]
  REV --> APPLY[Apply job<br/>on merge to main]
  APPLY -->|OIDC token| ENTRA[Entra ID app<br/>federated credential]
  ENTRA --> ARM[Azure Resource Manager]
  APPLY <-->|state and lock| ST[Storage account<br/>blob container tfstate]
  ARM --> RES[Resource groups and resources]
  POL[Azure Policy] -.->|guardrails| ARM

Terraform with the azurerm provider

Keep state in an Azure Storage account with locking (the backend uses a blob lease), created once outside Terraform:

az group create -n rg-tfstate -l centralindia
az storage account create -n sttfstate2024ci -g rg-tfstate -l centralindia --sku Standard_GRS \
  --min-tls-version TLS1_2 --allow-blob-public-access false
az storage container create -n tfstate --account-name sttfstate2024ci --auth-mode login

main.tf:

terraform {
  required_version = ">= 1.7"
  required_providers {
    azurerm = { source = "hashicorp/azurerm", version = "~> 3.90" }
  }
  backend "azurerm" {
    resource_group_name  = "rg-tfstate"
    storage_account_name = "sttfstate2024ci"
    container_name       = "tfstate"
    key                  = "demo.tfstate"
    use_azuread_auth     = true
  }
}
provider "azurerm" { features {} }

variable "location" { default = "centralindia" }
variable "tags" {
  default = { owner = "raviteja", env = "demo" }
}

resource "azurerm_resource_group" "demo" {
  name     = "rg-demo-iac"
  location = var.location
  tags     = var.tags
}
resource "azurerm_storage_account" "sa" {
  name                            = "stdemoiac2024"
  resource_group_name             = azurerm_resource_group.demo.name
  location                        = azurerm_resource_group.demo.location
  account_tier                    = "Standard"
  account_replication_type        = "LRS"
  min_tls_version                 = "TLS1_2"
  allow_nested_items_to_be_public = false
  tags                            = var.tags
}
az login
terraform init
terraform fmt -check && terraform validate
terraform plan -out tfplan
terraform apply tfplan

Always apply a saved plan, so what was reviewed is exactly what runs. State files can contain secrets, so restrict access to the storage account. Use separate state keys (or separate backends) per environment instead of one shared file, and pin provider versions with the lock file committed to Git.

Bicep

Bicep compiles to ARM templates and needs no state file, because Azure itself holds the deployed state. Storage account names must be globally unique, 3 to 24 lowercase letters and digits.

param location string = resourceGroup().location
param name string = 'stdemobicep2024'

resource sa 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: name
  location: location
  sku: { name: 'Standard_LRS' }
  kind: 'StorageV2'
  properties: {
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false
  }
}
output id string = sa.id
az group create -n rg-demo-iac -l centralindia
az deployment group what-if -g rg-demo-iac -f main.bicep
az deployment group create -g rg-demo-iac -f main.bicep

what-if shows creates, changes and deletes before anything happens. Split reusable pieces into modules, and pass values with parameter files.

Pipeline identity

The pipeline signs in with an Entra ID application that has a federated credential for the repository and branch (see the previous post), so there is no client secret to store. Give that identity Contributor on the target resource group only, plus Storage Blob Data Contributor on the state container.

Which one?

Terraform / OpenTofu Bicep
Clouds Many providers, not only Azure Azure only
State A state file you must store and protect None; Azure holds state
New Azure features After provider releases (some delay) Available on day one
Preview terraform plan what-if
Licence Terraform: Business Source License; OpenTofu: MPL 2.0 Open source (MIT)

Choose Bicep for Azure-only teams that want no state to manage, and Terraform or OpenTofu where several clouds or existing Terraform modules are in play. On the licence change: for internal use, the Business Source License generally does not restrict you, but it does restrict building competing commercial products, and some teams prefer OpenTofu for that reason. Review it against your own situation before choosing.