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
count | for_each | |
|---|---|---|
| Input | integer | map or set of strings |
| Address | resource[0], resource[1] | resource["key"] |
| Deletion safety | removing middle element shifts indices — destroys others | removing a key only destroys that resource |
| Best for | identical copies | named 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-argument | Purpose |
|---|---|
provider | use a specific provider alias |
count | create N instances |
for_each | create instances from map/set |
depends_on | explicit dependency not captured by reference |
lifecycle | control create/update/delete behavior |