Skip to main content

Command Palette

Search for a command to run...

Day 6: State Management Fundamentals

Updated
β€’10 min read
Day 6: State Management Fundamentals
S

I'm a cloud-native enthusiast and tech blogger, sharing insights on Kubernetes, AWS, CI/CD, and Linux across my blog and Facebook page. Passionate about modern infrastructure and microservices, I aim to help others understand and leverage cloud-native technologies for scalable, efficient solutions.

Welcome to Day 6 - the final day of Week 1! Today we’ll dive deep into Terraform state, one of the most critical concepts in Terraform. Understanding state management is essential for working with Terraform professionally.

🎯 Today’s Goals

  • Understand what Terraform state is and why it matters

  • Learn state file structure and contents

  • Master state commands

  • Configure remote state backends

  • Implement S3 backend with state locking

  • Learn state best practices and security

πŸ“¦ What is Terraform State?

Terraform state is a JSON file that maps your configuration to real-world resources. It’s Terraform’s memory of what infrastructure exists.

Why State Exists

Your Code      |    State File         | Real World
(.tf files)    | (terraform.tfstate)   | (AWS)
               |                       |
resource "     |   {                   | β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
aws_instance"  |   "resources": [      β”‚ | EC2     β”‚
"web" {        |   {                   β”‚ | Instanceβ”‚
  ...          |   "type":             β”‚ | i-12345 β”‚
}              |   "aws_instance",     | β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               |   "name": "web",      |
               |  "instances": [       |
               |     {                 |
               |       "id": "i-12345" |
               |     }                 |
               |   ]                   |
               | }]                    |
               |}                      |

      β–²                  β–²                      β–²
      β”‚                  β”‚                      β”‚
      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              Terraform uses state to map
              code to real infrastructure

What State Stores

  1. Resource IDs - Links code to actual resources

  2. Attributes - Current resource configuration

  3. Metadata - Dependencies, provider info

  4. Outputs - Output values from your configuration

πŸ” State File Structure

Let’s examine a typical state file:

{  "version": 4,  "terraform_version": "1.6.0",  "serial": 1,  "lineage": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",  "outputs": {    "instance_id": {      "value": "i-1234567890abcdef0",      "type": "string"    }  },  "resources": [    {      "mode": "managed",      "type": "aws_instance",      "name": "web",      "provider": "provider[\\"registry.terraform.io/hashicorp/aws\\"]",      "instances": [        {          "schema_version": 1,          "attributes": {            "id": "i-1234567890abcdef0",            "ami": "ami-0c55b159cbfafe1f0",            "instance_type": "t2.micro",            "public_ip": "54.123.45.67",            ...          }        }      ]    }  ]}

Key Fields

  • version: State file format version

  • terraform_version: Terraform version used

  • serial: Increments with each state change

  • lineage: Unique ID for this state file

  • resources: All managed resources

  • outputs: Output values

🚨 State File Warnings

⚠️ IMPORTANT: State files contain sensitive data!

{  "resources": [    {      "type": "aws_db_instance",      "attributes": {        "password": "supersecret123",  // πŸ”΄ Sensitive!        "endpoint": "db.example.com",        ...      }    }  ]}

State files may contain:

  • Passwords

  • API keys

  • Private IPs

  • SSH keys

  • Any sensitive resource attributes

Security rules:

  • ❌ Never commit state to version control

  • ❌ Never share state files publicly

  • βœ… Use remote state with encryption

  • βœ… Enable state locking

  • βœ… Restrict access to state

πŸŽ›οΈ State Commands

Terraform provides commands to inspect and manage state:

1. Show State

# Show all resources
terraform show

# Show in JSON format
terraform show -json

# Show specific resource
terraform state show aws_instance.web

2. List Resources

# List all resources in state
terraform state list

# Filter by pattern
terraform state list | grep vpc

3. Move Resources

# Rename resource in state
terraform state mv aws_instance.web aws_instance.webserver

# Move resource to a module
terraform state mv aws_instance.web module.web.aws_instance.server

4. Remove Resources

# Remove from state (doesn't delete real resource)
terraform state rm aws_instance.web

# Remove all instances of a resource type
terraform state rm 'aws_subnet.public[*]'

5. Pull/Push State

# Download remote state to stdout
terraform state pull

# Manually upload state (dangerous!)
terraform state push terraform.tfstate

6. Replace Provider

# Update provider in state
terraform state replace-provider hashicorp/aws registry.terraform.io/hashicorp/aws

πŸ’Ύ Local vs Remote State

Local State (Default)

project/
β”œβ”€β”€ main.tf
β”œβ”€β”€ terraform.tfstate         ← Stored locally
└── terraform.tfstate.backup  ← Previous version

Pros:

  • Simple setup

  • No additional configuration

  • Fast access

Cons:

  • ❌ No collaboration (can’t share)

  • ❌ No locking (risk of concurrent changes)

  • ❌ No encryption at rest

  • ❌ Risk of loss (local file)

State stored in shared location (S3, Terraform Cloud, etc.)

Pros:

  • βœ… Team collaboration

  • βœ… State locking prevents conflicts

  • βœ… Encryption at rest

  • βœ… Versioning and backup

  • βœ… Audit trail

Cons:

  • Requires additional setup

  • Network dependency

πŸͺ£ Remote State with S3

The most common remote backend is S3 + DynamoDB:

  • S3: Stores state file

  • DynamoDB: Provides state locking

Backend Configuration

terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "project/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

πŸ§ͺ Hands-On Lab: Remote State with S3

Let’s set up remote state storage with S3 and DynamoDB!

Step 1: Create State Backend Infrastructure

First, we need to create S3 bucket and DynamoDB table. Create a new directory:

mkdir terraform-state-backend
cd terraform-state-backend

Create backend-setup.tf:

# backend-setup.tf

terraform {
  required_version = ">= 1.0"

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

provider "aws" {
  region = "us-east-1"
}

# Random suffix for unique bucket name
resource "random_id" "bucket_suffix" {
  byte_length = 4
}

# S3 Bucket for Terraform State
resource "aws_s3_bucket" "terraform_state" {
  bucket = "terraform-state-${random_id.bucket_suffix.hex}"

  tags = {
    Name        = "Terraform State Bucket"
    Environment = "Learning"
  }
}

# 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 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 = "Learning"
  }
}

# Outputs
output "s3_bucket_name" {
  description = "S3 bucket name for state"
  value       = aws_s3_bucket.terraform_state.id
}

output "dynamodb_table_name" {
  description = "DynamoDB table name for locking"
  value       = aws_dynamodb_table.terraform_locks.name
}

output "backend_config" {
  description = "Backend configuration to use"
  value = <<-EOT
    terraform {
      backend "s3" {
        bucket         = "${aws_s3_bucket.terraform_state.id}"
        key            = "project/terraform.tfstate"
        region         = "us-east-1"
        encrypt        = true
        dynamodb_table = "${aws_dynamodb_table.terraform_locks.name}"
      }
    }
  EOT
}

Add terraform.tf for the random provider:

# terraform.tf
terraform {
  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
  }
}

Step 2: Create Backend Infrastructure

terraform init
terraform apply

Type yes to create the S3 bucket and DynamoDB table.

Save the outputs! You’ll need them.

# Save bucket name
terraform output -raw s3_bucket_name > ../bucket_name.txt

# View the backend config
terraform output backend_config

Step 3: Create a Project with Remote State

Create a new directory:

cd ..
mkdir terraform-remote-state-demo
cd terraform-remote-state-demo

Step 4: Configure Remote Backend

Create backend.tf:

# backend.tf

terraform {
  backend "s3" {
    bucket         = "terraform-state-XXXX"  # Replace with your bucket name
    key            = "demo/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

Replace terraform-state-XXXX with your actual bucket name from step 2!

Step 5: Create Resources

Create main.tf:

# main.tf

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

provider "aws" {
  region = "us-east-1"
}

# VPC
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "remote-state-demo-vpc"
  }
}

# Subnet
resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"

  tags = {
    Name = "remote-state-demo-subnet"
  }
}

# Security Group
resource "aws_security_group" "web" {
  name        = "remote-state-demo-sg"
  description = "Demo security group"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "remote-state-demo-sg"
  }
}

Create outputs.tf:

# outputs.tf

output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.main.id
}

output "subnet_id" {
  description = "Subnet ID"
  value       = aws_subnet.public.id
}

output "security_group_id" {
  description = "Security Group ID"
  value       = aws_security_group.web.id
}

Step 6: Initialize with Remote Backend

terraform init

You’ll see:

Initializing the backend...

Successfully configured the backend "s3"!

Step 7: Apply Configuration

terraform apply

Type yes to create resources.

Step 8: Verify State in S3

# List state files in S3
aws s3 ls s3://terraform-state-XXXX/demo/

# Download and view state
aws s3 cp s3://terraform-state-XXXX/demo/terraform.tfstate - | jq '.'

Step 9: Test State Locking

Terminal 1:

terraform plan
# Keep this running

Terminal 2 (while Terminal 1 is still running):

terraform plan

You should see:

Error: Error acquiring the state lock

Lock Info:
  ID:        abc123...
  Path:      terraform-state-XXXX/demo/terraform.tfstate
  Operation: OperationTypeApply
  Who:       user@hostname
  Version:   1.6.0
  Created:   2024-01-15 10:30:00

This proves state locking is working! βœ…

Step 10: Verify State in DynamoDB

# Check lock table
aws dynamodb scan --table-name terraform-state-lock

# During an active plan/apply, you'll see:{  "Items": [
    {      "LockID": {
        "S": "terraform-state-XXXX/demo/terraform.tfstate"      },      "Info": {
        "S": "{\\"ID\\":\\"...\\",\\"Operation\\":\\"OperationTypePlan\\",\\"Who\\":\\"user@hostname\\",...}"      }    }  ]}

Step 11: Inspect State Commands

# List resources (reads from S3)
terraform state list

# Show specific resource
terraform state show aws_vpc.main

# Pull state locally for inspection
terraform state pull > local_state.json
cat local_state.json | jq '.resources'

Step 12: Clean Up

# Destroy project resources
terraform destroy

# Go back and destroy backend infrastructure
cd ../terraform-state-backend
terraform destroy

πŸ” State Security Best Practices

1. Use Remote State

terraform {
  backend "s3" {
    bucket  = "terraform-state"
    encrypt = true  # βœ… Always encrypt!
  }
}

2. Enable Versioning

resource "aws_s3_bucket_versioning" "state" {
  versioning_configuration {
    status = "Enabled"  # βœ… Recover from accidents
  }
}

3. Restrict Access

# Use IAM policies to restrict access
{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:PutObject"],
  "Resource": "arn:aws:s3:::terraform-state/*",
  "Condition": {
    "StringEquals": {
      "aws:userid": ["allowed-user-id"]
    }
  }
}

4. Use Separate States

  • Different states for different environments

  • Blast radius limitation

s3://terraform-state/
β”œβ”€β”€ dev/terraform.tfstate
β”œβ”€β”€ staging/terraform.tfstate
└── prod/terraform.tfstate

πŸ“Š State Best Practices

βœ… DO:

  1. Use remote state for team projects

  2. Enable state locking to prevent conflicts

  3. Encrypt state at rest (S3 encryption)

  4. Enable versioning for recovery

  5. Use separate states per environment

  6. Back up state files regularly

  7. Use terraform state commands carefully

❌ DON’T:

  1. Don’t commit state to git

     # .gitignore
     *.tfstate
     *.tfstate.backup
    
  2. Don’t manually edit state files

    • Use terraform state commands instead
  3. Don’t share state files publicly

  4. Don’t use same state for all environments

  5. Don’t forget to lock the state.

πŸ†˜ State Troubleshooting

Problem: State Drift

Resources changed outside Terraform.

# Detect drift
terraform plan

# Refresh state
terraform apply -refresh-only

Problem: Lost State

State file deleted or corrupted.

# With S3 versioning
aws s3api list-object-versions --bucket terraform-state
# Restore previous version
aws s3api get-object --bucket terraform-state --key terraform.tfstate --version-id VERSION_ID terraform.tfstate

Problem: State Lock Stuck

Lock not released after the crash.

# Force unlock (use carefully!)
terraform force-unlock LOCK_ID

πŸ“ Summary

Today you learned:

  • βœ… What Terraform state is and why it exists

  • βœ… State file structure and contents

  • βœ… State security considerations

  • βœ… Essential state commands

  • βœ… Local vs remote state

  • βœ… S3 + DynamoDB backend setup

  • βœ… State locking mechanism

  • βœ… State best practices and troubleshooting

πŸŽ‰ Week 1 Complete!

Congratulations! You’ve completed Week 1 and learned:

  • Infrastructure as Code fundamentals

  • Terraform installation and setup

  • Providers and their configuration

  • Variables and outputs

  • Resource dependencies and data sources

  • State management

πŸš€ Week 2 Preview

Day 8: Terraform CLI Commands Deep Dive

Next week we’ll:

  • Master all Terraform CLI commands

  • Learn workspace management

  • Explore advanced variable types

  • Work with complex data structures

  • Build more sophisticated infrastructure

πŸ’­ Weekend Challenge

  1. Create a remote state backend in your AWS account

  2. Build a multi-tier application with remote state

  3. Practice state commands

  4. Set up separate states for dev and prod


← Day 5: Dependencies & Data Sources | Day 8: Terraform CLI Deep Dive β†’


Remember: Proper state management is the foundation of reliable infrastructure automation!

🎊 Enjoy your Sunday rest! See you on Monday for Week 2!

T

Thank you!

More from this blog

S

StackOps - Diary

33 posts

Welcome to the StackOps - Diary. We’re dedicated to empowering the tech community. We delve into cloud-native and microservices technologies, sharing knowledge to build modern, scalable solutions.