Matt Karmazyn

# Terraform State Setup

Time to Complete: 30-60 minutes
Prerequisites: AWS Account Setup completed

# Overview

Set up remote state storage to enable team collaboration and prevent state conflicts. This is a required foundational step before deploying any infrastructure with Terraform.

Account Scope: Run all commands in the management/root account using the terraform-admin IAM user profile.


# Why Remote State?


  1. Create S3 Bucket for State
  2. Create DynamoDB Table for Locking
  3. Import State Resources into Terraform

# Step 4: Import State Resources into Terraform

Now bring the manually created resources under Terraform management.

# Create Terraform Configuration

mkdir -p terraform/state-backend
cd terraform/state-backend

Create main.tf:

terraform {
  required_version = ">= 1.0"
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region  = "us-east-2"
  profile = "terraform-admin"
}

# S3 bucket for Terraform state
resource "aws_s3_bucket" "terraform_state" {
  bucket = "my-company-terraform-state"

  tags = {
    Name        = "Terraform State Bucket"
    Environment = "Management"
    ManagedBy   = "Terraform"
  }
}

# S3 bucket versioning
resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  versioning_configuration {
    status = "Enabled"
  }
}

# S3 bucket encryption
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 = "AES256"
    }
  }
}

# S3 bucket public access block
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
}

# DynamoDB table for state locking
resource "aws_dynamodb_table" "terraform_locks" {
  name         = "terraform-state-lock"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

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

  tags = {
    Name        = "Terraform State Lock Table"
    Environment = "Management"
    ManagedBy   = "Terraform"
  }
}

# Initialize Terraform

# Initialize Terraform (using local state for now)
terraform init

# Verify configuration is valid
terraform validate

# Import Existing Resources

# Import S3 bucket
terraform import aws_s3_bucket.terraform_state my-company-terraform-state

# Import S3 bucket versioning
terraform import aws_s3_bucket_versioning.terraform_state my-company-terraform-state

# Import S3 bucket encryption
terraform import aws_s3_bucket_server_side_encryption_configuration.terraform_state my-company-terraform-state

# Import S3 bucket public access block
terraform import aws_s3_bucket_public_access_block.terraform_state my-company-terraform-state

# Import DynamoDB table
terraform import aws_dynamodb_table.terraform_locks terraform-state-lock

# Verify Import

# Show current state
terraform state list

# Expected output:
# aws_s3_bucket.terraform_state
# aws_s3_bucket_versioning.terraform_state
# aws_s3_bucket_server_side_encryption_configuration.terraform_state
# aws_s3_bucket_public_access_block.terraform_state
# aws_dynamodb_table.terraform_locks

# Verify no changes needed
terraform plan

# Should show: "No changes. Your infrastructure matches the configuration."

# Step 5: Migrate to Remote State

Create backend.tf in the same directory:

terraform {
  backend "s3" {
    bucket         = "my-company-terraform-state"
    key            = "management/state-backend/terraform.tfstate"
    region         = "us-east-2"
    dynamodb_table = "terraform-state-lock"
    encrypt        = true
    profile        = "terraform-admin"
  }
}

Migrate the state:

# Reinitialize with new backend
terraform init -migrate-state

# Terraform will ask: "Do you want to copy existing state to the new backend?"
# Type: yes

# Verify state is now in S3
aws s3 ls s3://my-company-terraform-state/management/state-backend/ --profile terraform-admin

# You should see: terraform.tfstate

# Clean up local state files (optional)
rm -f terraform.tfstate terraform.tfstate.backup

# Step 6: Test State Locking

# Run a plan (should acquire lock)
terraform plan

# In another terminal, try to run plan simultaneously
# It should wait for the lock to be released

# Step 7: Commit Configuration

# In your terraform/state-backend directory
cat > .gitignore <<EOF
# Local state files
*.tfstate
*.tfstate.backup
.terraform/
.terraform.lock.hcl

# Sensitive variable files
*.tfvars
*.auto.tfvars
EOF

# Add and commit
git add main.tf backend.tf .gitignore
git commit -m "Add Terraform config for state backend infrastructure"

# What We Accomplished

S3 Bucket: Created and configured for Terraform state storage
DynamoDB Table: Created for state locking
Terraform Management: State resources now managed by Terraform
Remote State: State migrated to S3 with locking enabled
Version Control: Terraform configuration committed to Git


# Validation Checklist

# Verify Terraform state is in S3
aws s3 ls s3://my-company-terraform-state/management/state-backend/ --profile terraform-admin

# Verify state locking works
terraform plan
# Should show lock being acquired and released

# Verify Git commit
git log --oneline -1
# Should show your commit

Expected outputs:


# Cost Estimate


← AWS Account Setup Basic Security Setup →