Skip to content

Terraform Cheat Sheet

Name origin: Terraform = "to transform the earth" (Latin: terra + forma). HashiCorp chose the name because the tool transforms infrastructure declarations into real cloud resources. Created by Mitchell Hashimoto, first released in 2014.

Core Workflow

terraform init      # Download providers, init backend
terraform plan      # Preview changes (dry-run)
terraform apply     # Apply changes
terraform destroy   # Tear down all resources — DANGEROUS: destroys everything
# Gotcha: use -target for surgical destruction: terraform destroy -target=aws_instance.web

# CI-safe apply
terraform plan -out=plan.tfplan
terraform apply plan.tfplan

Configuration Basics

# Provider
terraform {
  required_version = ">= 1.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

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

# Resource
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  tags          = { Name = "web-server" }
}

# Variable
variable "instance_type" {
  type    = string
  default = "t3.micro"
}

# Output
output "public_ip" {
  value = aws_instance.web.public_ip
}

# Data source (read-only lookup)
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-*"]
  }
}

Remote Backend (State)

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

Modules

module "vpc" {
  source     = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
  name       = "production"
}

# Reference module output
resource "aws_subnet" "web" {
  vpc_id = module.vpc.vpc_id
}

State Commands

terraform state list                    # List all resources
terraform state show aws_instance.web   # Show resource details
terraform state mv old_name new_name    # Rename resource
terraform state rm aws_instance.web     # Remove from state (doesn't destroy)
terraform import aws_instance.web i-123 # Import existing resource
terraform force-unlock <LOCK_ID>        # Unlock stuck state

Lifecycle Rules

resource "aws_instance" "web" {
  lifecycle {
    prevent_destroy = true              # Block accidental destroy
    create_before_destroy = true        # Zero-downtime replacement
    ignore_changes = [tags]             # Don't track tag drift
  }
}

# Rename without destroy
moved {
  from = aws_instance.web
  to   = aws_instance.app
}

Expressions

# Conditional
instance_type = var.env == "prod" ? "m5.xlarge" : "t3.micro"

# For each
resource "aws_iam_user" "users" {
  for_each = toset(["alice", "bob", "carol"])
  name     = each.key
}

# Count
resource "aws_instance" "web" {
  count         = var.instance_count
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  tags          = { Name = "web-${count.index}" }
}

# Dynamic blocks
dynamic "ingress" {
  for_each = var.ingress_rules
  content {
    from_port   = ingress.value.port
    to_port     = ingress.value.port
    protocol    = "tcp"
    cidr_blocks = ingress.value.cidrs
  }
}

Common Flags

Flag Purpose
-auto-approve Skip confirmation prompt
-target=resource Apply to specific resource
-replace=resource Force recreation
-var="key=val" Set a variable
-var-file=file Load variables from file
-parallelism=N Concurrent operations (default 10)
-refresh=false Skip state refresh

Troubleshooting

Error Fix
State lock terraform force-unlock <id> (verify no one else running)
Provider version Pin version in required_providers
Dependency cycle Use depends_on or restructure resources
Destroy then create Add create_before_destroy = true
Drift detected terraform refresh or fix in config