# Terraform and OpenTofu on Azure: Getting Started with the AzureRM Provider

> Deploy your first Azure resources with Terraform or OpenTofu: Azure CLI login, resource group, virtual network, storage account and remote state.

- Source: https://www.itwonderlab.com/terraform-azure-getting-started/
- Published: 2026-06-15
- Updated: 2026-06-15
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## Azure with Terraform in four steps

The same workflow that you use for [AWS](https://www.itwonderlab.com/aws-terraform-tutorial-terraform-basics/) works for Microsoft Azure: only the [provider](https://www.itwonderlab.com/terraform-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](https://learn.microsoft.com/cli/azure/install-azure-cli) and sign in:

```shell
$ 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](https://www.itwonderlab.com/terraform-github-actions-aws-oidc/), so there are no stored secrets.

### 2. Provider configuration

```hcl title="providers.tf"
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**:

```hcl title="main.tf"
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

```shell
$ 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](https://www.itwonderlab.com/aws-organizations/) | Subscription / Management groups |
| [VPC](https://www.itwonderlab.com/aws-vpc/) | Virtual network (VNet) |
| [Security group](https://www.itwonderlab.com/aws-security-groups/) | Network security group (NSG) |
| [EC2](https://www.itwonderlab.com/aws-ec2/) | Virtual machine |
| [S3](https://www.itwonderlab.com/aws-s3/) | Storage account (Blob) |
| [IAM](https://www.itwonderlab.com/aws-iam/) role | Managed identity and RBAC role assignment |
| [KMS](https://www.itwonderlab.com/aws-kms/) | Key Vault |
| [EKS](https://www.itwonderlab.com/aws-eks/) | AKS |

### Remote state in Azure Storage

Create a storage account and a container for the state, then configure the backend:

```hcl title="backend.tf"
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](https://www.itwonderlab.com/terraform-backend/).

### Next steps

[Variables and outputs](https://www.itwonderlab.com/terraform-variables-outputs-locals/), [modules](https://www.itwonderlab.com/terraform-module/), and for Kubernetes see [Terraform and EKS](https://www.itwonderlab.com/terraform-eks/) (the concepts are the same for AKS). Check the [Azure Verified Modules](https://azure.github.io/Azure-Verified-Modules/) for production-ready modules. Compare with [Google Cloud](https://www.itwonderlab.com/terraform-gcp-getting-started/).
