Terraform

Terraform Azure Blob Backend Deep Dive: Remote State Management on Azure

2026-04-19
NicheeLab Editorial Team

To run Terraform safely across multiple people and environments, you need centralized remote state, locking to prevent concurrent runs, and robust authentication. This article distills practical operational tips for the azurerm backend on Azure Blob Storage, along with the points most commonly tested on the Associate / Pro exams.

Based on the official specification, we cover creation commands, authentication patterns, workspace separation, locking behavior, and CI/CD pitfalls with concrete fixes.

Why the Azure Blob Backend: Fundamentals and Key Points

A Terraform backend defines where state is stored and how it is locked. On Azure, the standard choice is the azurerm backend with Blob Storage. Mutual exclusion via Blob leases prevents concurrent apply runs.

Storage has server-side encryption enabled by default, and Azure AD-based RBAC lets you grant fine-grained data-plane permissions. On the Terraform side, terraform init wires up the backend, and subsequent plan/apply runs read and update remote state.

  • Locking: Mutual exclusion through Azure Blob leases. Manual release with force-unlock is also possible.
  • Encryption: Server-side encryption is enabled by default. You can switch to CMK on the storage side if required.
  • Authentication: Azure AD (service principal / Managed Identity / Azure CLI), with storage access keys or SAS as alternatives (Azure AD is recommended).
BackendLockingDefault EncryptionPrimary Authentication
localNone (risk of corruption with concurrent runs)NoneLocal permissions only
azurerm (Azure Blob)Mutual exclusion via Blob LeaseServer-side encryption (Microsoft-managed keys)Azure AD (RBAC) / Managed Identity / Service Principal / (alternative) access key, SAS
s3 (reference)Optional lock via DynamoDB tableServer-side encryption (SSE-S3 default)AWS IAM (role/user)

Azure Blob backend flow (including locking)

Dev / CI Runnerterraform init / plan / applyAzure Storage AccountContainer: tfstate / Blob: *.tfstateacquire lease (Blob Lease) for mutual exclusion → single writer, prevents concurrent apply

Minimal backend block (authentication supplied from the environment)

terraform {
  backend "azurerm" {
    resource_group_name  = "rg-tfstate"
    storage_account_name = "sttfstate1234"
    container_name       = "tfstate"
    key                  = "network/terraform.tfstate"
    # Credentials are sourced from the environment (Azure AD / CLI / Managed Identity / SP)
  }
}

Prerequisites: Creating the Storage Account and Container

Provision a dedicated storage account for state. Names must be lowercase alphanumeric and 3 to 24 characters long. Split Blob containers per environment, or separate logically by key. Disable public access, and enable versioning and soft delete for easier recovery.

Before running terraform init, create the resource group, storage account, and container. When you authenticate with Azure AD, creating the container via CLI login authentication is the safest path.

  • Storage kind: StorageV2; choose redundancy such as LRS or GRS according to requirements
  • Recommended settings: enable Blob versioning, configure soft-delete retention, and disable public access
  • Naming guidelines: use tfstate as the container name, and classify keys with paths such as proj/env/

Example creation with the Azure CLI and recommended property settings

# 1) Resource group
az group create -n rg-tfstate -l japaneast

# 2) Storage account (public access disabled)
az storage account create \
  -n sttfstate1234 \
  -g rg-tfstate \
  -l japaneast \
  --sku Standard_LRS \
  --kind StorageV2 \
  --allow-blob-public-access false

# 3) Blob versioning and soft delete (e.g. 30 days)
az storage account blob-service-properties update \
  --account-name sttfstate1234 \
  --enable-versioning true
az storage blob service-properties delete-policy update \
  --account-name sttfstate1234 \
  --enable true --days-retained 30

# 4) Create the container (using Azure AD login)
az storage container create \
  --name tfstate \
  --account-name sttfstate1234 \
  --auth-mode login

Authentication and Authorization: Azure AD and RBAC as the Foundation

Azure AD is the recommended way to authenticate the backend. You can use a service principal, Managed Identity, a local Azure CLI login, or Workload Identity (OIDC). For storage data-plane permissions, grant at least Storage Blob Data Contributor (scoping to the container level is preferable to minimize blast radius).

For environment-variable service principal authentication, set ARM_CLIENT_ID / ARM_CLIENT_SECRET / ARM_TENANT_ID / ARM_SUBSCRIPTION_ID. For Workload Identity federation (e.g. GitHub Actions), set client ID / tenant ID / subscription ID, and the OIDC token is sourced from the file or environment provided by the login action. Access keys and SAS tokens are also possible, but Azure AD is the safer default because key rotation and secret management get complex in production.

  • Required roles (examples): Storage Blob Data Contributor (read/write); use Storage Blob Data Reader for read-only environments
  • Recommended: avoid sharing storage account keys; leverage RBAC and auditing instead
  • Cross-tenant / cross-subscription: tenant_id and subscription_id can be specified explicitly in the backend block

RBAC assignment and environment variable example (service principal authentication)

# Grant Storage Blob Data Contributor to the service principal (appId)
az role assignment create \
  --assignee <APP_ID> \
  --role "Storage Blob Data Contributor" \
  --scope "/subscriptions/<SUB_ID>/resourceGroups/rg-tfstate/providers/Microsoft.Storage/storageAccounts/sttfstate1234"

# Environment variables for Terraform (SP secret authentication)
export ARM_CLIENT_ID=<APP_ID>
export ARM_CLIENT_SECRET=<CLIENT_SECRET>
export ARM_TENANT_ID=<TENANT_ID>
export ARM_SUBSCRIPTION_ID=<SUB_ID>
# (For Workload Identity / OIDC, the login action supplies the token; CLIENT_SECRET is not required)

Backend Configuration and State Separation: Keys and Containers per Environment

The backend block is static, so variable interpolation generally is not allowed. To separate state per environment, switch the key or split the container. For team operations, enforce a naming convention of project/environment/component.tfstate to prevent accidental cross-references.

A practical pattern is to fill in a partial configuration by passing -backend-config at terraform init time. To reflect the workspace name in the key, fetch the current workspace from a script and overwrite the key with init -reconfigure.

  • Example key: key = projectA/dev/network.tfstate
  • Recommended: harden boundaries with a dedicated container or storage account for production
  • Externalize sensitive backend values with -backend-config (do not hardcode them in the repository)

Example of -backend-config with workspace integration

# Example) switch the key based on the environment
RG=rg-tfstate
ST=sttfstate1234
CT=tfstate
WS=$(terraform workspace show)
KEY="projectA/${WS}/network.tfstate"

terraform init -reconfigure \
  -backend-config="resource_group_name=${RG}" \
  -backend-config="storage_account_name=${ST}" \
  -backend-config="container_name=${CT}" \
  -backend-config="key=${KEY}"

Locking, Encryption, and Resilience: Settings That Pay Off in Production

Locking is implemented via Blob leases, and concurrent applies are rejected. If a process aborts and leaves a stale lock, release it with terraform force-unlock (the lock ID is shown in the error message).

Encryption is enabled by default. Depending on requirements, you can switch to customer-managed keys (CMK) on the storage side. Enabling Blob versioning and soft delete makes rollback after a mistake easy by restoring a prior version.

  • Stuck lock: terraform force-unlock -force <LOCK_ID>
  • Recommended: strengthen auditing with Blob versioning, soft delete, and change feed
  • Caution: manual edits to state are forbidden as a rule; if absolutely required, take a backup first

Releasing locks and checking versioning

# Unlock (use the LOCK_ID shown in the error message)
terraform force-unlock -force 01234567-89ab-cdef-0123-456789abcdef

# Versioning enablement is covered in section 2. Restore prior Blob versions from the Portal or CLI.

CI/CD Integration and Troubleshooting

In CI, the keys are non-interactive execution (-input=false) and reliable credential delivery. When the backend is reconfigured, specify -reconfigure explicitly. Because state can contain sensitive data, be careful about where you store plan files and what you log.

Common errors and fixes: 403 (AuthorizationPermissionMismatch) usually means insufficient RBAC; other typical culprits are a missing or misspelled container, mismatched tenant or subscription, and lock contention from concurrent jobs.

  • On any change, always run terraform init -reconfigure
  • Serialize jobs to avoid concurrent apply, or fully separate environments and keys
  • Verify that plan and apply reference the same backend and the same key

GitHub Actions example (OIDC + Azure AD)

name: tf-apply
on: [workflow_dispatch]
jobs:
  apply:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id:  ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.6.6
      - name: Init backend
        run: |
          terraform init -reconfigure \
            -backend-config="resource_group_name=rg-tfstate" \
            -backend-config="storage_account_name=sttfstate1234" \
            -backend-config="container_name=tfstate" \
            -backend-config="key=projectA/prod/app.tfstate"
      - name: Plan
        run: terraform plan -input=false -out=plan.tfplan
      - name: Apply
        run: terraform apply -input=false -auto-approve plan.tfplan

Check Your Understanding

Associate / Pro

問題 1

Which mechanism does the Terraform azurerm backend use to prevent concurrent state updates?

  1. Mutual exclusion using an Azure Blob lease
  2. Azure Queue Storage message visibility timeout
  3. Substitute with Azure Files file locks (SMB)
  4. Terraform Cloud's lock API must always be combined

正解: A

The azurerm backend implements mutual exclusion via Azure Blob leases. Queue visibility timeouts and SMB locks do not apply here, and Terraform Cloud integration is not required.

Frequently Asked Questions

Can I authenticate with Azure AD instead of a storage access key?

Yes. You can use a service principal, Managed Identity, Azure CLI login, or Workload Identity (OIDC). Grant at least the Storage Blob Data Contributor RBAC role.

What is the best practice for separating state per environment?

Use a dedicated container or storage account for production, and separate non-production environments by key naming such as project/env/component.tfstate. Since the backend block is static, switch keys at init time with -backend-config.

The lock will not release and I cannot apply. What should I do?

First make sure no other run is still in progress, then run terraform force-unlock -force <LOCK_ID> if needed. If this happens often, review CI serialization, timeout settings, and job-level mutual exclusion.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.