Skip to main content

State Lock

A Terraform state lock prevents multiple terraform apply operations from running against the same state file simultaneously. When one engineer runs terraform apply, the state is locked — any other apply attempt waits or fails until the lock is released.

What Is a Terraform State Lock?

Imagine two engineers at Swiggy both notice the same infrastructure issue at 9am on Monday. Both run terraform apply at almost the same time. Both see a clean plan. Both hit enter. Without locking, both applies run simultaneously — both read the same state, both make changes, both write back a new state file. The second write overwrites the first. Resources get created twice. State ends up describing infrastructure that does not match reality. Nobody knows what actually exists.

A state lock prevents exactly this. The moment an apply starts, Terraform writes a lock record to DynamoDB. Any other terraform apply that tries to start reads that lock record and waits — or fails with a clear error explaining who holds the lock and when they acquired it.

Bash
+------------------------------------------+
| Engineer A runs terraform apply |
| Lock acquired: LockID written to DynamoDB|
+------------------------------------------+
|
+-----------+-----------+
| |
v v
+------------------+ +-------------------+
| Engineer A | | Engineer B |
| Apply proceeds | | terraform apply |
| normally | | Error: state is |
| | | locked by A since |
| | | 09:03:14 UTC |
+------------------+ +-------------------+
|
v
+------------------------------------------+
| Apply complete |
| Lock released: LockID deleted from table |
| Engineer B can now run apply |
+------------------------------------------+

How Locking Works with S3 and DynamoDB

When using the S3 backend with DynamoDB, Terraform performs these steps:

  1. Before apply starts: write a lock item to DynamoDB with key LockID = <bucket>/<key>
  2. If that item already exists: another process holds the lock — fail or wait
  3. During apply: the lock item stays in DynamoDB
  4. After apply completes (success or failure): delete the lock item

The lock item contains who holds it, when they acquired it, and what operation they are running — visible if you look in the DynamoDB table directly.

Bash
# What a lock looks like when another apply is running
terraform apply
# Error: Error acquiring the state lock
#
# Error message: ConditionalCheckFailedException: The conditional request failed
# Lock Info:
# ID: 8a3c2f91-4b5d-4e6f-9a2b-1c3d4e5f6a7b
# Path: razorpay-terraform-state-ap-south-1/prod/payments-api/terraform.tfstate
# Operation: OperationTypeApply
# Who: priya@razorpay.com
# Version: 1.6.3
# Created: 2024-01-15 09:03:14.782341 +0000 UTC
# Info:
#
# Terraform acquires a state lock to protect the state from being written
# by multiple users at the same time. Please resolve the issue above and try
# again. For most commands, you can disable locking with the "-lock=false"
# flag, but this is not recommended.

Breaking a Stuck Lock

Sometimes a lock is left behind — the engineer's laptop died mid-apply, the CI/CD runner was killed, or the network dropped. The lock item stays in DynamoDB but nobody is actually running an apply.

Before breaking a lock, verify that no apply is actually running:

Bash
# Check who holds the lock — look at the Created timestamp
# If it is hours or days old and you confirmed no apply is running, break it
terraform force-unlock <LOCK-ID>
# The LOCK-ID is the ID shown in the error message above
# Example:
terraform force-unlock 8a3c2f91-4b5d-4e6f-9a2b-1c3d4e5f6a7b
# Terraform will ask for confirmation:
# Do you really want to force-unlock?
# Terraform will remove the lock on the remote state.
# This will allow local Terraform commands to modify this state, even though it
# may still be used by a running Terraform process.
# There is no undo. Only 'yes' will be accepted to confirm.
# Enter a value: yes
Security

Never run terraform force-unlock while an apply might actually be in progress. Force-unlocking an active apply causes both applies to proceed simultaneously — exactly the race condition locking is designed to prevent. Confirm the original apply is dead before unlocking.

DynamoDB Table Requirements

The DynamoDB table for state locking must have exactly one attribute — LockID as the partition key of type String. Nothing else is required.

HCL
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST" # locks are infrequent — pay-per-request is cheapest
hash_key = "LockID" # MUST be exactly "LockID" — Terraform expects this name
attribute {
name = "LockID"
type = "S"
}
tags = {
ManagedBy = "terraform"
Purpose = "terraform-state-locking"
}
}

Locking in Terraform Cloud

If you use Terraform Cloud as your backend, locking is built in — no DynamoDB table needed. Terraform Cloud handles locking automatically for every plan and apply, with a UI that shows who holds the lock and lets workspace admins force-unlock.

Troubleshooting State Locks

Error Root Cause Fix
Error acquiring the state lock Another apply is running Wait for it to complete, or verify it is stuck and force-unlock
ConditionalCheckFailedException DynamoDB locking conflict Normal — another process holds the lock
ResourceNotFoundException on lock table DynamoDB table does not exist Create the table with LockID as partition key
AccessDenied on DynamoDB Missing IAM permissions Add dynamodb:GetItem, dynamodb:PutItem, dynamodb:DeleteItem
Lock stuck for hours Apply crashed without releasing Confirm no apply is running, then terraform force-unlock <ID>
Remember

The -lock=false flag disables locking entirely. Never use it in a shared team environment — it removes the only protection against simultaneous applies. It is only acceptable for read-only operations or in a single-engineer local setup.

Frequently Asked Questions

Why does Terraform need a state lock at all if only one person runs apply?

State locking exists for team environments and CI/CD, not solo use — without it, two concurrent `terraform apply` runs (say, a developer running it locally while a pipeline also triggers) can both read the same state, make conflicting changes, and corrupt the state file, leaving Terraform's record of infrastructure out of sync with reality. Locking is typically implemented via the backend: S3 backends use a DynamoDB table, Terraform Cloud has native locking built in.

What do you do when a Terraform state lock gets stuck and won't release?

This usually happens after a crashed apply, killed CI job, or lost network connection that never released the lock cleanly. `terraform force-unlock <lock-id>` removes it manually, but you should first confirm no other apply is genuinely still running — force-unlocking during an active apply is exactly the corruption scenario locking exists to prevent. Check the lock ID and holder info the error message gives you before forcing it.