Remote Backends: S3 Storage, DynamoDB State Locking & GCS
Stores state centrally with S3 bucket versioning and KMS customer-managed encryption.
Acquires a mutual exclusion lock before plan and apply to prevent concurrent team write races.
Run 'terraform init -migrate-state' to transfer local state to the cloud safely.
The Big Idea
Storing state on a developer's local laptop makes team collaboration impossible. If Engineer A adds an S3 bucket and Engineer B creates an RDS instance concurrently, their local state files will diverge. The engineer who applies last will overwrite or orphan the other's resources.
The enterprise solution is a Remote Backend. In AWS, this standard architecture uses two services: Amazon S3 for durable, encrypted storage, and Amazon DynamoDB for distributed state locking.
When an engineer runs terraform plan or apply, Terraform acquires an exclusive lock by writing a lock ID record into DynamoDB. If a colleague or CI/CD runner attempts to run an apply at the exact same moment, Terraform immediately halts with an Error acquiring state lock message, protecting your production infrastructure from catastrophic race conditions.
Jargon Buster: Plain-English Translations
- State Locking: A mechanism that locks the state during write operations to prevent concurrent modifications that could corrupt state.
- LockID: The mandatory primary string partition key required on the DynamoDB locking table.
- S3 Versioning: An S3 bucket feature that preserves past revisions of the state file, enabling instantaneous rollback if state is ever corrupted.
- State Migration (-migrate-state): The automated process of transferring state data when switching backend providers or keys.
Production Remote Backend & S3 Bootstrap Infrastructure
# Step 1: Bootstrap the S3 Bucket & DynamoDB Table for State
resource "aws_s3_bucket" "tf_state" {
bucket = "enterprise-production-terraform-state-2026"
force_destroy = false # Prevent accidental deletion
}
resource "aws_s3_bucket_versioning" "tf_state_versioning" {
bucket = aws_s3_bucket.tf_state.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_server_side_encryption_configuration" "tf_state_crypto" {
bucket = aws_s3_bucket.tf_state.id
rule {
apply_server_side_encryption_by_default { sse_algorithm = "AES256" }
}
}
resource "aws_dynamodb_table" "tf_locks" {
name = "enterprise-production-terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID" # Mandatory key for Terraform backend locking
attribute {
name = "LockID"
type = "S"
}
}
# Step 2: Configure the Backend in your Root Module
terraform {
backend "s3" {
bucket = "enterprise-production-terraform-state-2026"
key = "core/production/vpc.tfstate"
region = "us-east-1"
dynamodb_table = "enterprise-production-terraform-locks"
encrypt = true
}
}
Beginner Traps & Gotchas
1. DynamoDB table partition key typo
The Gotcha: Naming the DynamoDB hash key 'id' or 'lock_id' instead of the exact case-sensitive string 'LockID'.
The Fix: Terraform hardcodes the expectation for 'LockID'. Any other name will cause locking failures during terraform init.
2. Stuck state locks after aborted CI jobs or killed processes
The Gotcha: A CI process getting cancelled mid-apply, leaving the DynamoDB lock active and blocking all subsequent runs.
The Fix: Inspect the lock ID displayed in the error message, verify nobody else is executing, and run 'terraform force-unlock