Terraform and OpenTofu on Azure: Getting Started with the AzureRM Provider
Azure with Terraform in four steps #
The same workflow that you use for AWS works for Microsoft Azure: only the provider and the resource names change. This tutorial creates a resource group, a virtual network with a subnet and a storage account, and stores the state in Azure.
1. Authentication #
Install the Azure CLI and sign in:
$ az login
$ az account list --output table
$ az account set --subscription "<subscription-id>"
The azurerm provider uses that session for local development. In CI/CD use a service principal or, better, workload identity federation with OIDC, which is the equivalent of GitHub Actions with AWS OIDC, so there are no stored secrets.
2. Provider configuration #
terraform {
required_version = ">= 1.6"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}
variable "subscription_id" {
type = string
}
variable "location" {
type = string
default = "westeurope"
}Since version 4 of the provider the subscription_id is required. The empty features {} block is mandatory.
3. Resources #
Everything in Azure lives inside a resource group:
resource "azurerm_resource_group" "main" {
name = "rg-ditwl-demo"
location = var.location
tags = {
environment = "demo"
managed_by = "opentofu"
}
}
resource "azurerm_virtual_network" "main" {
name = "vnet-ditwl-demo"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
address_space = ["10.0.0.0/16"]
}
resource "azurerm_subnet" "private" {
name = "snet-private"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.1.0/24"]
}
resource "random_string" "suffix" {
length = 6
upper = false
special = false
}
resource "azurerm_storage_account" "main" {
name = "stditwl${random_string.suffix.result}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "LRS"
min_tls_version = "TLS1_2"
allow_nested_items_to_be_public = false
}Storage account names must be globally unique, lowercase, letters and numbers only, up to 24 characters, hence the random suffix.
4. Run it #
$ tofu init
$ tofu plan -var subscription_id=<subscription-id>
$ tofu apply -var subscription_id=<subscription-id>
Delete everything with tofu destroy, or by deleting the resource group.
Compare with AWS #
| AWS | Azure |
|---|---|
| Account / Organizations | Subscription / Management groups |
| VPC | Virtual network (VNet) |
| Security group | Network security group (NSG) |
| EC2 | Virtual machine |
| S3 | Storage account (Blob) |
| IAM role | Managed identity and RBAC role assignment |
| KMS | Key Vault |
| EKS | AKS |
Remote state in Azure Storage #
Create a storage account and a container for the state, then configure the backend:
terraform {
backend "azurerm" {
resource_group_name = "rg-tfstate"
storage_account_name = "sttfstateditwl"
container_name = "tfstate"
key = "demo.terraform.tfstate"
use_azuread_auth = true
}
}Blob leases provide locking. See backends.
Next steps #
Variables and outputs, modules, and for Kubernetes see Terraform and EKS (the concepts are the same for AKS). Check the Azure Verified Modules for production-ready modules. Compare with Google Cloud.