TrademarkTrademark
Features
Documentation
  1. Learning Center
  2. Terraform State & Backends: The Complete Guide

Article · part of a guide

Using the AzureRM Backend Block in Terraform

Using the AzureRM backend makes it much easier to scale your Terraform usage.

Using the AzureRM Backend Block in Terraform

Key takeaways

  1. The Terraform azurerm backend stores state files in Azure Blob Storage, enabling team collaboration, state locking, and secure durable storage.
  2. A basic azurerm backend block specifies resource_group_name, storage_account_name, container_name, and key, requiring a pre-existing storage account and container.
  3. Authenticate without hardcoding credentials by using environment variables, the Azure CLI session from az login, or a Managed Identity with the Storage Blob Data Contributor role.
  4. Terraform forbids variables inside the backend block, but OpenTofu 1.8 introduced support for variables and local values, enabling dynamic per-environment backend configuration.

If you're using Terraform to manage Azure infrastructure, you'll want a remote backend instead of keeping state on your laptop. The azurerm backend block stores your Terraform state files in Azure Blob Storage. That gives your team a shared place to read and write state, with locking so two people don't apply at the same time.

The Terraform state file is a JSON file that records your deployed resources. It maps your configuration to the real resources in your Azure subscription. Storing that file remotely with the azurerm backend buys you a few things:

  • Collaboration: Several team members can work on the same infrastructure without overwriting each other's changes.
  • State Locking: Concurrent terraform apply operations are blocked, since they can corrupt the state file and cause resource conflicts. Azure Blob Storage's native leasing feature handles this locking automatically.
  • Security & Durability: State files can contain sensitive data, so they belong in an encrypted, highly available Azure storage account.

How Do You Configure a Basic AzureRM Backend Block?

To use the azurerm backend, you need an Azure Storage Account and a container inside it that already exist. It's good practice to create a dedicated storage account just for your Terraform state files.

Here's a basic azurerm backend block configuration:

terraform {
  backend "azurerm" {
    resource_group_name  = "terraform-state-rg"
    storage_account_name = "myterraformstateaccount"
    container_name       = "tfstate"
    key                  = "my-app.tfstate"
  }
}
  • resource_group_name: The name of the resource group where your storage account is located.
  • storage_account_name: The globally unique name of your Azure Storage Account.
  • container_name: The name of the blob container where the state file will be stored.
  • key: The name of the blob (the state file itself) within the container. You should use a unique key for each separate Terraform configuration.

Once you've added this block to your main Terraform configuration file, run terraform init. That command sets up the backend and asks if you want to migrate any existing local state to the remote location.


When Should You Use the AzureRM Backend?

You'll want the azurerm backend for any serious Terraform project.

  • Team Projects: When several developers work on the same infrastructure, the azurerm backend keeps everyone on the same, up-to-date state file, so nobody accidentally manages resources they shouldn't.
  • CI/CD Pipelines: A CI/CD pipeline needs a consistent place to read and write the state file. The azurerm backend gives tools like Azure DevOps, GitHub Actions, or Jenkins that place when they run Terraform.
  • Production Environments: For production infrastructure, don't skip it. Built-in state locking and durable storage help prevent downtime and keep production state intact.

How Do You Authenticate to the AzureRM Backend?

Never hardcode credentials like access keys directly in your configuration. Use one of the supported authentication methods instead:

Environment Variables: This is the most common approach. Terraform automatically uses credentials set in environment variables like ARM_CLIENT_ID, ARM_CLIENT_SECRET, and ARM_TENANT_ID.

export ARM_SUBSCRIPTION_ID="<your-subscription-id>"
export ARM_CLIENT_ID="<your-service-principal-client-id>"
export ARM_CLIENT_SECRET="<your-service-principal-client-secret>"
export ARM_TENANT_ID="<your-tenant-id>"

Azure CLI: If you're logged in via az login, Terraform can use that session to authenticate. It's a good fit for local development.

Log in to Azure:

az login

Set your subscription (if you have multiple):

az account set --subscription="<your-subscription-id>"

Then run Terraform commands without any extra authentication configuration:

terraform init

Terraform will find the cached credentials and use them.

Managed Identity (MSI): For resources like Azure Functions, Virtual Machines, or Azure DevOps agents, you can authenticate with a Managed Identity, which needs no password or secret.

A Virtual Machine, for example, can be assigned an identity that has permissions to access other Azure resources.

  1. Enable a system-assigned or user-assigned identity on your Azure resource.
  2. Grant the identity the Storage Blob Data Contributor role on the storage account where your state file is stored.
  3. Set use_msi = true and use_azuread_auth = true in the backend block (or set ARM_USE_MSI=true). Terraform then authenticates with the managed identity, with no secrets in your configuration or environment variables.

What Are the Best Practices for the AzureRM Backend?

Separate State: Use a separate storage account and container for each environment (e.g., dev, stage, prod). This isolates state files and prevents accidental cross-environment changes.

Permission Scoping: Grant the service principal or managed identity the Storage Blob Data Contributor role scoped to the specific storage account or container. This follows the principle of least privilege.

Versioning: Enable blob versioning on the storage account that holds your state container (Azure turns versioning on per storage account, not per container). This gives you a history of your state files and allows you to revert to a previous version if something goes wrong.


How Do Partial Configs and Workspaces Extend the Backend?

Partial Backend Configuration

Terraform won't let you use variables directly inside the backend block (OpenTofu does, see more below). But you can leave out sensitive or environment-specific values and supply them at runtime with a backend configuration file or command-line flags on terraform init.

Example using a file:

Run terraform init**:**

terraform init -backend-config="backend.conf"

backend.conf :

storage_account_name = "myterraformstateaccount"
resource_group_name = "terraform-state-rg"

main.tf (partial config):

terraform {
  backend "azurerm" {
    container_name = "tfstate"
    key            = "my-app.tfstate"
  }
}

This keeps sensitive values out of source control.

Managing Multiple Environments with Workspaces

If a single configuration deploys to several environments, Terraform workspaces can keep a separate state file for each one in the same container.

Example:

# Create and switch to a development workspace
terraform workspace new dev

# Create and switch to a production workspace
terraform workspace new prod

When you switch between workspaces, Terraform automatically changes the key to include the workspace name (e.g., env:/dev/my-app.tfstate), ensuring each environment has its own isolated state file.

Does OpenTofu Support Dynamic Backend Blocks?

The classic Terraform CLI sticks to its strict rule against dynamic backend blocks. OpenTofu, a fork of Terraform, does it differently.

Starting with version 1.8, OpenTofu lets you use variables and local values inside the backend block, which answers one of the longest-standing requests in the Terraform community. You can write a DRY (Don't Repeat Yourself) backend configuration, which helps most in multi-environment setups.

Here's how a dynamic azurerm backend block could look in OpenTofu:

variable "env" {
  type    = string
  default = "dev"
}

terraform {
  backend "azurerm" {
    resource_group_name  = "terraform-state-${var.env}-rg"
    storage_account_name = "myterraformstate${var.env}"
    container_name       = "tfstate"
    key                  = "my-app-${var.env}.tfstate"
  }
}

Now you switch environments by changing the env variable, which you can pass at the command line or in a file:

tofu init -var="env=prod"

Now you don't need separate backend configuration files or fiddly scripts to handle different environments, so your codebase stays cleaner and you make fewer manual mistakes. If you manage a lot of similar environments, or you just prefer a shorter, variable-driven config, OpenTofu's dynamic backend blocks save real work. There's a full post on dynamic backend blocks in OpenTofu.

Running the storage account, locking, and access control yourself works well for one team. As more teams share the same state, a managed platform that provides the remote backend and run execution takes that operational load off you. Scalr does this on usage-based pricing that's free up to 50 runs a month.

This blog has been verified for Terraform and OpenTofu.

About the author

Ryan Fee

director of platform engineering at Scalr

Ryan Fee is the director of platform engineering at Scalr, with over 15 years of experience improving infrastructure experiences at companies large and small.

Part of this guide

19 sheets

Terraform State & Backends: The Complete Guide

18 articles