Using the AzureRM Backend Block in Terraform
Using the AzureRM backend makes it much easier to scale your Terraform usage.
If you're using Terraform to manage your Azure infrastructure, you'll likely need to configure a remote backend. The azurerm
backend block is the solution for storing your Terraform state files in Azure Blob Storage. This is a crucial step for team collaboration, state locking, and maintaining the integrity of your infrastructure as code.
The Terraform state file is a JSON file that acts as a record of your deployed resources. It maps your Terraform configuration to the actual resources in your Azure subscription. Storing this file remotely with the azurerm
backend provides several key benefits:
- Collaboration: It allows multiple team members to work on the same infrastructure without overwriting each other's changes.
- State Locking: It prevents concurrent
terraform apply
operations, which can lead to state file corruption and resource conflicts. Azure Blob Storage's native leasing feature handles this locking automatically. - Security & Durability: State files can contain sensitive data. Storing them in a secure, encrypted, and highly available Azure storage account is a best practice.
Basic Usage and Configuration
To use the azurerm
backend, you need a pre-existing Azure Storage Account and a container within it. It's considered best practice to create a dedicated storage account 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.
After adding this block to your main Terraform configuration file, you must run terraform init
. This command initializes the backend and prompts you to migrate any existing local state to the remote location.
Use Cases
The azurerm
backend is essential for any serious Terraform project.
- Team Projects: When multiple developers are working on a single infrastructure, the
azurerm
backend ensures everyone is using the same, up-to-date state file. This prevents developers from accidentally managing resources they shouldn't. - CI/CD Pipelines: In a continuous integration and continuous deployment (CI/CD) workflow, the pipeline needs a consistent place to read and write the state file. The
azurerm
backend provides a reliable and secure endpoint for tools like Azure DevOps, GitHub Actions, or Jenkins to execute Terraform. - Production Environments: For production infrastructure, the
azurerm
backend is non-negotiable. Its built-in state locking and data durability features are critical for preventing downtime and ensuring the integrity of your production environment.
Authentication Methods
Authentication is a critical aspect of the azurerm
backend. You should never hardcode credentials like access keys directly in your configuration. Instead, use one of the supported authentication methods:
Environment Variables: The most common approach. Terraform can automatically use 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 leverage that session to authenticate. This is a great option for local development.
Run Terraform commands without any extra authentication configuration:
terraform init
Set your subscription (if you have multiple):
az account set --subscription="<your-subscription-id>"
Log in to Azure:
az login
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 use a Managed Identity to authenticate. This is a highly secure, password-less method.
A Virtual Machine, for example, can be assigned an identity that has permissions to access other Azure resources.
- Enable a system-assigned or user-assigned identity on your Azure resource.
- Grant the identity the
Storage Blob Data Contributor
role on the storage account where your state file is stored. - Your Terraform process, when run on that resource, will automatically authenticate using the managed identity. No credentials are needed in your configuration or environment variables.
Best Practices
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 versioning on your Azure Blob Storage container. This gives you a history of your state files and allows you to revert to a previous version if something goes wrong.
Advanced Concepts
Partial Backend Configuration
Terraform's design prevents you from using variables directly inside the backend block (OpenTofu does, see more below). However, you can leave out sensitive or environment-specific information and supply it at runtime using a backend configuration file or command-line flags with 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 method prevents sensitive information from being committed to source control.
Managing Multiple Environments with Workspaces
For a single configuration that deploys to multiple environments, Terraform workspaces can be used to manage different state files within 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.
A Note on OpenTofu and Dynamic Backends
While the classic Terraform CLI maintains its strict rule against dynamic backend blocks, the OpenTofu project, a fork of Terraform, has broken with this convention.
OpenTofu, starting with version 1.8, introduced the ability to use variables and local values within the backend
block. This is a significant feature that addresses one of the most long-standing user requests in the Terraform community.
With OpenTofu, you can now write a more flexible and DRY (Don't Repeat Yourself) backend configuration, particularly useful for 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"
}
}
This simple configuration allows you to switch between environments by simply changing the env
variable. You can pass the variable at the command line or file:
tofu init -backend-config="env=prod"
This removes the need for separate backend configuration files or complex scripts to handle different environments, making your codebase cleaner and less prone to manual errors. For teams managing many similar environments, or for those who simply prefer a more concise and variable-driven configuration, OpenTofu's support for dynamic backend blocks is a game-changer. See a full blog on this here.