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
- Azure Storage Backend
- S3 Backend
- State Locking
- State Commands
- Recovering from Problems
Why Remote State
Local terraform.tfstate has serious problems for teams:
- No locking — two people running
applysimultaneously 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
- Pull current remote state:
terraform state pull > current.tfstate - Compare with
terraform plan - Manually reconcile:
terraform importmissing resources orterraform state rmphantom entries
Best Practices
- Separate state per environment —
dev.tfstate,staging.tfstate,prod.tfstate(or separate storage accounts entirely) - Enable versioning — recover from corruption
- Restrict access — only CI/CD and admins should write state
- Never edit state by hand — use
terraform statecommands - Small states — split large projects into multiple stacks (networking / compute / apps)
- Backend config is code — keep
backend.tfin version control, never credentials - 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
- Terraform Azure Service Principal
- Terraform Workspaces: Multi-Environment
- Terraform Modules: Create and Reuse
Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Intermediate
Estimated Reading Time: 13 minutes