Terraform Tutorial — DynamoDB Locking

1 minute read

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.


← S3 Backend Setup Terraform Cloud Backend →