Skip to main content

AWS Backend Setup

Configure S3 bucket and DynamoDB table for Terraform remote state management with encryption, versioning, and state locking.

Overview

Terraform backend stores infrastructure state in S3 with DynamoDB for state locking, preventing concurrent modifications.

Benefits

  • Team collaboration: Shared state across team members
  • State locking: Prevents concurrent modifications
  • Version history: S3 versioning for rollback capability
  • Encryption: KMS encryption for sensitive data
  • Audit trail: Access logging for compliance

Architecture

Quick Setup

1

Navigate to backend setup

cd terraform/backend-setup
2

Create terraform.tfvars

project_name = "mcp-langgraph"
environment  = "prod"
region       = "us-east-1"
3

Initialize and deploy

terraform init
terraform plan
terraform apply
Creates:
  • S3 bucket: mcp-langgraph-terraform-state-prod
  • DynamoDB table: mcp-langgraph-terraform-lock-prod
  • KMS key for encryption
  • S3 bucket for access logs
4

Note the outputs

terraform output
Save these values for backend configuration:
  • state_bucket_name
  • lock_table_name

Backend Configuration

For Environment Deployments

# terraform/environments/prod/main.tf
terraform {
  backend "s3" {
    bucket         = "mcp-langgraph-terraform-state-prod"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "mcp-langgraph-terraform-lock-prod"
    encrypt        = true
  }
}

Initialize Backend

cd terraform/environments/prod
terraform init

# You'll see:
# Initializing the backend...
# Successfully configured the backend "s3"!

Features

S3 State Bucket

  • Versioning
  • Encryption
  • Access Logging
  • Lifecycle
Enabled for rollback capability
# List all versions
aws s3api list-object-versions \
  --bucket mcp-langgraph-terraform-state-prod \
  --prefix prod/terraform.tfstate

# Restore previous version if needed
aws s3api copy-object \
  --bucket mcp-langgraph-terraform-state-prod \
  --copy-source mcp-langgraph-terraform-state-prod/prod/terraform.tfstate?versionId=VERSION_ID \
  --key prod/terraform.tfstate

DynamoDB Lock Table

  • State Locking
  • On-Demand Billing
Prevents concurrent Terraform runs
# Check for active locks
aws dynamodb scan \
  --table-name mcp-langgraph-terraform-lock-prod \
  --query "Items[].LockID.S"

# Should be empty when no terraform running

Multiple Environments

Separate State Per Environment

# Production
terraform {
  backend "s3" {
    bucket = "mcp-langgraph-terraform-state-prod"
    key    = "prod/terraform.tfstate"
    # ...
  }
}

# Staging
terraform {
  backend "s3" {
    bucket = "mcp-langgraph-terraform-state-staging"
    key    = "staging/terraform.tfstate"
    # ...
  }
}

# Development
terraform {
  backend "s3" {
    bucket = "mcp-langgraph-terraform-state-dev"
    key    = "dev/terraform.tfstate"
    # ...
  }
}

Same Bucket, Different Keys

terraform {
  backend "s3" {
    bucket = "mcp-langgraph-terraform-state"
    key    = "${environment}/terraform.tfstate"  # Variable not allowed here
    # Must hardcode environment name in each environment's config
  }
}

Security Best Practices

Restrict access to authorized users only
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::123456789012:user/terraform-admin",
          "arn:aws:iam::123456789012:role/github-actions"
        ]
      },
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::mcp-langgraph-terraform-state-prod/*"
    }
  ]
}
Minimal permissions for state locking
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:PutItem",
        "dynamodb:GetItem",
        "dynamodb:DeleteItem"
      ],
      "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/mcp-langgraph-terraform-lock-prod"
    }
  ]
}
Require MFA to delete state versions
# Enable MFA delete (requires root account)
aws s3api put-bucket-versioning \
  --bucket mcp-langgraph-terraform-state-prod \
  --versioning-configuration Status=Enabled,MFADelete=Enabled \
  --mfa "arn:aws:iam::123456789012:mfa/root-account-mfa-device XXXXXX"

Disaster Recovery

State Backup

1

Manual backup

# Download current state
aws s3 cp s3://mcp-langgraph-terraform-state-prod/prod/terraform.tfstate \
  ./terraform.tfstate.backup-$(date +%Y%m%d)
2

Cross-region replication

resource "aws_s3_bucket_replication_configuration" "state" {
  bucket = aws_s3_bucket.terraform_state.id
  role   = aws_iam_role.replication.arn

  rule {
    id     = "replicate-state"
    status = "Enabled"

    destination {
      bucket        = aws_s3_bucket.terraform_state_replica.arn
      storage_class = "STANDARD_IA"

      encryption_configuration {
        replica_kms_key_id = aws_kms_key.replica.arn
      }
    }
  }
}

State Recovery

1

List versions

aws s3api list-object-versions \
  --bucket mcp-langgraph-terraform-state-prod \
  --prefix prod/terraform.tfstate \
  --query 'Versions[].[VersionId,LastModified,IsLatest]' \
  --output table
2

Restore specific version

# Download specific version
aws s3api get-object \
  --bucket mcp-langgraph-terraform-state-prod \
  --key prod/terraform.tfstate \
  --version-id VERSION_ID \
  terraform.tfstate.restored

# Upload as current version
aws s3 cp terraform.tfstate.restored \
  s3://mcp-langgraph-terraform-state-prod/prod/terraform.tfstate
3

Verify state

cd terraform/environments/prod
terraform init
terraform plan  # Should show no changes if restored correctly

Troubleshooting

Cause: S3 bucket doesn’t exist or no accessSolution:
# Verify bucket exists
aws s3 ls s3://mcp-langgraph-terraform-state-prod

# Check IAM permissions
aws iam get-user
aws iam list-user-policies --user-name YOUR_USER
Cause: Previous Terraform run didn’t release lock (e.g., Ctrl+C during apply)Solution:
# Check for stuck lock
aws dynamodb scan \
  --table-name mcp-langgraph-terraform-lock-prod

# Force unlock (use with caution!)
terraform force-unlock LOCK_ID
Cause: Backend not created yetSolution:
# Run backend setup first
cd terraform/backend-setup
terraform init
terraform apply

Cost

ComponentMonthly CostNotes
S3 Storage~$0.02~1 MB state file
S3 Requests~$0.01Minimal GET/PUT
DynamoDB~$0.01Pay-per-request
KMS$1.00First 20K requests/month free
Total~$1.04/monthNegligible cost