Terraform Tutorial — DynamoDB Locking
DynamoDB locking prevents two people from modifying state simultaneously. It’s the standard companion to the S3 backend.
Create the DynamoDB Table
$ 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
The table needs exactly one attribute: LockID (string, hash key).
Add to Backend Config
terraform {
backend "s3" {
bucket = "my-company-terraform-state"
key = "projects/web-app/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks" # ← locking
}
}
How It Works
terraform apply
├─ 1. Write lock to DynamoDB (LockID = bucket/key)
├─ 2. Read state from S3
├─ 3. Make changes
├─ 4. Write state to S3
└─ 5. Delete lock from DynamoDB
If someone else runs terraform apply while the lock exists:
Error: Error acquiring the state lock
Lock Info:
ID: 12345-abc-678
Path: my-company-terraform-state/projects/web-app/terraform.tfstate
Operation: OperationTypeApply
Who: alice@laptop
Created: 2024-01-15 14:30:00 UTC
Terraform acquires a state lock to protect the state from being
written by multiple users at the same time.
Force Unlock
If a lock is stuck (crashed process, network timeout):
$ terraform force-unlock 12345-abc-678
Do you really want to force-unlock?
Enter a value: yes
⚠️ Warning: Only use force-unlock when you’re certain no other operation is in progress.
Complete Bootstrap Configuration
Bootstrap the state bucket and lock table with Terraform itself:
# bootstrap/main.tf — run this first with local state
resource "aws_s3_bucket" "state" {
bucket = "my-company-terraform-state"
}
resource "aws_s3_bucket_versioning" "state" {
bucket = aws_s3_bucket.state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "state" {
bucket = aws_s3_bucket.state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
resource "aws_s3_bucket_public_access_block" "state" {
bucket = aws_s3_bucket.state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_dynamodb_table" "locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
What’s Next?
Let’s look at Terraform Cloud as an alternative backend.