No more DynamoDB tables for Terraform locking! Terraform 1.9+ introduced S3 native state locking - a built-in mechanism that eliminates the extra AWS resource while keeping your team deployments safe. Here's everything you need to know.
The Problem with DynamoDB Locking
Traditional S3 backend required two AWS resources:
terraform {
backend "s3" {
bucket = "my-state-bucket"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks" # Extra cost + management ❌
encrypt = true
}
}
Issues:
- DynamoDB table = always-on cost (~$0.25/month minimum)
- Extra IAM permissions to manage
- One more resource to create/delete
- Lock table drift (table deleted but state remains)
### Enter S3 Native State Locking
Terraform 1.9+ (January 2026):
use_lockfile = truemakes S3 handle locking natively.
terraform {
backend "s3" {
bucket = "my-state-bucket"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true # S3 does it all! ✅
}
}
How it works:
- Terraform creates
terraform.tfstate.tflockalongside your state file - S3 manages lock acquisition/release atomically
- Optional: Keep DynamoDB for double-locking redundancy
-
S3 bucket policy needs
PutObjectfor*.tflockfiles ### Migration: 2 Minutes Flat #### Step 1: Verify compatibility
terraform version # Must be 1.9.0+
Step 2: Update your backend
# Remove dynamodb_table, add use_lockfile = true
Step 3: Re-init
terraform init -migrate-state
Step 4: Clean up
aws dynamodb delete-table --table-name terraform-locks
Done. Zero downtime, same locking guarantees.
Real-World Test
Scenario: Two terminals, same state file, rapid apply commands.
Before (DynamoDB):
Terminal 1: Acquiring lock via DynamoDB...
Terminal 2: [WAIT] Lock held by Terminal 1
After (S3 Native):
Terminal 1: Acquiring S3 lock...
Terminal 2: [WAIT] Lock held by Terminal 1 (via .tflock)
Identical behavior, one less AWS bill line item.
Gotchas & Requirements
| Item | Requirement |
|---|---|
| Terraform | 1.9.0+ |
| S3 Permissions |
PutObject on *.tflock files |
| Bucket Policy | Allow lock file creation |
| Existing State | terraform init -migrate-state |
Bucket policy update:
{
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": "arn:aws:s3:::my-state-bucket/prod/*.tflock"
}
Why This Changes Everything
❌ OLD: S3 state + DynamoDB lock = 2 resources
✅ NEW: S3 state + S3 lock = 1 resource
💰 SAVINGS: ~$3/year per state file
🔧 SIMPLICITY: One less failure point
Production teams: Delete 100+ DynamoDB lock tables across your org.
Solo devs: Zero extra resources for remote state.
CI/CD: Simpler IAM roles.
My Updated Workflow (2026)
# Every project now gets this backend
terraform {
backend "s3" {
bucket = "my-org-terraform-state"
key = "env/${terraform.workspace}/terraform.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true
}
}
Daily habit:
# Verify locks work
terraform plan # Should show "Acquiring state lock..."
Try It Now
- Create S3 bucket:
aws s3 mb s3://my-terraform-state-2026 - Update your EC2
.tfwith above backend terraform init- Push your local state:
terraform init -migrate-state
Result: Locked, remote, DynamoDB-free Terraform in 5 minutes.
S3 native locking makes Terraform state management finally "set it and forget it." No more lock table drift, no more surprise charges, no more complexity.
Already migrated? What was your experience? Still using DynamoDB? Why? 👇
Top comments (0)