Terraform Advanced — S3 + DynamoDB Backend

1 minute read

The gold standard for AWS Terraform state: S3 for storage, DynamoDB for locking.


Architecture

┌─────────────────────────────────────────┐
│  terraform apply                        │
│    ↓                                    │
│  1. Acquire lock (DynamoDB)             │
│  2. Read state (S3)                     │
│  3. Plan & apply changes                │
│  4. Write updated state (S3)            │
│  5. Release lock (DynamoDB)             │
└─────────────────────────────────────────┘

Create the Backend Infrastructure

# bootstrap/main.tf — Run this ONCE with local state
provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "terraform_state" {
  bucket = "mycompany-terraform-state"

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "aws:kms"
    }
  }
}

resource "aws_s3_bucket_public_access_block" "terraform_state" {
  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-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

Configure the Backend

# terraform.tf
terraform {
  backend "s3" {
    bucket         = "mycompany-terraform-state"
    key            = "prod/networking/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

State Locking in Action

$ terraform apply

Acquiring state lock. This may take a few moments...
# DynamoDB entry created:
#   LockID: "mycompany-terraform-state/prod/networking/terraform.tfstate"
#   Info:   {"ID":"...","Operation":"OperationTypeApply",...}

# Apply completes...

Releasing state lock. This may take a few moments...

If another engineer runs terraform apply at the same time:

$ terraform apply
Error: Error acquiring the state lock
  Lock Info:
    ID:        abc-123
    Path:      prod/networking/terraform.tfstate
    Operation: OperationTypeApply
    Who:       alice@laptop
    Created:   2025-07-01 10:30:00 UTC

What’s Next?

Encrypt state at rest.

← Remote State State Encryption →

Updated: