Terraform Cheatsheet

Resources

Use this Terraform reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Resource Block Syntax

resource "<PROVIDER_TYPE>" "<LOCAL_NAME>" {
  argument = value
}

<PROVIDER_TYPE> is the provider prefix + resource kind (e.g., aws_instance). <LOCAL_NAME> is arbitrary — it's only used within Terraform to reference this specific resource.

Common AWS Resources

# EC2 instance
resource "aws_instance" "web" {
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]
  key_name               = aws_key_pair.deployer.key_name

  tags = { Name = "web-server" }
}

# S3 bucket
resource "aws_s3_bucket" "assets" {
  bucket = "my-app-assets-${random_id.suffix.hex}"
}

resource "aws_s3_bucket_versioning" "assets" {
  bucket = aws_s3_bucket.assets.id
  versioning_configuration { status = "Enabled" }
}

# Security group
resource "aws_security_group" "web" {
  name   = "web-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 443
    to_port     = 443
    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"]
  }
}

# RDS instance
resource "aws_db_instance" "main" {
  identifier        = "myapp-db"
  engine            = "postgres"
  engine_version    = "15"
  instance_class    = "db.t3.medium"
  allocated_storage = 20
  db_name           = "myapp"
  username          = var.db_user
  password          = var.db_password
  skip_final_snapshot = true
}

Referencing Attributes

# <type>.<name>.<attribute>
resource "aws_instance" "web" {
  subnet_id = aws_subnet.public.id   # implicit dependency
}

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

Terraform builds a dependency graph from attribute references — no depends_on needed unless the dependency isn't expressed through attributes.

count — Create N Copies

resource "aws_instance" "worker" {
  count         = var.worker_count
  ami           = var.ami_id
  instance_type = "t3.small"

  tags = { Name = "worker-${count.index}" }
}

# Reference: aws_instance.worker[0], aws_instance.worker[1], …
output "worker_ips" {
  value = aws_instance.worker[*].private_ip  # splat expression
}

for_each — Create From Map or Set

# From a set of strings
resource "aws_iam_user" "team" {
  for_each = toset(["alice", "bob", "carol"])
  name     = each.key
}

# From a map of objects
variable "buckets" {
  default = {
    logs    = { versioning = true  }
    backups = { versioning = false }
  }
}

resource "aws_s3_bucket" "app" {
  for_each = var.buckets
  bucket   = "myapp-${each.key}"
}

# Reference: aws_s3_bucket.app["logs"], aws_s3_bucket.app["backups"]

count vs for_each

countfor_each
Inputintegermap or set of strings
Addressresource[0], resource[1]resource["key"]
Deletion safetyremoving middle element shifts indices — destroys othersremoving a key only destroys that resource
Best foridentical copiesnamed or config-distinct resources

Lifecycle Meta-Arguments

lifecycle {
  create_before_destroy = true   # build replacement before destroying old
  prevent_destroy       = true   # `terraform destroy` will error
  ignore_changes        = [      # ignore drift on these attributes
    tags,
    user_data,
  ]
  replace_triggered_by = [       # force replacement when this changes
    aws_launch_template.app.latest_version
  ]
}

dynamic Blocks

Generate repeated nested blocks (not resources) from a collection:

variable "ingress_rules" {
  default = [
    { port = 80,  cidrs = ["0.0.0.0/0"] },
    { port = 443, cidrs = ["0.0.0.0/0"] },
  ]
}

resource "aws_security_group" "web" {
  name   = "web-sg"
  vpc_id = aws_vpc.main.id

  dynamic "ingress" {
    for_each = var.ingress_rules      # list, set, or map
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = "tcp"
      cidr_blocks = ingress.value.cidrs
    }
  }
}

The iterator variable is named after the block label (ingress.key / ingress.value); rename it with iterator = rule. Use sparingly — explicit blocks are easier to read and diff.

terraform_data (built-in, replaces null_resource)

resource "terraform_data" "deploy" {
  triggers_replace = [aws_instance.web.id]   # re-run when this changes

  provisioner "local-exec" {
    command = "./deploy.sh"
  }
}

Available since 1.4 with no extra provider — prefer it over null_resource (which needs hashicorp/null).

check Blocks (1.5+)

Post-apply assertions that warn (never fail the run):

check "api_health" {
  data "http" "api" {
    url = "https://${aws_lb.api.dns_name}/healthz"
  }

  assert {
    condition     = data.http.api.status_code == 200
    error_message = "API health endpoint did not return 200."
  }
}

Provisioners (last resort)

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t3.micro"

  provisioner "remote-exec" {
    inline = ["sudo apt-get install -y nginx"]
    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = file("~/.ssh/id_rsa")
      host        = self.public_ip
    }
  }

  provisioner "local-exec" {
    command = "echo ${self.private_ip} >> inventory.txt"
  }
}

Prefer cloud-init / user_data or configuration management tools. Provisioners don't run on updates and failures leave resources in an unknown state.

Importing Existing Resources

Terraform 1.5+ — import block in config (preferred):

import {
  id = "i-0abcd1234efgh5678"
  to = aws_instance.web
}
# Generate config for resources declared in import blocks (1.5+)
terraform plan -generate-config-out=generated.tf

# Classic one-shot CLI import
terraform import aws_instance.web i-0abcd1234efgh5678

Timeouts

resource "aws_db_instance" "main" {
  # ...
  timeouts {
    create = "60m"
    update = "30m"
    delete = "20m"
  }
}

Resource Meta-Arguments Summary

Meta-argumentPurpose
provideruse a specific provider alias
countcreate N instances
for_eachcreate instances from map/set
depends_onexplicit dependency not captured by reference
lifecyclecontrol create/update/delete behavior