All cheat sheets
🏗️

Terraform Cheat Sheet

IaC commands and patterns for cloud interviews

Key Concepts

State file

Terraform's record of what it created. Maps your config to real infrastructure. The state file is the source of truth - if it drifts from reality, plan output will be wrong.

Never edit state manually. Use terraform state commands instead.

Idempotency

Running terraform apply multiple times on unchanged config produces no changes. Terraform computes a diff between desired state (config) and actual state (state file) each time.

Resource vs Data source

Resource: Terraform creates and manages it. Data source: reads an existing resource Terraform doesn't manage (e.g., look up an AMI ID, read a secret from Secrets Manager).

Provider

Plugin that talks to an API (AWS, GCP, Kubernetes). Defines which resource types are available. Pinned with version constraints to prevent breaking changes.

Module

A reusable group of resources with inputs (variables) and outputs. Like a function for infrastructure. Promotes DRY code and consistent patterns across environments.

Input variable vs Local value vs Output

Variable: external input (from tfvars or CLI). Local: computed value used internally. Output: value exposed to other modules or displayed after apply.

count vs for_each

count creates N identical resources indexed by number. for_each creates resources from a map or set, indexed by key. for_each is safer - removing an item doesn't shift indices.

lifecycle rules

prevent_destroy: blocks accidental deletion. create_before_destroy: provisions replacement before destroying original (blue/green). ignore_changes: prevents drift from updating a field.

Backend

Where state is stored. Default is local file. Remote backends (S3, Terraform Cloud) enable team collaboration, state locking (DynamoDB or S3 native), and encryption.

Workspaces

Isolated state files within the same backend. Used for environment separation (dev/stg/prd) within one codebase. Not recommended for large environment differences - use separate directories instead.

Implicit vs explicit dependency

Implicit: Terraform detects when resource A references resource B and waits automatically. Explicit: use depends_on when there is a dependency Terraform can not detect from references.

terraform plan -out

Saves the exact plan to a file. terraform apply planfile then applies exactly that plan - no recalculation, no surprises. Best practice before any risky apply.

Commands

Core Workflow

terraform init          # download providers & modules
terraform init -upgrade # upgrade provider versions

terraform fmt           # format all .tf files
terraform fmt -check    # exit 1 if formatting needed
terraform fmt -recursive

terraform validate      # check syntax & config
terraform plan          # preview changes
terraform plan -out=tfplan  # save plan to file
terraform plan -var 'env=prd'
terraform plan -target=aws_instance.web

terraform apply         # apply (prompts for confirm)
terraform apply -auto-approve
terraform apply tfplan  # apply saved plan (no prompt)
terraform apply -target=aws_instance.web
terraform destroy                # destroy all
terraform destroy -auto-approve
terraform destroy -target=aws_instance.web

State Commands

terraform show              # human-readable state
terraform show tfplan       # show saved plan

terraform state list        # list all resources
terraform state show aws_instance.web  # detail

terraform state pull        # download remote state
terraform state push        # upload local state
# Move resource to new address
terraform state mv   aws_instance.old aws_instance.new

# Remove from state (does NOT destroy resource)
terraform state rm aws_instance.web

# Import existing resource into state
terraform import aws_instance.web i-0abc123
# Refresh state from real infra
terraform apply -refresh-only

# Force-unlock state (if locked)
terraform force-unlock <lock-id>

# Taint (mark for replacement)
terraform taint aws_instance.web   # deprecated
# Use: terraform apply -replace=aws_instance.web

Variables

# variables.tf declaration
variable "instance_type" {
  type        = string
  description = "EC2 instance type"
  default     = "t3.small"
}

variable "allowed_cidrs" {
  type = list(string)
}

variable "tags" {
  type = map(string)
  default = {}
}
# terraform.tfvars
instance_type  = "t3.medium"
allowed_cidrs  = ["10.0.0.0/8"]
tags = {
  Environment = "prd"
  Team        = "platform"
}

# Override on command line
terraform plan -var 'instance_type=t3.large'

# Use .tfvars file
terraform plan -var-file=prod.tfvars
# Referencing variables
resource "aws_instance" "web" {
  instance_type = var.instance_type
  tags          = var.tags
}

# Local values (computed, not input)
locals {
  name_prefix = "${var.env}-${var.app}"
}

resource "aws_instance" "web" {
  tags = { Name = local.name_prefix }
}

Outputs & Data Sources

# outputs.tf
output "instance_ip" {
  value       = aws_instance.web.public_ip
  description = "Public IP of web server"
}

output "db_endpoint" {
  value     = aws_db_instance.main.endpoint
  sensitive = true   # masked in CLI output
}

terraform output              # show all outputs
terraform output instance_ip  # show one
# Data sources (read existing resources)
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

resource "aws_instance" "web" {
  ami = data.aws_ami.amazon_linux.id
}

Useful Patterns

# Count (create N copies)
resource "aws_instance" "web" {
  count         = 3
  instance_type = "t3.small"
  tags = { Name = "web-${count.index}" }
}

# For each (create from map/set)
resource "aws_s3_bucket" "buckets" {
  for_each = toset(["dev", "stg", "prd"])
  bucket   = "myapp-${each.key}"
}
# Lifecycle rules
resource "aws_db_instance" "main" {
  lifecycle {
    prevent_destroy       = true   # block destroy
    create_before_destroy = true   # blue/green
    ignore_changes        = [password]
  }
}

# Dynamic blocks
dynamic "ingress" {
  for_each = var.allowed_ports
  content {
    from_port = ingress.value
    to_port   = ingress.value
    protocol  = "tcp"
  }
}

Backends & Workspaces

# S3 backend (backend.tf)
terraform {
  backend "s3" {
    bucket       = "my-tf-state"
    key          = "prd/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true   # S3 native locking
  }
}
# Workspaces
terraform workspace list
terraform workspace new dev
terraform workspace select prd
terraform workspace show       # current

# Use workspace name in config
resource "aws_s3_bucket" "state" {
  bucket = "myapp-${terraform.workspace}"
}
# Remote state data source
data "terraform_remote_state" "vpc" {
  backend = "s3"
  config = {
    bucket = "my-tf-state"
    key    = "shared/terraform.tfstate"
    region = "us-east-1"
  }
}

# Use outputs from remote state
module.vpc.id = data.terraform_remote_state.vpc.outputs.vpc_id

Modules

# Calling a module
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"
}

# Local module
module "app" {
  source = "./modules/app"
  env    = var.env
}

# After adding a module
terraform init    # always re-init first

Interview Tips

# Safe apply pattern
terraform plan -out=changes.tfplan
terraform show changes.tfplan   # review
terraform apply changes.tfplan  # no surprises

# Check for destroying or replacing
terraform plan | grep -E "will be destroyed|must be replaced"

# Back up state before risky ops
terraform state pull > backup.json
# Key concepts to know:
# - Terraform state = source of truth
# - plan is idempotent (safe to run multiple times)
# - destroy removes real infra, not just state
# - -target is for emergencies, not routine use
# - sensitive = true masks in output, NOT in state

acecloudinterviews.com - Free forever. No login required.