State Locking: Preventing Concurrent State Corruption
Scenario
Two engineers on your team ran terraform apply at the same time. Both wrote to the same state file. The state is now corrupted — resources are duplicated, some are orphaned. You need state locking to prevent this. With DynamoDB, Terraform acquires a lock before modifying state and releases it after. If someone else is applying, you get a clear error instead of corruption.
- Create a DynamoDB table for state locking with a LockID hash key (case-sensitive).
- Add dynamodb_table to the S3 backend config.
- Observe the lock being acquired and released during terraform apply.
- Understand terraform force-unlock for stuck locks.
Additional Context
The DynamoDB table for locking requires exactly one attribute: LockID (type String) as the partition key. The name is case-sensitive — lockid or lock_id will NOT work.
When Terraform starts an operation that modifies state, it writes a lock record to DynamoDB. When done, it deletes the record. If a previous apply crashed and left a stale lock, use terraform force-unlock LOCK_ID — but only if you are certain nobody else is actively applying.
bootstrap/main.tf (create lock table first)
# bootstrap/main.tf — run with local state FIRST
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" { region = "us-east-1" }
# DynamoDB table for state locking
# CRITICAL: hash_key MUST be "LockID" (case-sensitive)
resource "aws_dynamodb_table" "terraform_lock" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST" # no capacity planning needed
hash_key = "LockID" # exactly this name, case-sensitive
attribute {
name = "LockID"
type = "S" # String
}
tags = { Purpose = "terraform-state-lock", ManagedBy = "terraform" }
}
output "lock_table_name" { value = aws_dynamodb_table.terraform_lock.name }versions.tf (main config with locking)
# versions.tf — S3 backend WITH DynamoDB locking
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "locking-demo/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock" # enables locking
}
}Workflow Commands
# Step 1: Create the lock table
cd bootstrap && terraform init && terraform apply && cd ..
# Step 2: Init main config with locking enabled
terraform init
# Step 3: Apply — watch for lock messages
terraform apply
# Output includes: "Acquiring state lock. This may take a few moments..."
# and: "Releasing state lock. This may take a few moments..."
# If a lock is stuck (crashed apply left the lock):
# First, find the Lock ID from the error message
# ERROR: Error acquiring the state lock
# Lock Info:
# ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# Force unlock (DANGEROUS — only if you're sure nobody is applying)
terraform force-unlock xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
terraform destroyAcquiring state lock. This may take a few moments... aws_s3_bucket.demo: Creating... aws_s3_bucket.demo: Creation complete after 2s Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Releasing state lock. This may take a few moments...

