CloudOpsGuide
terraform

Terraform State Management: Remote State and Backends

Intermediate
13 minutes
October 2026
CloudOpsGuide Team

Terraform State Management: Remote State and Backends

Configure Terraform remote state with Azure Storage, S3, and other backends for safe team collaboration — and avoid the classic state corruption mistakes.

Table of Contents

Why Remote State

Local terraform.tfstate has serious problems for teams:

  • No locking — two people running apply simultaneously corrupts state
  • Secrets in plaintext — state contains sensitive values
  • Can't be shared — teammates can't access each other's laptops
  • No versioning — can't recover a corrupted state

Remote backends solve all of these.

Azure Storage Backend

Create the Storage Resources

# Resource group for state
az group create --name terraform-state-rg --location uksouth

# Storage account (name must be globally unique)
az storage account create \
  --name tfstate$RANDOM \
  --resource-group terraform-state-rg \
  --location uksouth \
  --sku Standard_LRS \
  --allow-blob-public-access false \
  --min-tls-version TLS1_2

# Container for state files
az storage container create \
  --name tfstate \
  --account-name <storage-account-name>

Configure the Backend

# backend.tf
terraform {
  backend "azurerm" {
    resource_group_name  = "terraform-state-rg"
    storage_account_name = "tfstate12345"
    container_name       = "tfstate"
    key                  = "prod.terraform.tfstate"
  }
}

Authenticate

# Option 1: Azure CLI login (simplest)
az login

# Option 2: Service Principal (CI/CD)
export ARM_CLIENT_ID="..."
export ARM_CLIENT_SECRET="..."
export ARM_TENANT_ID="..."
export ARM_SUBSCRIPTION_ID="..."

# Option 3: Access key (not recommended for prod)
export ARM_ACCESS_KEY="..."

Initialize

terraform init

# Migrating existing local state:
terraform init -migrate-state

S3 Backend (AWS)

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "eu-west-2"
    encrypt        = true
    dynamodb_table = "terraform-locks"   # locking
  }
}

The DynamoDB table provides state locking — create it once:

aws dynamodb create-table \
  --table-name terraform-locks \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

State Locking

Locking prevents concurrent apply runs. If you see:

Error: Error acquiring the state lock
Lock Info:
  ID:        abc-123
  Operation: apply
  Who:       user@machine

Check First — Is Someone Actually Applying?

# DON'T force-unlock while a real apply is running!
# Ask your team / check CI pipelines first

Force Unlock (only if safe)

terraform force-unlock <lock-id>

State Commands Cheat Sheet

# List all resources in state
terraform state list

# Show a specific resource
terraform state show azurerm_resource_group.main

# Remove a resource from state (doesn't delete in Azure)
terraform state rm azurerm_resource_group.main

# Move/rename a resource in state
terraform state mv azurerm_resource_group.old azurerm_resource_group.new

# Pull current state (view it)
terraform state pull > backup.tfstate

# Import an existing resource into state
terraform import azurerm_resource_group.main /subscriptions/xxx/resourceGroups/rg-name

Drift Detection

# Detect out-of-band changes made in the portal
terraform plan -refresh-only

Recovering from Problems

Corrupted State

# Azure blob storage keeps versions — restore one
az storage blob list \
  --account-name tfstate12345 \
  --container-name tfstate \
  --prefix prod.terraform.tfstate

# Download an earlier version
az storage blob download \
  --account-name tfstate12345 \
  --container-name tfstate \
  --name prod.terraform.tfstate \
  --version-id <version-id> \
  --file recovered.tfstate

Prevention: enable blob versioning + soft delete on the state container:

az storage account blob-service-properties update \
  --account-name tfstate12345 \
  --enable-versioning true

Resource Deleted Manually in Portal

# Terraform will try to recreate it — or remove from state:
terraform state rm <resource-address>
terraform plan   # should show no changes if resource is gone

Two Applies Ran Concurrently

  1. Pull current remote state: terraform state pull > current.tfstate
  2. Compare with terraform plan
  3. Manually reconcile: terraform import missing resources or terraform state rm phantom entries

Best Practices

  1. Separate state per environment — dev.tfstate, staging.tfstate, prod.tfstate (or separate storage accounts entirely)
  2. Enable versioning — recover from corruption
  3. Restrict access — only CI/CD and admins should write state
  4. Never edit state by hand — use terraform state commands
  5. Small states — split large projects into multiple stacks (networking / compute / apps)
  6. Backend config is code — keep backend.tf in version control, never credentials
  7. Enable soft delete on state storage for accidental deletion protection

Example: Complete Backend Setup (Azure)

# backend.tf
terraform {
  required_version = ">= 1.6"

  backend "azurerm" {
    resource_group_name  = "terraform-state-rg"
    storage_account_name = "tfstate12345"
    container_name       = "tfstate"
    key                  = "prod.terraform.tfstate"
  }

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

Related Articles


Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Intermediate
Estimated Reading Time: 13 minutes