Skip to content

Terraform on AWS


Terraform is an open-source Infrastructure as Code (IaC) tool that enables you to define and provision infrastructure using a high-level configuration language.

Terraform Overview
+------------------------------------------------------------------+
| |
| +------------------------+ |
| | Terraform | |
| +------------------------+ |
| | |
| +---------------------+---------------------+ |
| | | | | |
| v v v v |
| +----------+ +----------+ +----------+ +----------+ |
| | HCL | | State | | Providers| | Modules | |
| | Config | | Management| | | | | |
| | | | | | | | | |
| | - Declar | | - Track | | - AWS | | - Reuse | |
| | ative | | Infra | | - Azure | | - Share | |
| | - Idempo | | - Lock | | - GCP | | - Version| |
| +----------+ +----------+ +----------+ +----------+ |
| |
+------------------------------------------------------------------+
FeatureDescription
HCLHashiCorp Configuration Language
StateTracks infrastructure state
ProvidersCloud platform integrations
ModulesReusable configurations

Terraform Configuration Structure
+------------------------------------------------------------------+
| |
| main.tf |
| +------------------------------------------------------------+ |
| | | |
| | # Provider configuration | |
| | provider "aws" { | |
| | region = "us-east-1" | |
| | } | |
| | | |
| | # Resource definition | |
| | resource "aws_vpc" "main" { | |
| | cidr_block = "10.0.0.0/16" | |
| | tags = { | |
| | Name = "main-vpc" | |
| | } | |
| | } | |
| | | |
| +------------------------------------------------------------+ |
| |
| variables.tf |
| +------------------------------------------------------------+ |
| | | |
| | variable "region" { | |
| | type = string | |
| | default = "us-east-1" | |
| | description = "AWS region" | |
| | } | |
| | | |
| +------------------------------------------------------------+ |
| |
| outputs.tf |
| +------------------------------------------------------------+ |
| | | |
| | output "vpc_id" { | |
| | value = aws_vpc.main.id | |
| | description = "VPC ID" | |
| | } | |
| | | |
| +------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
# Basic resource syntax
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "WebServer"
Environment = "Production"
}
}
# Resource with dependencies
resource "aws_eip" "web_eip" {
instance = aws_instance.web_server.id
vpc = true
}
# Resource with count
resource "aws_instance" "server" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "Server-${count.index + 1}"
}
}
# Resource with for_each
resource "aws_instance" "server" {
for_each = toset(["web", "api", "db"])
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "Server-${each.key}"
}
}

Terraform State Management
+------------------------------------------------------------------+
| |
| Local State |
| +------------------------------------------------------------+ |
| | | |
| | terraform.tfstate | |
| | +--------------------------------------------------------+ | |
| | | - Stored locally | | |
| | | - Not recommended for teams | | |
| | | - No locking | | |
| | +--------------------------------------------------------+ | |
| | | |
| +------------------------------------------------------------+ |
| |
| Remote State (S3 + DynamoDB) |
| +------------------------------------------------------------+ |
| | | |
| | terraform { | |
| | backend "s3" { | |
| | bucket = "my-terraform-state" | |
| | key = "prod/terraform.tfstate" | |
| | region = "us-east-1" | |
| | encrypt = true | |
| | dynamodb_table = "terraform-locks" | |
| | } | |
| | } | |
| | | |
| +------------------------------------------------------------+ |
| |
| State Locking |
| +------------------------------------------------------------+ |
| | | |
| | - Prevents concurrent modifications | |
| | - DynamoDB table for S3 backend | |
| | - Automatic lock acquisition | |
| | | |
| +------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

Terraform Module Structure
+------------------------------------------------------------------+
| |
| modules/ |
| +------------------------------------------------------------+ |
| | | |
| | vpc/ | |
| | +--------------------------------------------------------+ | |
| | | main.tf # Main resource definitions | | |
| | | variables.tf # Input variables | | |
| | | outputs.tf # Output values | | |
| | | README.md # Module documentation | | |
| | +--------------------------------------------------------+ | |
| | | |
| +------------------------------------------------------------+ |
| |
| Using Modules |
| +------------------------------------------------------------+ |
| | | |
| | module "vpc" { | |
| | source = "./modules/vpc" | |
| | | |
| | vpc_cidr = "10.0.0.0/16" | |
| | environment = "production" | |
| | az_count = 3 | |
| | } | |
| | | |
| | # Using remote module from registry | |
| | module "vpc" { | |
| | source = "terraform-aws-modules/vpc/aws" | |
| | version = "~> 3.0" | |
| | | |
| | name = "my-vpc" | |
| | cidr = "10.0.0.0/16" | |
| | } | |
| | | |
| +------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

Terraform Workflow
+------------------------------------------------------------------+
| |
| 1. Write |
| +------------------------------------------------------------+ |
| | | |
| | - Define infrastructure in HCL | |
| | - Create configuration files | |
| | | |
| +------------------------------------------------------------+ |
| | |
| v |
| 2. Plan |
| +------------------------------------------------------------+ |
| | | |
| | $ terraform plan | |
| | | |
| | - Preview changes | |
| | - Compare state with configuration | |
| | - Show execution plan | |
| | | |
| +------------------------------------------------------------+ |
| | |
| v |
| 3. Apply |
| +------------------------------------------------------------+ |
| | | |
| | $ terraform apply | |
| | | |
| | - Execute changes | |
| | - Update state | |
| | - Provision infrastructure | |
| | | |
| +------------------------------------------------------------+ |
| | |
| v |
| 4. Destroy |
| +------------------------------------------------------------+ |
| | | |
| | $ terraform destroy | |
| | | |
| | - Remove all resources | |
| | - Clean up state | |
| | | |
| +------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

Terminal window
# Initialize Terraform
terraform init
# Initialize with backend configuration
terraform init -backend-config="bucket=my-state"
# Validate configuration
terraform validate
# Format configuration files
terraform fmt
# Plan changes
terraform plan
# Plan and save to file
terraform plan -out=tfplan
# Apply changes
terraform apply
# Apply saved plan
terraform apply tfplan
# Apply with auto-approve
terraform apply -auto-approve
# Destroy infrastructure
terraform destroy
# Show state
terraform show
# List resources in state
terraform state list
# Show specific resource
terraform state show aws_vpc.main
# Move resource in state
terraform state mv aws_vpc.main aws_vpc.primary
# Remove resource from state
terraform state rm aws_vpc.main
# Import existing resource
terraform import aws_vpc.main vpc-12345678
# Output values
terraform output
# Get specific output
terraform output vpc_id
# Workspace management
terraform workspace list
terraform workspace new dev
terraform workspace select dev
terraform workspace delete dev
# Refresh state
terraform refresh
# Graph dependencies
terraform graph | dot -Tpng > graph.png

Terraform Built-in Functions
+------------------------------------------------------------------+
| |
| String Functions |
| +------------------------------------------------------------+ |
| | join(", ", ["a", "b", "c"]) # "a, b, c" | |
| | split(", ", "a, b, c") # ["a", "b", "c"] | |
| | lower("HELLO") # "hello" | |
| | upper("hello") # "HELLO" | |
| | substr("hello world", 0, 5) # "hello" | |
| | replace("hello", "l", "L") # "heLLo" | |
| | format("Hello %s!", "World") # "Hello World!" | |
| +------------------------------------------------------------+ |
| |
| Numeric Functions |
| +------------------------------------------------------------+ |
| | max(1, 2, 3) # 3 | |
| | min(1, 2, 3) # 1 | |
| | sum([1, 2, 3]) # 6 | |
| | abs(-5) # 5 | |
| | ceil(4.3) # 5 | |
| | floor(4.7) # 4 | |
| +------------------------------------------------------------+ |
| |
| Collection Functions |
| +------------------------------------------------------------+ |
| | length(["a", "b"]) # 2 | |
| | element(["a", "b", "c"], 1) # "b" | |
| | contains(["a", "b"], "a") # true | |
| | distinct(["a", "a", "b"]) # ["a", "b"] | |
| | merge({a="1"}, {b="2"}) # {a="1", b="2"} | |
| | keys({a=1, b=2}) # ["a", "b"] | |
| | values({a=1, b=2}) # [1, 2] | |
| +------------------------------------------------------------+ |
| |
| Encoding Functions |
| +------------------------------------------------------------+ |
| | base64encode("hello") # "aGVsbG8=" | |
| | base64decode("aGVsbG8=") # "hello" | |
| | jsonencode({"hello"="world"}) # {"hello":"world"} | |
| | jsondecode("{\"hello\":\"world\"}") # {"hello"="world"} | |
| +------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
# Conditional expression (ternary)
resource "aws_instance" "web" {
count = var.environment == "production" ? 3 : 1
instance_type = var.environment == "production" ? "t3.large" : "t3.micro"
tags = {
Name = var.enable_monitoring ? "Monitored-Server" : "Server"
}
}
# Dynamic blocks for repeated nested blocks
resource "aws_security_group" "example" {
name = "dynamic-sg"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
# For expressions
locals {
# Transform list
uppercase_names = [for name in var.names : upper(name)]
# Filter list
long_names = [for name in var.names : name if length(name) > 5]
# Transform map
instance_types = { for k, v in var.environments : k => v.instance_type }
# Create map from list
name_to_id = { for instance in aws_instance.web : instance.tags.Name => instance.id }
}
Terraform Data Sources
+------------------------------------------------------------------+
| |
| Data sources read existing AWS resources |
| +------------------------------------------------------------+ |
| | | |
| | # Get latest Amazon Linux 2 AMI | |
| | data "aws_ami" "amazon_linux" { | |
| | most_recent = true | |
| | owners = ["amazon"] | |
| | | |
| | filter { | |
| | name = "name" | |
| | values = ["amzn2-ami-hvm-*-x86_64-gp2"] | |
| | } | |
| | } | |
| | | |
| | # Get default VPC | |
| | data "aws_vpc" "default" { | |
| | default = true | |
| | } | |
| | | |
| | # Get subnet IDs in VPC | |
| | data "aws_subnets" "default" { | |
| | filter { | |
| | name = "vpc-id" | |
| | values = [data.aws_vpc.default.id] | |
| | } | |
| | } | |
| | | |
| | # Use data source in resource | |
| | resource "aws_instance" "web" { | |
| | ami = data.aws_ami.amazon_linux.id | |
| | instance_type = "t3.micro" | |
| | subnet_id = data.aws_subnets.default.ids[0] | |
| | } | |
| | | |
| +------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
# Local values for DRY code
locals {
common_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
CreatedAt = timestamp()
}
# Computed values
name_prefix = "${var.project_name}-${var.environment}"
# Conditional locals
instance_count = var.environment == "production" ? 3 : 1
# Complex transformations
subnet_cidrs = [for i in range(3) : cidrsubnet(var.vpc_cidr, 8, i)]
}
# Using locals in resources
resource "aws_instance" "web" {
count = local.instance_count
ami = var.ami_id
instance_type = "t3.micro"
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-web-${count.index + 1}"
})
}
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = "t3.micro"
# Lifecycle management
lifecycle {
# Prevent destruction
prevent_destroy = true
# Create before destroy (for zero-downtime updates)
create_before_destroy = true
# Ignore changes to specific attributes
ignore_changes = [
ami, # Ignore AMI changes
tags["LastUpdated"] # Ignore specific tag changes
]
# Replace resource when condition changes
replace_triggered_by = [
aws_security_group.web.id
]
}
}
# Blue-green deployment example
resource "aws_launch_template" "web" {
name_prefix = "web-"
image_id = var.ami_id
instance_type = "t3.micro"
lifecycle {
create_before_destroy = true
}
}

Terraform Cloud Architecture
+------------------------------------------------------------------+
| |
| +------------------------+ |
| | Terraform Cloud | |
| +------------------------+ |
| | |
| +---------------------+---------------------+ |
| | | | | |
| v v v v |
| +----------+ +----------+ +----------+ +----------+ |
| | Remote | | State | | Policy | | Private | |
| | Execution| | Management| | as Code | | Module | |
| | | | | | | | Registry| |
| | - VCS | | - Encrypted| | - Sentinel| | - Share | |
| | Integra-| | - Versioning| | - OPA | | - Version| |
| | tion | | - Locking| | - Cost | | - Access | |
| | - API | | - History| | Estim- | | - Control| |
| | - CLI | | | | ation | | | |
| +----------+ +----------+ +----------+ +----------+ |
| |
+------------------------------------------------------------------+
# Configure Terraform Cloud backend
terraform {
cloud {
organization = "my-organization"
workspaces {
name = "my-workspace"
}
}
}
# Workspace-based configuration
# terraform.tfvars in Terraform Cloud
environment = "production"
region = "us-east-1"
# Environment variables in Terraform Cloud
# TF_VAR_database_password = "sensitive-value"
# AWS_ACCESS_KEY_ID = "AKIA..."
# AWS_SECRET_ACCESS_KEY = "secret..."

.github/workflows/terraform.yml
name: Terraform CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Terraform Init
run: terraform init
- name: Terraform Format
run: terraform fmt -check
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -out=tfplan
if: github.event_name == 'pull_request'
- name: Terraform Apply
run: terraform apply -auto-approve
if: github.ref == 'refs/heads/main' && github.event_name == 'push'

Terraform Best Practices
+------------------------------------------------------------------+
| |
| 1. Use remote state with locking |
| +------------------------------------------------------------+ |
| | - S3 backend with DynamoDB for locking | |
| | - Enable encryption | |
| +------------------------------------------------------------+ |
| |
| 2. Use modules for reusability |
| +------------------------------------------------------------+ |
| | - Create reusable modules | |
| | - Use Terraform Registry modules | |
| +------------------------------------------------------------+ |
| |
| 3. Use variables for flexibility |
| +------------------------------------------------------------+ |
| | - Parameterize configurations | |
| | - Use variable files (terraform.tfvars) | |
| +------------------------------------------------------------+ |
| |
| 4. Implement proper naming |
| +------------------------------------------------------------+ |
| | - Consistent resource naming | |
| | - Use tags for organization | |
| +------------------------------------------------------------+ |
| |
| 5. Use workspaces for environments |
| +------------------------------------------------------------+ |
| | - Separate state per environment | |
| | - dev, staging, prod workspaces | |
| +------------------------------------------------------------+ |
| |
| 6. Version control your code |
| +------------------------------------------------------------+ |
| | - Store in Git | |
| | - Use branches for changes | |
| +------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

Terraform is the de facto standard for Infrastructure as Code. It’s the tool SREs use to define, version, and manage entire cloud environments declaratively.

Terraform in DevOps/SRE
+------------------------------------------------------------------+
| |
| Why Terraform is Critical: |
| |
| 1. Infrastructure Reproducibility |
| +----------------------------------------------------------+ |
| | - Same config → same infrastructure every time | |
| | - Disaster recovery: rebuild entire env from code | |
| | - Environment parity: dev mirrors prod | |
| +----------------------------------------------------------+ |
| |
| 2. Change Management & Audit |
| +----------------------------------------------------------+ |
| | - Git history = infrastructure change log | |
| | - PR reviews for infrastructure changes | |
| | - terraform plan in CI = change impact preview | |
| +----------------------------------------------------------+ |
| |
| 3. Team Collaboration |
| +----------------------------------------------------------+ |
| | - Remote state with locking prevents conflicts | |
| | - Modules standardize patterns across teams | |
| | - Workspaces separate environments cleanly | |
| +----------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

Terminal window
# Install Terraform on Arch Linux
sudo pacman -S terraform
# Or specific version via tfenv
yay -S tfenv
tfenv install 1.7.0
tfenv use 1.7.0
# Essential Terraform aliases for ~/.zshrc
alias tf="terraform"
alias tfp="terraform plan"
alias tfa="terraform apply"
alias tfd="terraform destroy"
alias tfi="terraform init"
alias tfv="terraform validate"
alias tff="terraform fmt -recursive"
alias tfs="terraform state list"
# Pre-commit hooks for Terraform
# Install pre-commit
sudo pacman -S python-pre-commit
cat > .pre-commit-config.yaml << 'EOF'
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.88.0
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
- id: terraform_docs
EOF
pre-commit install
# Terraform state recovery script
tf-unlock() {
local lock_id="$1"
terraform force-unlock -force "$lock_id"
}
# Quick drift detection
tf-drift() {
terraform plan -detailed-exitcode 2>&1
local exit_code=$?
case $exit_code in
0) echo "✅ No drift detected" ;;
1) echo "❌ Error running plan" ;;
2) echo "⚠️ DRIFT DETECTED - infrastructure has changed outside Terraform" ;;
esac
}

IssueCauseSolution
State lock stuckPrevious apply crashedterraform force-unlock <LOCK_ID>
Provider version conflictIncompatible constraintsPin versions in required_providers block
Error: Cycle detectedCircular resource dependenciesRestructure resources, use depends_on explicitly
State file corruptionManual edits or concurrent writesRestore from S3 versioned state backup
Error: Unsupported attributeResource schema changedRun terraform init -upgrade to update providers
Drift between state and realityManual console changesRun terraform import or terraform refresh
Terminal window
# Debug Terraform issues
# Enable debug logging
export TF_LOG=DEBUG
export TF_LOG_PATH=./terraform-debug.log
terraform plan
# Check state for specific resource
terraform state show aws_instance.web
# List all resources in state
terraform state list | grep "aws_instance"
# Remove orphaned resource from state
terraform state rm aws_instance.deleted_manually

Terraform Anti-Patterns
+------------------------------------------------------------------+
| |
| ❌ Mistake 1: Local State in Production |
| +----------------------------------------------------------+ |
| | Problem: State file on one person's laptop | |
| | Impact: No team collaboration, data loss risk | |
| | Fix: Always use S3+DynamoDB remote backend | |
| +----------------------------------------------------------+ |
| |
| ❌ Mistake 2: Committing State to Git |
| +----------------------------------------------------------+ |
| | Problem: State contains secrets (passwords, keys) | |
| | Impact: Credential exposure in version control | |
| | Fix: Add terraform.tfstate* to .gitignore | |
| +----------------------------------------------------------+ |
| |
| ❌ Mistake 3: One Giant State File |
| +----------------------------------------------------------+ |
| | Problem: All resources in single state | |
| | Impact: Slow plans, blast radius of errors too large | |
| | Fix: Split by service/layer, use data sources/outputs | |
| +----------------------------------------------------------+ |
| |
| ❌ Mistake 4: `terraform apply -auto-approve` in Production |
| +----------------------------------------------------------+ |
| | Problem: No human review before production changes | |
| | Impact: Accidental resource deletion or misconfiguration| |
| | Fix: Always review plan output, use -auto-approve only | |
| | in CI/CD with separate plan+apply stages | |
| +----------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

  1. Q: Explain Terraform state and why it’s important.

    • A: State is Terraform’s record of what infrastructure exists. It maps configuration to real-world resources, tracks metadata, and enables plan/apply to determine what needs to change. Without state, Terraform can’t know what already exists. Store remotely in S3 with DynamoDB locking for teams. Never commit to Git (contains secrets). Enable versioning on S3 bucket for recovery.
  2. Q: How do you handle secrets in Terraform?

    • A: (1) Never hardcode secrets in .tf files, (2) Use sensitive = true on variables, (3) Reference Secrets Manager/SSM Parameter Store via data sources, (4) Use environment variables (TF_VAR_*), (5) Terraform Cloud/Enterprise has encrypted variable storage, (6) Enable S3 state encryption with KMS. State file itself contains secrets, so protect access.
  1. Q: How would you migrate existing AWS infrastructure to Terraform?
    • A: (1) Use terraform import to bring resources into state, (2) Write corresponding HCL configuration, (3) Run terraform plan to verify no changes needed (should show “No changes”), (4) Use tools like terraformer for bulk import, (5) Start with non-critical resources, (6) Test in a separate workspace first, (7) Gradually expand coverage.

Exam Tip

Key Exam Points
+------------------------------------------------------------------+
| |
| 1. Terraform uses HCL (HashiCorp Configuration Language) |
| |
| 2. State tracks infrastructure and must be protected |
| |
| 3. Use S3 + DynamoDB for remote state with locking |
| |
| 4. terraform plan shows changes before apply |
| |
| 5. terraform import brings existing resources under management |
| |
| 6. Modules enable code reuse and organization |
| |
| 7. Workspaces separate environments |
| |
| 8. Providers connect Terraform to cloud platforms |
| |
| 9. Use terraform destroy to remove all resources |
| |
| 10. State file is sensitive - never commit to Git |
| |
+------------------------------------------------------------------+

Chapter 41 Summary
+------------------------------------------------------------------+
| |
| Terraform Core Concepts |
| +------------------------------------------------------------+ |
| | - HCL: Declarative configuration language | |
| | - State: Infrastructure tracking | |
| | - Providers: Cloud integrations | |
| | - Modules: Reusable configurations | |
| +------------------------------------------------------------+ |
| |
| Key Commands |
| +------------------------------------------------------------+ |
| | - init: Initialize configuration | |
| | - plan: Preview changes | |
| | - apply: Execute changes | |
| | - destroy: Remove infrastructure | |
| +------------------------------------------------------------+ |
| |
| Best Practices |
| +------------------------------------------------------------+ |
| | - Remote state with locking | |
| | - Use modules | |
| | - Version control | |
| | - Use workspaces | |
| +------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

Next Chapter: Chapter 42: Packer - Machine Image Building