🔧 Week 7: Infrastructure as Code

Day 4: Terraform State Management

⏱ Duration: 5 Hours

📚 Learning Objectives

  • Understand Terraform state and its importance
  • Configure remote state with S3 backend
  • Use state locking with DynamoDB
  • Work with Terraform workspaces

📖 Core Concepts (2 Hours)

What is Terraform State?

Terraform state is how Terraform knows what resources it manages and their current configuration.

terraform.tfstate - JSON file containing: ┌─────────────────────────────────────────────┐ │ • Resource IDs (aws_instance.web.id) │ │ • Resource attributes (IP, ARN, etc.) │ │ • Dependencies between resources │ │ • Metadata about the Terraform run │ └─────────────────────────────────────────────┘ Why State Matters: • Maps config to real resources • Tracks metadata and dependencies • Performance - caches resource attributes • Enables terraform plan to show changes

Local vs Remote State

Local State (Default): ┌──────────────────────────────────────────┐ │ Developer A Developer B │ │ ┌──────────┐ ┌──────────┐ │ │ │ .tfstate │ │ .tfstate │ │ │ └────┬─────┘ └────┬─────┘ │ │ │ │ Different! │ │ ▼ ▼ │ │ AWS Resources AWS Resources │ │ (conflicts!) (conflicts!) │ └──────────────────────────────────────────┘ Remote State (Best Practice): ┌──────────────────────────────────────────┐ │ Developer A Developer B │ │ │ │ │ │ └────────┬────────┘ │ │ ▼ │ │ ┌───────────┐ │ │ │ S3 State │ ← Single source │ │ └─────┬─────┘ of truth │ │ │ │ │ ▼ │ │ AWS Resources │ └──────────────────────────────────────────┘

S3 Backend Configuration

# First, create the S3 bucket and DynamoDB table # (Usually done once manually or with separate Terraform) # backend.tf terraform { backend "s3" { bucket = "my-terraform-state-bucket" key = "project-name/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "terraform-state-lock" } } # Benefits: # ✓ Shared state across team # ✓ State locking prevents conflicts # ✓ Encryption at rest # ✓ Versioning for state history

State Locking with DynamoDB

# DynamoDB table for state locking resource "aws_dynamodb_table" "terraform_lock" { name = "terraform-state-lock" billing_mode = "PAY_PER_REQUEST" hash_key = "LockID" attribute { name = "LockID" type = "S" } tags = { Name = "Terraform State Lock Table" } } # How locking works: ┌─────────────────────────────────────────────┐ │ Developer A: terraform apply │ │ 1. Acquires lock in DynamoDB │ │ 2. Reads state from S3 │ │ 3. Makes changes │ │ 4. Writes state to S3 │ │ 5. Releases lock │ ├─────────────────────────────────────────────┤ │ Developer B: terraform apply (same time) │ │ 1. Tries to acquire lock │ │ 2. BLOCKED! Lock held by Developer A │ │ 3. Waits or errors out │ └─────────────────────────────────────────────┘

State Commands

# List resources in state terraform state list # Show details of a resource terraform state show aws_instance.web # Remove resource from state (doesn't delete actual resource) terraform state rm aws_instance.web # Move/rename resource in state terraform state mv aws_instance.web aws_instance.web_server # Import existing resource into state terraform import aws_instance.web i-1234567890abcdef0 # Pull remote state to local file terraform state pull > state.json # Push local state to remote terraform state push state.json # Force unlock (use carefully!) terraform force-unlock LOCK_ID

Terraform Workspaces

# Workspaces allow multiple state files per configuration # Great for dev/staging/prod environments # List workspaces terraform workspace list # * default # Create new workspace terraform workspace new dev terraform workspace new staging terraform workspace new prod # Switch workspace terraform workspace select dev # Show current workspace terraform workspace show # Use workspace in configuration resource "aws_instance" "web" { ami = data.aws_ami.amazon_linux.id instance_type = terraform.workspace == "prod" ? "t3.large" : "t2.micro" tags = { Name = "web-${terraform.workspace}" Environment = terraform.workspace } } # State files stored as: # s3://bucket/env:/dev/terraform.tfstate # s3://bucket/env:/staging/terraform.tfstate # s3://bucket/env:/prod/terraform.tfstate

🔬 Hands-on Lab (2.5 Hours)

Lab 1: Create Backend Infrastructure

# Create backend setup project mkdir -p ~/terraform-labs/lab4-backend cd ~/terraform-labs/lab4-backend # main.tf - Creates S3 and DynamoDB for state cat > main.tf << 'EOF' terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = "us-east-1" } # Random suffix for unique bucket name resource "random_id" "suffix" { byte_length = 4 } # S3 bucket for state resource "aws_s3_bucket" "terraform_state" { bucket = "terraform-state-${random_id.suffix.hex}" lifecycle { prevent_destroy = false # Set to true in production } tags = { Name = "Terraform State Bucket" Purpose = "Terraform Remote State" } } # Enable versioning resource "aws_s3_bucket_versioning" "terraform_state" { bucket = aws_s3_bucket.terraform_state.id versioning_configuration { status = "Enabled" } } # Enable 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" } } } # Block public access 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 locking resource "aws_dynamodb_table" "terraform_lock" { name = "terraform-state-lock" billing_mode = "PAY_PER_REQUEST" hash_key = "LockID" attribute { name = "LockID" type = "S" } tags = { Name = "Terraform Lock Table" Purpose = "Terraform State Locking" } } output "state_bucket_name" { value = aws_s3_bucket.terraform_state.id } output "dynamodb_table_name" { value = aws_dynamodb_table.terraform_lock.name } EOF # Apply to create backend infrastructure terraform init terraform apply -auto-approve # Save the bucket name! BUCKET_NAME=$(terraform output -raw state_bucket_name) echo "State bucket: $BUCKET_NAME"

Lab 2: Use Remote Backend

# Create a project that uses remote state mkdir -p ~/terraform-labs/lab4-remote cd ~/terraform-labs/lab4-remote # Get bucket name from previous lab cd ~/terraform-labs/lab4-backend BUCKET_NAME=$(terraform output -raw state_bucket_name) cd ~/terraform-labs/lab4-remote # Create configuration with remote backend cat > main.tf << EOF terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } backend "s3" { bucket = "$BUCKET_NAME" key = "demo/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "terraform-state-lock" } } provider "aws" { region = "us-east-1" } resource "aws_s3_bucket" "demo" { bucket = "demo-app-\${random_id.suffix.hex}" tags = { Name = "Demo App Bucket" Environment = terraform.workspace } } resource "random_id" "suffix" { byte_length = 4 } output "bucket_name" { value = aws_s3_bucket.demo.id } EOF # Initialize with remote backend terraform init # Apply terraform apply -auto-approve # Notice: no local terraform.tfstate file! ls -la # State is stored in S3!

Lab 3: Work with Workspaces

# List workspaces terraform workspace list # Create dev workspace terraform workspace new dev terraform apply -auto-approve # Create prod workspace terraform workspace new prod terraform apply -auto-approve # List workspaces again terraform workspace list # Check S3 - you'll see multiple state files aws s3 ls s3://$BUCKET_NAME/env:/ --recursive # Switch between workspaces terraform workspace select dev terraform output terraform workspace select prod terraform output # Clean up all workspaces for ws in dev prod; do terraform workspace select $ws terraform destroy -auto-approve done terraform workspace select default terraform workspace delete dev terraform workspace delete prod terraform destroy -auto-approve

✅ Day 4 Checklist

  • Understand why Terraform state is important
  • Can configure S3 remote backend
  • Know how state locking prevents conflicts
  • Can use terraform state commands
  • Understand workspaces for multiple environments