⏱ 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 changesLocal 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 historyState 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_IDTerraform 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