Skip to main content
All articles

You Don’t Need DynamoDB for Terraform State Locking Anymore

Terraform 1.10+ can lock state natively in S3, so the DynamoDB table is optional. When to migrate, and the trade-offs.

Sahil BansalConnect

3 min readOriginally on Medium

How a single config flag quietly changed the way teams manage remote state — and when you should (and shouldn’t) use it.

If you’ve been working with Terraform for any length of time, you’ve gone through the ritual:

  1. Create an S3 bucket for remote state
  2. Create a DynamoDB table for state locking
  3. Wire them together in your backend config
  4. Hope your team doesn’t corrupt state during a simultaneous apply

It’s been the standard playbook for years. But as of Terraform 1.10+, there’s a cleaner option — and most engineers haven’t noticed it yet.

The Old Way: S3 + DynamoDB

The classic backend config looked like this:

terraform {
 backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-lock"
 }
}

The DynamoDB table served one purpose: prevent two concurrent terraform apply runs from corrupting the same state file. It stored a lock entry (LockID) for the duration of the operation and released it on completion (or forced-unlock on failure).

It worked well. But it added operational overhead — another resource to manage, another IAM policy to wire up, another table to monitor.

The New Way: Native S3 Locking

Terraform now supports state locking natively within S3, using S3’s conditional writes under the hood.

You enable it with a single flag:

terraform {
 backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    use_lockfile   = true     # No DynamoDB needed
 }
}

No DynamoDB table. No extra IAM policy. Terraform manages the lock as a .tflock file directly in the bucket alongside your state.

How It Works Under the Hood

When use_lockfile = true:

  • On terraform apply (or plan), Terraform attempts to create a .tflock object in S3 using a conditional PUT request — it only succeeds if the object doesn't already exist.
  • If another process holds the lock, the conditional write fails, and Terraform returns a lock error — same behavior as DynamoDB.
  • On completion, Terraform deletes the .tflock file to release the lock.

This is made possible by S3’s If-None-Match: * header support, which provides atomic conditional writes without an external coordination service.

When to Use Each Approach

✅ Go with use_lockfile = true if:

  • You’re a solo developer or working in a small team
  • Your pipelines have low concurrency — one or two runners at a time
  • You’re setting up a greenfield project and want minimal infrastructure

⚠️ Evaluate carefully before switching if:

  • You already have DynamoDB set up and running — the migration risk simply isn’t worth it for an existing stable setup
  • You’re running multi-region or cross-account Terraform — S3 replication introduces edge cases around lock visibility that DynamoDB handles more predictably

❌ Stick with DynamoDB if:

  • You have a large team with many parallel pipelines running simultaneously
  • You’re running high-frequency automation with 10+ concurrent applies — at that scale, DynamoDB’s battle-tested conditional writes and CloudWatch visibility are worth the overhead

Trade-offs You Need to Know

✅ Advantages of native S3 locking:

  • One fewer AWS resource to manage
  • Simpler IAM — no dynamodb:PutItem, GetItem, DeleteItem permissions needed
  • Faster onboarding for new environments
  • Less cost (DynamoDB is cheap, but it’s still another resource)

⚠️ Limitations to be aware of:

  • Requires Terraform 1.10+ — verify your version before enabling
  • If a pipeline crashes mid-apply, you may need to manually delete the .tflock file (same as terraform force-unlock, just in S3)
  • No built-in TTL — a dangling lock from a crashed runner won’t auto-expire (DynamoDB also doesn’t TTL by default, but it’s easier to add)
  • Less battle-tested at scale compared to DynamoDB locking

Note: Always enable S3 versioning on your state bucket regardless of which locking method you use. It’s your last line of defense against accidental state corruption.

Migrating from DynamoDB to Native Locking

If you want to switch an existing setup:

terraform {
 backend "s3" {
    bucket       = "my-terraform-state"
    key          = "prod/terraform.tfstate"
    region       = "us-east-1"
    use_lockfile = true
    # Remove: dynamodb_table = "terraform-lock"
 }
}

Then run:

terraform init -reconfigure

Before removing the DynamoDB table, make sure no active pipelines reference it. Consider keeping the table around for a few weeks as a safety net.

Bottom Line

Native S3 locking is a genuine simplification — fewer resources, simpler IAM, less overhead. For greenfield projects or small teams, it’s the right default today.

For large teams running dozens of concurrent pipelines, DynamoDB’s track record and operational visibility (CloudWatch metrics, conditional writes at scale) still make it the safer choice.

Infrastructure practices evolve. The best engineers don’t just adopt new patterns — they understand when to adopt them.

Originally published on LinkedIn by Sahil Bansal(Me).

  • Terraform
  • AWS
  • State management

Written by Sahil Bansal

DevOps and platform engineer. I write about the infrastructure decisions I have had to live with.

Connect