Terraform Cheat Sheet

The Terraform commands and HCL patterns developers actually use daily — resources, variables, state, and modules — with real examples, common mistakes, and a searchable quick reference.

runs locally on your browser. Your data never leaves your device.
13 sections
41 commands
Searchable quick reference
100% offline

Core Workflow & CLI

Four commands you'll run constantly, in almost this exact order, every time you touch a Terraform directory.

terraform init
Essential

Initializes a working directory: downloads providers and modules, sets up the backend. Always the first command in a new or freshly-cloned Terraform directory.

terraform plan
Essential

Shows exactly what Terraform would change — create, update, destroy — without actually doing any of it. Always read a plan before applying it.

terraform apply
Essential

Applies the changes described in a plan, prompting for confirmation first unless -auto-approve is passed.

terraform plan -out=tfplan terraform apply tfplan

terraform destroy
Advanced

Destroys every resource Terraform is currently managing in state.

terraform validate
Handy

Checks configuration files for syntax errors and internal consistency, without contacting any provider or touching real infrastructure.

terraform fmt
Handy

Rewrites configuration files into Terraform's canonical formatting style — the HCL equivalent of a code formatter.

Always read the plan, not just skim it
apply does exactly what the plan said, including a destroy-and-recreate you didn't expect from a change that looks small in the diff. A few seconds reading the summary line (Plan: X to add, Y to change, Z to destroy) catches a real mistake before it happens, not after.
Pro trick
terraform plan -out=tfplan saves the exact plan to a file, and terraform apply tfplan applies precisely that saved plan — guaranteeing what gets applied is exactly what was reviewed, with nothing changed in between (like a teammate merging an unrelated update).

Providers & Resources

A provider is the plugin that knows how to talk to a specific platform. A resource is the actual thing you want it to create.

provider "aws" { ... }
Essential

Configures a provider — the plugin Terraform uses to talk to a specific platform (AWS, Google Cloud, Kubernetes, and hundreds more).

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

resource "aws_instance" "web" { ... }
Essential

Declares a piece of infrastructure Terraform should create and manage — the type (aws_instance) comes from the provider, the name (web) is your own label.

resource "aws_instance" "web" { ami = "ami-123456" instance_type = "t2.micro" }

aws_instance.web
Essential

References a declared resource elsewhere in your configuration — in an output, another resource's argument, or an expression.

aws_instance.web["primary"]
Advanced

References one specific instance out of a resource created with for_each, by its key.

resource "TYPE" "NAME" — what each part means
The type is defined by the provider and determines what gets created. The name is entirely your own label, used only to reference this resource elsewhere in your own configuration — it's never sent to the actual cloud provider.

Variables & Outputs

How configuration gets values in, and how results get values back out.

variable "instance_type" { ... }
Essential

Declares an input variable, with an optional type constraint and default value.

variable "instance_type" { type = string default = "t2.micro" }

var.instance_type
Essential

References a declared variable's value anywhere in the configuration.

output "instance_ip" { ... }
Essential

Exposes a value after apply — printed to the terminal, and available to any module that calls this configuration as a child module.

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

terraform.tfvars
Handy

A file providing values for declared variables, loaded automatically if it's named exactly terraform.tfvars (or matches *.auto.tfvars).

terraform apply -var="instance_type=t2.large"
Handy

Overrides a single variable's value from the command line, taking priority over any default or tfvars file.

terraform apply -var-file="prod.tfvars"
Handy

Loads variable values from a specific file — the usual way to point the same configuration at a different environment's values.

Tip
A variable with no default is required — Terraform will interactively prompt for a value if one isn't supplied any other way, which is almost never what you want in automation. Always provide a default or a tfvars file for anything that runs unattended.

State Management

The file linking your configuration to real infrastructure — and the part teams most often get wrong first.

terraform state list
Essential

Lists every resource currently tracked in state.

terraform state show aws_instance.web
Handy

Shows the full recorded attributes of one specific resource in state.

terraform state mv aws_instance.old aws_instance.new
Advanced

Renames a resource in state without destroying and recreating the real infrastructure — the fix when you rename a resource block in code and don't want Terraform to treat it as brand new.

terraform state rm aws_instance.web
Advanced

Removes a resource from state without destroying the real infrastructure — Terraform simply stops tracking it.

terraform { backend "s3" { ... } }
Essential

Configures a remote backend — where state is actually stored, instead of a local file on one person's machine.

terraform { backend "s3" { bucket = "my-terraform-state" key = "prod/terraform.tfstate" } }

State is the single source of truth — treat it that way
Losing it, or two people applying against different local state files, is one of the most common ways a team gets into real trouble. Use a remote backend (S3, Terraform Cloud, and similar) with locking for anything beyond a personal experiment.
Note
terraform state mv and rm are surgical, deliberate edits to state, not routine commands — reach for them specifically when you know exactly what mismatch you're fixing.

Modules

Reusable, self-contained bundles of configuration — call one the same way you'd call a function.

module "vpc" { ... }
Essential

Calls a reusable module, either from a local path (shown here) or a registry source like terraform-aws-modules/vpc/aws, passing in its input variables.

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

module.vpc.vpc_id
Handy

References one of a called module's declared outputs, the same way you'd reference a resource's attribute.

terraform get
Advanced

Downloads or updates modules referenced by the configuration, without doing a full init.

Pro trick
Pin a registry module to a specific version the same way you'd pin a package dependency — an unpinned module can change out from under you the next time anyone on the team runs init.

Data Sources & Expressions

Reading information Terraform doesn't manage, and the small expression language that ties everything else together.

data "aws_ami" "latest" { ... }
Essential

Reads information about an existing resource Terraform isn't managing — the read-only counterpart to a resource block.

data "aws_ami" "latest" { most_recent = true owners = ["amazon"] }

data.aws_ami.latest.id
Handy

References an attribute from a declared data source.

"${var.name}-bucket"
Handy

Splices a value into the middle of a literal string using interpolation syntax.

[for s in var.subnets : s.id]
Advanced

A for expression — transforms a list or map into another list or map, the closest thing HCL has to a loop.

var.environment == "prod" ? "t3.large" : "t3.micro"
Handy

A conditional expression — evaluates to the first value if the condition is true, the second if it's false.

Note
Modern Terraform mostly doesn't need the ${...} interpolation syntax outside of a string — var.name works directly as an expression almost everywhere. ${...} is really only for splicing a value into the middle of a literal string, as in the example above.

Workspaces

A lightweight way to keep multiple states under one configuration — with real limitations worth knowing before you rely on them.

terraform workspace new staging
Handy

Creates a new workspace — a distinct state, kept separate from other workspaces, under the same configuration and backend.

terraform workspace select staging
Essential

Switches to a different existing workspace.

terraform workspace list
Handy

Lists every workspace and marks the currently active one.

terraform.workspace
Handy

A built-in reference to the current workspace's name, usable inside your configuration — for example, to vary an instance size or a resource's tags per environment.

Workspaces aren't a full substitute for separate environments
They share the same backend configuration and the same variable defaults, which can be more fragile than fully separate directories (or a dedicated tool) once environments genuinely need to diverge from each other — different regions, different account credentials, different provider versions. Fine for a small project; know the limitation before treating it as your only environment strategy.

Provisioners & Lifecycle

Fine-tuning exactly how and when Terraform is allowed to change or replace a resource.

lifecycle { create_before_destroy = true }
Essential

Creates the replacement resource before destroying the original, instead of the default destroy-then-create order — avoids a window with no resource at all for anything that can't tolerate downtime.

lifecycle { prevent_destroy = true }
Handy

Makes Terraform refuse to destroy this specific resource, causing plan or apply to error out instead — a guardrail for anything genuinely irreplaceable.

lifecycle { ignore_changes = [tags] }
Handy

Tells Terraform to stop treating drift on specific arguments as something to fix — useful when another system (like autoscaling) legitimately manages that field after creation.

depends_on = [aws_instance.web]
Advanced

Forces an explicit dependency between resources that Terraform can't infer automatically from a direct reference — used sparingly, when a real ordering requirement isn't otherwise visible in the configuration.

provisioner "local-exec" { ... }
Advanced

Runs an arbitrary script when a resource is created or destroyed — deliberately the last option on this page, not a first choice.

provisioner "local-exec" { command = "echo done" }

Provisioners are a last resort, per Terraform's own docs
They run outside Terraform's dependency graph and state tracking, which makes failures hard to reason about and impossible to plan for ahead of time. Prefer a resource's own native provider arguments, or your platform's own bootstrapping mechanism (cloud-init, user-data), whenever one already exists.

Import & Refactoring

Bringing infrastructure that already exists under Terraform's management, and reshaping configuration without losing track of what's real.

terraform import aws_instance.web i-1234567890abcdef0
Advanced

Brings a resource that already exists in the real world — created manually, or by another tool — under Terraform's management, without recreating it.

moved { ... }
Advanced

Declares that a resource was renamed or moved in configuration, so Terraform updates state to match instead of destroying the old address and creating the new one — the modern, version-controlled alternative to running terraform state mv by hand.

moved { from = aws_instance.old_name to = aws_instance.new_name }

Tip
Always run terraform plan immediately after an import. It's the only way to confirm the configuration you wrote actually matches every setting the real resource already has — an import brings a resource under management, it doesn't verify your HCL describes it accurately.

Quick Reference

Already comfortable with Terraform — just blanked on a syntax fragment? Search instead of scrolling.

terraform init

Download providers/modules and set up the backend

terraform plan

Show exactly what would change, without changing anything

terraform apply

Apply the changes, prompting for confirmation first

terraform apply -auto-approve

Apply without an interactive confirmation prompt

terraform plan -out=tfplan

Save an exact plan to a file for later review and apply

terraform destroy

Destroy every resource Terraform is managing

terraform validate

Check configuration syntax without touching real infrastructure

terraform fmt

Rewrite files into Terraform's canonical formatting

provider "aws" { }

Configure a provider — the plugin talking to a specific platform

resource "type" "name" { }

Declare a piece of infrastructure to create and manage

aws_instance.web

Reference a resource elsewhere in the configuration

variable "name" { }

Declare an input variable with a type and optional default

var.name

Reference a declared variable's value

output "name" { }

Expose a value after apply, or to a calling module

terraform.tfvars

File providing variable values, loaded automatically

-var="key=value"

Override a single variable from the command line

-var-file="prod.tfvars"

Load variable values from a specific file

terraform state list

List every resource currently tracked in state

terraform state show

Show one resource's full recorded attributes in state

terraform state mv

Rename a resource in state without destroying it

terraform state rm

Stop tracking a resource without destroying it

backend "s3" { }

Configure where state is stored remotely

module "name" { }

Call a reusable module by local path or registry source

module.name.output

Reference an output value from a called module

terraform get

Download or update modules without a full init

data "type" "name" { }

Read information about an existing resource, not manage it

data.type.name.attr

Reference a data source's attribute

"${var.name}-bucket"

Splice a value into the middle of a literal string

[for s in var.list : s.id]

Transform a list or map into another list or map

cond ? a : b

Conditional expression — a if cond is true, else b

terraform workspace new

Create a new named workspace

terraform workspace select

Switch to a different workspace

terraform workspace list

List every workspace, marking the active one

terraform.workspace

Reference the current workspace's name in configuration

create_before_destroy

Create the replacement before destroying the original

prevent_destroy

Block Terraform from ever destroying this resource

ignore_changes

Ignore drift on specific arguments after creation

depends_on

Force an explicit dependency Terraform can't infer on its own

provisioner "local-exec"

Run a local script after a resource is created — a last resort

terraform import

Bring existing infrastructure under Terraform's management

moved { }

Declare a resource rename so Terraform doesn't recreate it

41 of 41 commands

Common Use Cases

Real situations, not just isolated syntax — the exact sequence that gets you through each one.

You need to rename a resource in code without destroying it

Update state to match, or declare the rename directly in configuration:

  1. terraform state mv aws_instance.old aws_instance.new

You want to preview a change before touching real infrastructure

Save the exact plan, review it, then apply precisely that:

  1. terraform plan -out=tfplan
  2. terraform apply tfplan

You need different instance sizes for dev vs. prod

One configuration, a separate tfvars file per environment:

  1. terraform apply -var-file="prod.tfvars"

You already created a resource by hand and want Terraform to manage it

Import it, then confirm your config actually matches:

  1. terraform import aws_instance.web i-abc123
  2. terraform plan

You need to replace a resource with zero downtime

Create the replacement before tearing down the original:

  1. lifecycle { create_before_destroy = true }

You want to safely test a destructive-looking change

Read the plan's summary line before ever running apply:

  1. terraform plan
  2. // check "X to destroy" before continuing

Debugging & Troubleshooting

The exact error text Terraform actually prints, decoded.

Error: Resource already exists

Something with that identity already exists in the real world but isn't in Terraform's state. Import it if it should be managed going forward, or rename/remove one side if it's genuinely a duplicate.

terraform import aws_instance.web i-abc123
Error acquiring the state lock

Another plan or apply is already running against the same state, or a previous run crashed and left a stale lock behind. Confirm nothing is genuinely still running before force-unlocking.

terraform force-unlock LOCK_ID
Plan shows a destroy and recreate for a change that looks small

Some resource arguments are ForceNew — the provider's API has no way to update that specific property in place, so destroy-then-recreate is the only option Terraform has. Check that argument's documentation before applying if this surprises you.

Error: Provider configuration not present

Usually a module or resource referencing a provider that was never configured, or a mismatched alias. Check every provider block, and any explicit providers = or provider = argument.

terraform apply keeps prompting for a variable with no default

The variable has no default and wasn't supplied via -var, -var-file, or a *.auto.tfvars file. Provide it one of those ways instead of typing it in manually at every apply.

State and real infrastructure have drifted apart

Something changed outside Terraform — manually, or by another tool. terraform plan shows the drift as a diff; terraform apply reconciles it back to what's in your configuration. Review carefully before applying — it might revert an intentional manual fix.

terraform plan

Things to Remember

If you forget everything else on this page, keep these.

  • Always run terraform plan and actually read it before terraform apply — apply does exactly what the plan says, no exceptions.
  • State is the single source of truth linking your configuration to real infrastructure — use a remote backend with locking for anything beyond a personal experiment.
  • terraform state mv (or a moved block) renames a resource without destroying it; editing state by hand should be rare and deliberate.
  • Pin module versions the same way you'd pin a package dependency — an unpinned module can change out from under you.
  • Provisioners are a last resort per Terraform's own docs — prefer a resource's native arguments or cloud-init whenever one exists.
  • A variable with no default will interactively prompt for a value — always supply one via a default, -var, or a tfvars file in automation.
  • Some resource changes force a destroy-and-recreate even when the diff looks small — read what plan actually says, not just what you expect.
  • terraform import brings existing infrastructure under management without recreating it — always run plan immediately after to confirm your configuration actually matches.
  • fmt and validate cost nothing and catch real mistakes — run them before every commit, not just before apply.

Common Use Cases

Recall the exact terraform state mv syntax before renaming a resource without destroying it
Figure out why a plan wants to destroy and recreate a resource you only changed slightly
Reference the difference between a module's local path and registry source before wiring one in
Search the quick-reference table for HCL syntax instead of digging through provider docs

About Terraform Cheat Sheet

Terraform's documentation is thorough per-resource and scattered everywhere else, and most cheat sheets either dump the CLI verb list flat or show HCL syntax in complete isolation — without ever explaining the workflow that actually matters day to day: why you read a plan before applying it, what state genuinely is and why losing it is a real problem, or why changing one small argument can make Terraform want to destroy and recreate a resource instead of just updating it.

This one starts with the core init, plan, apply, destroy workflow and CLI basics, into providers and resources — the actual building blocks — variables and outputs, and state management, the part teams most often get wrong first. From there it covers modules, data sources and expressions, workspaces, provisioners and lifecycle rules, and import for bringing existing infrastructure under management.

Every section calls out the mistakes that actually cause outages or wasted afternoons: applying without reading what the plan actually says, treating a local state file as fine once more than one person touches the configuration, an unpinned module version changing out from under you, reaching for a provisioner before checking whether the resource already has a native argument for what you need. There's a searchable quick reference for the moment you already know roughly what you want, and a troubleshooting section built around the exact error text Terraform prints — a state lock, a ForceNew attribute, a missing provider configuration — not a paraphrase of it.

Nothing on this page talks to a real cloud provider, a state backend, or any actual infrastructure — it's a static reference, like every tool on AllCoreKit. The quick-reference search filters entirely in your browser, and nothing you read or search for is ever sent anywhere or logged.

Frequently Asked Questions