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

Sponsored

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 apply runs 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:

  1. Acquire lock: Terraform posts a lock request to the backend. For S3, it writes a file called default.tflock or uses DynamoDB to track the lock. For Azure, it uses blob storage leases.
  2. Read state: Only after acquiring the lock does Terraform read the current state.
  3. Plan and execute: Terraform calculates changes and applies them.
  4. Write state: Terraform writes the new state file.
  5. 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:

  1. In terminal 1, run terraform apply -auto-approve but pause it—you can add a sleep resource to keep it running.
  2. 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-unlock sparingly. 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-unlock without verifying the lock is stale — you can corrupt state if another operation is actually in progress.
  • Running terraform init -migrate-state concurrently with teammates — the state migration can overwrite remote state.
  • Ignoring the error message that says who holds the lock — you might force-unlock someone else’s active apply.

Variations

  1. Use Terraform Cloud as a backend — it provides managed state locking plus a web UI, ideal for teams not tied to a single cloud.
  2. Adopt an HTTP backend with a custom locking service — gives you full control but requires more engineering effort.
  3. 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 apply alongside 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.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.