Terraform Remote State Locking with S3 and DynamoDB

Local state files don't survive a team. Two people running apply at the same time will race each other and corrupt state. The fix is a remote backend with locking.

Backend config

terraform {
  backend "s3" {
    bucket         = "acme-tfstate"
    key            = "prod/network/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "tfstate-locks"
    encrypt        = true
  }
}

Creating the lock table

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

Useful one-liners

Check who's holding a lock right now:

aws dynamodb scan --table-name tfstate-locks --query "Items[].LockID.S"

Force-unlock after a crashed apply (only after confirming no one is actually running):

terraform force-unlock <LOCK_ID>

Migrate an existing local state file into the new backend:

terraform init -migrate-state

Once the backend is wired up, every plan/apply in the workspace acquires the DynamoDB lock first, so a second run just waits (or fails fast) instead of stomping on the first.