Terraform Cheatsheet
Basics
Use this Terraform reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
What Terraform Does
Terraform is an infrastructure-as-code tool that provisions and manages cloud resources from declarative config files. You describe what you want in HCL, Terraform diffs that against recorded state, then calls provider APIs to reconcile the difference.
Current stable line is 1.16 (1.16.0 shipped 26 August 2026). The two most recent language additions worth knowing: 1.14 (19 November 2025) added List Resources with .tfquery.hcl files plus the terraform query command, and top-level action blocks. 1.15 (April 2026) added deprecated on variables and outputs, the convert() function, and variables in module source.
Core Workflow
terraform init # download providers and modules, set up the backend terraform validate # syntax and type check, no credentials needed terraform plan # diff: what will change? terraform apply # make it so terraform destroy # tear everything down
# The production loop: review the plan you actually apply terraform plan -out=plan.tfplan terraform show plan.tfplan # or -json for tooling terraform apply plan.tfplan # no prompt, no re-plan
terraform applywithout a saved plan file re-plans and prompts. In CI, always-outa plan and apply that file, so what ships is what was reviewed.
HCL and Block Types
Every Terraform file is a list of blocks. These are the top-level block types, with the version that introduced each.
Top-Level Block Types
| Block | Purpose | Since |
|---|---|---|
terraform | version constraints, providers, backend | 0.x |
provider | configure a plugin | 0.x |
resource | managed object Terraform creates | 0.x |
data | read-only lookup | 0.x |
variable | input | 0.x |
output | exported value | 0.x |
locals | named local expressions | 0.x |
module | call another configuration | 0.x |
moved | relocate an object in state | 1.1 |
import | adopt existing infrastructure | 1.5 |
check | post-apply assertion that warns | 1.5 |
removed | drop from state without destroying | 1.7 |
ephemeral | value never written to state or plan | 1.10 |
action | provider-defined side effect | 1.14 |
list | query existing infrastructure (.tfquery.hcl) | 1.14 |
Block Anatomy
resource "aws_instance" "web" { # ^type ^label1 ^label2 ami = "ami-0c55b159cbfafe1f0" # argument instance_type = "t3.micro" tags = { # object value Name = "web-server" Env = "prod" } root_block_device { # nested block volume_size = 20 } }
- Block type decides how many labels follow.
resourceanddatatake two,variableandoutputtake one,terraformandlocalstake none. - Arguments are
name = expression. Nested blocks have no=. - Identifiers may hold letters, digits,
_and-, and must not start with a digit.
# Strings and interpolation name = "server-${var.environment}" # Heredoc, the - strips leading indentation user_data = <<-EOT #!/bin/bash yum install -y httpd EOT # Comments # single line // also single line /* multi line */
terraform fmt is canonical. Run it and stop arguing about alignment.
Project Structure
project/
├── main.tf # core resources
├── variables.tf # input variable declarations
├── outputs.tf # output value declarations
├── providers.tf # provider configuration
├── versions.tf # the terraform block
├── terraform.tfvars # variable values (never commit secrets)
├── tests/ # *.tftest.hcl
├── .terraform.lock.hcl # provider lock file, DO commit
└── .terraform/ # downloaded plugins and modules, never commitEvery .tf file in one directory is merged into a single root module. File names carry no meaning to Terraform, only to humans, so the split above is convention.
# .gitignore essentials .terraform/ *.tfstate *.tfstate.* *.tfvars crash.log
Note the exception: .terraform.lock.hcl is committed. It pins provider versions and checksums for the whole team.
The terraform Block
terraform { required_version = ">= 1.10.0, < 2.0.0" required_providers { aws = { source = "hashicorp/aws" # <namespace>/<type> version = "~> 6.0" } random = { source = "hashicorp/random" version = "~> 3.6" } } backend "s3" { bucket = "my-tf-state" key = "prod/terraform.tfstate" region = "us-east-1" encrypt = true use_lockfile = true } }
Rules that catch people out:
- The
terraformblock takes no expressions. Novar.*, no functions, no interpolation. Everything must be literal. required_versiongates the Terraform CLI.required_providersgates plugins. Both are checked atinit.- A child module should declare
required_providersbut never abackend. Only the root module has one. - Use
cloud { ... }instead ofbackendfor HCP Terraform.
Version Constraints
| Constraint | Allows |
|---|---|
= 1.6.0 | exactly 1.6.0 |
!= 1.5.0 | anything except 1.5.0 |
>= 1.6.0 | 1.6.0 and up |
<= 1.9.0 | 1.9.0 and down |
~> 1.6 | >= 1.6.0, < 2.0.0 (last component may rise) |
~> 1.6.0 | >= 1.6.0, < 1.7.0 |
>= 1.4, < 2.0 | both must hold |
Convention: ~> MAJOR.0 for providers you trust to respect semver, ~> MAJOR.MINOR.0 when a minor bump has burned you. Pin exactly only when you have a specific reason, because it blocks security patches.
Locals
locals { env = var.environment name_prefix = "myapp-${local.env}" # locals may reference locals common_tags = { Project = "myapp" Environment = local.env ManagedBy = "terraform" } } resource "aws_instance" "web" { tags = merge(local.common_tags, { Name = "${local.name_prefix}-web" }) }
Locals are computed once per plan and cannot be set from outside. Use a variable for anything a caller should override, and a local for anything derived.
Meta-Arguments and Addressing
Every resource has an address used in plan output, state commands and depends_on.
| Address form | Example |
|---|---|
<type>.<name> | aws_instance.web |
| indexed | aws_instance.web[0], aws_s3_bucket.app["logs"] |
| in a module | module.vpc.aws_vpc.main |
| data source | data.aws_ami.ubuntu |
| variable / local / output | var.x, local.y, output.z |
| action (1.14+) | action.aws_lambda_invoke.warm |
The five meta-arguments (count, for_each, depends_on, provider, lifecycle) plus dynamic blocks are covered in depth in the Meta-Arguments and Lifecycle topic.