Lock State with a Backend
Lock state with a backend provider — Terraform foundations tutorial, lesson 20. Learn how to prevent concurrent state conflicts and ensure safe, consistent infrastructure updates.
Focus: lock state with a backend provider
You’ve spent hours crafting the perfect Terraform configuration. You run terraform apply with confidence, and then—disaster—someone else on your team runs the same command at the exact same moment. Before you know it, your state file is corrupted, resources are created twice, and your infrastructure drifts into chaos. This lesson solves that pain by teaching you how to lock state with a backend provider, the key to safe, concurrent infrastructure updates.
The problem this lesson solves
When multiple developers or CI/CD pipelines run Terraform against the same infrastructure, they share a single state file. Without coordination, two concurrent operations can read the same state, make conflicting changes, and write back corrupted results. This leads to:
- Duplicate resources: Two
applyruns create the same VPC or storage bucket. - State corruption: One run overwrites another’s changes, leaving the state inconsistent with reality.
- Silent drift: Terraform thinks infrastructure is in one state, but the actual cloud resources are different.
This problem is not theoretical—it happens in real teams every day. The solution is state locking, a mechanism that ensures only one Terraform operation modifies the state at a time.
Core concept / mental model
Think of state locking like a bathroom door lock in a shared office. Only one person can use the room at a time; others wait their turn. When you enter, you lock the door. When you leave, you unlock it. If someone forgets to unlock, everyone else is stuck.
In Terraform, the backend provider (like S3, Azure Storage, or GCS) is the door. Terraform acquires a lock before reading or writing state, and releases it after the operation completes. The lock is stored as a special file or record in the backend, typically alongside the state file.
Key terms
- Backend: Where Terraform stores its state — local file, S3 bucket, Azure Storage, etc.
- State locking: A mechanism that prevents concurrent operations on the same state.
- Lock ID: A unique identifier for a lock, used to release it if needed.
How it works step by step
When you run terraform apply or terraform plan, Terraform performs these steps automatically:
- Acquire lock: Terraform posts a lock request to the backend. For S3, it writes a file called
default.tflockor uses DynamoDB to track the lock. For Azure, it uses blob storage leases. - Read state: Only after acquiring the lock does Terraform read the current state.
- Plan and execute: Terraform calculates changes and applies them.
- Write state: Terraform writes the new state file.
- Release lock: After writing, Terraform releases the lock so others can proceed.
If another user tries to acquire the lock while it’s held, Terraform blocks with an error message: "Error acquiring the state lock". It shows who holds the lock and what operation they’re running.
The process is transparent—you don’t have to manually manage locks. But you must configure a backend that supports locking, like S3 with DynamoDB, Azure Storage, or GCS with object versioning.
Hands-on walkthrough
Let’s put this into practice. You’ll configure an S3 backend with DynamoDB for locking, then run Terraform to see it in action.
Prerequisites
- AWS account and credentials configured
- Terraform installed (v1.0+)
- AWS CLI installed
Step 1: Create the backend infrastructure
First, create a simple Terraform configuration to provision an S3 bucket and DynamoDB table for locking. Save as backend.tf:
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "tf_state" {
bucket = "my-tf-state-bucket-12345"
versioning {
enabled = true
}
}
resource "aws_dynamodb_table" "terraform_lock" {
name = "terraform-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
Run terraform init and terraform apply to create these resources.
Step 2: Configure the backend
Now update backend.tf to use S3 as the backend:
terraform {
backend "s3" {
bucket = "my-tf-state-bucket-12345"
key = "terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-lock"
encrypt = true
}
}
provider "aws" {
region = "us-east-1"
}
Run terraform init -reconfigure to migrate state to the remote backend. Terraform will prompt to copy existing state—accept it.
Step 3: Test locking
Now run terraform apply while another operation is in progress. To simulate, open two terminals:
- In terminal 1, run
terraform apply -auto-approvebut pause it—you can add asleepresource to keep it running. - In terminal 2, run
terraform plan. You’ll see an error like:
Error: Error acquiring the state lock
... message: ...
Lock Info:
ID: 12345
Path: terraform.tfstate
Operation: OperationTypeApply
Who: [email protected]
Created: 2024-01-01 00:00:00
Terraform blocks the second operation until the first releases the lock.
Compare options / when to choose what
Different backends support locking differently. Here’s a comparison:
| Backend | Locking support | Mechanism | Best for |
|---|---|---|---|
| Local | No | N/A | Single user, learning |
| S3 | Requires DynamoDB | DynamoDB table | Team, AWS-centric |
| Azure Storage | Yes | Blob lease | Team, Azure-centric |
| GCS | Yes | Object versioning + lock | Team, GCP-centric |
| HTTP | Varies | Server-side implementation | Custom solutions |
| Terraform Cloud | Yes | Built-in | Teams with Terraform Cloud |
When to choose: For simple experiments, local is fine. For any team collaboration, use a remote backend with locking. S3 + DynamoDB is the most common choice in AWS environments. If you’re already using Terraform Cloud, its built-in locking is the least effort.
Variations
- Terraform Cloud: Managed backend with UI, locking built in.
- Consul: A backend that supports locking natively, good for HashiCorp stacks.
- MongoDB: Community backend, supports locking but less common.
Troubleshooting & edge cases
Error: "Error acquiring the state lock" — how to force unlock
If a process crashes and leaves a stale lock, Terraform can’t proceed. The error shows a lock ID. You can force release with:
terraform force-unlock <LOCK_ID>
Pro tip: Use
force-unlocksparingly. Only do it if you’re sure no other operation is running — otherwise you risk corruption.
Lock is stuck for a long time
Check the backend: for S3, look for the default.tflock file. For DynamoDB, query the table. Delete the lock record if it’s genuinely stale, but first confirm the process is dead.
Backend doesn’t support locking
If you configure an S3 backend without DynamoDB, Terraform warns that locking is not available. You can still use it, but you lose protection. Always add a DynamoDB table for production.
Concurrent terraform init during migration
If two people run terraform init -migrate-state at the same time, they can conflict. Communicate with your team or schedule migrations during off-hours.
What you learned & what's next
In this lesson, you learned how to lock state with a backend provider to prevent concurrent conflicts. You can now:
- Explain why state locking is critical for team collaboration.
- Configure an S3 backend with DynamoDB locking.
- Troubleshoot and force-unlock stale locks.
This skill is foundational for safe infrastructure-as-code practices. Up next, you’ll learn about workspaces, which help you manage multiple environments (dev, staging, prod) using the same configuration — another essential tool in your Terraform toolkit.
Keep your state safe, and your team will thank you.
Practice recap
Now it’s your turn: extend the hands-on walkthrough by creating a second resource (like an EC2 instance) in your configuration and run terraform plan from two terminals simultaneously. Observe how the second command blocks. Then, simulate a crashed apply and practice using terraform force-unlock to release the lock. You’ll gain confidence in handling real-world locking scenarios.
Common mistakes
- Forgetting to add a DynamoDB table to the S3 backend — Terraform silently proceeds without locking, leaving you exposed to conflicts.
- Using
force-unlockwithout verifying the lock is stale — you can corrupt state if another operation is actually in progress. - Running
terraform init -migrate-stateconcurrently with teammates — the state migration can overwrite remote state. - Ignoring the error message that says who holds the lock — you might
force-unlocksomeone else’s active apply.
Variations
- Use Terraform Cloud as a backend — it provides managed state locking plus a web UI, ideal for teams not tied to a single cloud.
- Adopt an HTTP backend with a custom locking service — gives you full control but requires more engineering effort.
- For Azure, use the Azure Storage backend with blob leases — a native locking mechanism without extra configuration.
Real-world use cases
- Multiple developers applying Terraform to the same shared AWS environment (e.g., staging) without state corruption.
- CI/CD pipelines running
terraform applyalongside manual ops — locking prevents conflicting changes. - Migrating state from local to remote with a team — locking ensures safe migration without data loss.
Key takeaways
- State locking prevents concurrent Terraform operations from corrupting the state file.
- An S3 backend requires DynamoDB to enable locking; without it, there is no conflict protection.
- Terraform acquires and releases locks automatically per operation — no manual steps.
- To release a stale lock, use
terraform force-unlock <LOCK_ID>after confirming no other process is running. - Choose a backend that supports locking (S3+DynamoDB, Azure, GCS) for any team collaboration.
- State locking is a small configuration step with a huge payoff for infrastructure reliability.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.