Terraform Remote State: Locking with S3 and DynamoDB
Naveen Teja
3/2/2026

When multiple engineers or CI/CD pipelines run Terraform simultaneously against the same infrastructure, state file corruption becomes a serious risk. The default local state file offers no protection against concurrent runs — two engineers could apply conflicting changes simultaneously, resulting in a corrupted state and unrecoverable infrastructure drift.
The production-grade solution is storing Terraform state remotely in Amazon S3 with state locking provided by a DynamoDB table. S3 provides durable, versioned storage for the state file, while DynamoDB implements a pessimistic locking mechanism. Before any plan or apply operation, Terraform writes a lock entry to DynamoDB. Any subsequent run that detects an existing lock will immediately halt, preventing concurrent modifications.
Enabling S3 versioning is critical — it allows you to roll back to a previous known-good state if a bad apply corrupts the current one. You should also enable server-side encryption on the bucket since the state file contains plaintext sensitive values like passwords and API keys. The Terraform configuration below provisions the complete remote state backend including bucket versioning, encryption, public access blocking, and the DynamoDB lock table.
resource "aws_s3_bucket" "terraform_state" {
bucket = "your-org-terraform-state"
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_versioning" "state_versioning" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "state_enc" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "state_block" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-state-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
# Add this to your backend.tf
# terraform {
# backend "s3" {
# bucket = "your-org-terraform-state"
# key = "prod/terraform.tfstate"
# region = "us-east-1"
# dynamodb_table = "terraform-state-locks"
# encrypt = true
# }
# }You might also like

Migrating from EC2 to AWS Fargate: A Step-by-Step Guide

Multi-Region Active-Active Architecture on AWS

Implementing AWS GuardDuty with Automated Threat Response

OpenTofu vs Terraform in 2024: Migration Guide and Key Differences

Zero-Trust Networking on AWS with IAM Identity Center and SCPs
