Lesson 6 of 16
Section 2: Deep Dive into State, Remote Backends & Drift Management40 min read & walkthrough

Remote Backends: S3 Storage, DynamoDB State Locking & GCS

01 / S3 BACKEND
Durable Storage

Stores state centrally with S3 bucket versioning and KMS customer-managed encryption.

02 / DYNAMODB
State Locking

Acquires a mutual exclusion lock before plan and apply to prevent concurrent team write races.

03 / MIGRATION
Zero-Loss Cutover

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 ' to release it.