Infrastructure as code 23 entries

Terraform cheat sheet

Plan, apply, inspect state and recover from drift without drama. Each entry has a worked example and the flags worth knowing, and where one is easy to get wrong, the gotcha.

Core workflow

terraform init

Download providers and modules and configure the backend. Run it first in any new checkout.

terraform init -upgrade
-upgrade
Allow newer provider and module versions within constraints.
-backend-config=FILE
Supply backend settings per environment.
-reconfigure
Ignore the saved backend configuration.
-migrate-state
Copy state to a newly configured backend.

Gotcha Commit .terraform.lock.hcl so every machine and CI run uses the same provider builds.

terraform fmt -recursive && terraform validate

Normalize formatting and check that the configuration is internally consistent, without calling any API.

terraform fmt -check -recursive && terraform validate
fmt -check
Exit non-zero if files need formatting (for CI).
fmt -recursive
Include subdirectories such as modules.
validate
Syntax, references and types; requires init.

terraform plan -out=<file>

Compute the changes needed and save the exact plan, so apply performs only what you reviewed.

terraform plan -out=tfplan
-out=FILE
Save the plan for apply.
-var-file=FILE
Load variables, for example prod.tfvars.
-target=ADDR
Plan a subset; for emergencies only.
-detailed-exitcode
Exit 0 no changes, 1 error, 2 changes present.

Reading a plan

Read the summary line first, then every replace and destroy.

Plan: 1 to add, 2 to change, 1 to destroy. Any -/+ on a database, disk or load balancer deserves a second pair of eyes.

+ create
A new resource.
~ update in-place
Changed attributes; no replacement.
-/+ replace
Destroyed and recreated because a ForceNew attribute changed.
+/- create before destroy
Replacement with lifecycle create_before_destroy.
- destroy
Deleted.
<= read
A data source read during apply.
# forces replacement
The attribute that caused a replace.

terraform apply <planfile>

Apply a saved plan exactly as reviewed. Without a plan file, apply plans again and asks for confirmation.

terraform apply tfplan
PLANFILE
Apply without re-planning and without a prompt.
-auto-approve
Skip confirmation; only in pipelines that apply a reviewed plan.
-parallelism=N
Concurrent operations (default 10); lower it when APIs rate-limit.

Gotcha A saved plan goes stale if state changes. Terraform refuses it then; plan again rather than forcing.

terraform plan -refresh-only

Detect drift: show what changed in real infrastructure outside Terraform, without proposing changes.

-refresh-only
Compare state with reality only.
apply -refresh-only
Accept the drift into state.

Gotcha Accepting drift into state does not fix the configuration. Update the code too, or the next plan reverts the manual change.

terraform apply -replace=<addr>

Force one resource to be destroyed and recreated, for example a VM with a broken disk. Replaces the old taint command.

terraform apply -replace=aws_instance.web[1]
-replace=ADDR
Plan a replacement for that resource only.

terraform destroy

Delete everything the configuration manages. Review the plan it prints as carefully as any apply.

terraform plan -destroy -out=destroy.tfplan
plan -destroy
Preview and save a destroy plan.
-target=ADDR
Destroy only a subset.

Gotcha Guard critical resources with lifecycle { prevent_destroy = true } so a destroy plan errors out instead.

terraform output

Print root module outputs, for example an endpoint or an IP, for scripts and humans.

terraform output -raw lb_dns_name
-raw NAME
One string without quotes.
-json
Every output as JSON; includes sensitive values.

terraform console

Evaluate expressions against the current state and variables: test a function or a for expression.

echo 'cidrsubnet("10.0.0.0/16", 8, 3)' | terraform console
"10.0.3.0/24"

State surgery

terraform state list

Every resource address Terraform tracks.

terraform state list | grep aws_security_group
ADDR-PREFIX
Filter, for example module.network.
-id=ID
Find the address that holds a cloud ID.

terraform state show <addr>

All recorded attributes of one resource.

terraform state show aws_db_instance.main

moved block

Rename or move a resource in code without destroying and recreating it. Reviewable, unlike state mv.

moved {
  from = aws_instance.web
  to   = module.web.aws_instance.this
}
from / to
Old and new resource addresses.
state mv
The imperative equivalent; changes state immediately and leaves no trace in code.

Gotcha Without moved (or state mv), a refactor into a module plans a destroy and create of everything.

import block

Adopt an existing resource into state, and let plan generate the matching configuration.

import {
  to = aws_s3_bucket.logs
  id = "acme-prod-logs"
}

# terraform plan -generate-config-out=generated.tf
to
Resource address to import into.
id
Provider-specific ID of the existing resource.
-generate-config-out
Write HCL for imported resources (Terraform 1.5+).
terraform import ADDR ID
The older one-off CLI form.

terraform state rm <addr>

Stop managing a resource without deleting it in the cloud.

terraform state rm aws_s3_bucket.legacy
-dry-run
Show what would be removed.
removed block
Terraform 1.7+ declarative alternative with lifecycle { destroy = false }.

terraform state pull > backup.tfstate

Save a copy of the current remote state before any state surgery.

terraform state pull > state-$(date +%F-%H%M).tfstate
state push
Upload a state file; dangerous, last resort.

Gotcha State files contain secrets in plain text. Store backups as carefully as the remote state itself.

terraform force-unlock <lock-id>

Release a state lock left behind by a crashed or cancelled run.

terraform force-unlock 6f1e2d3c-4b5a-6978-8f90-a1b2c3d4e5f6
LOCK_ID
Printed in the 'Error acquiring the state lock' message.
-force
Skip the confirmation prompt.

Gotcha Make sure no other apply is actually running. Unlocking under a live run lets two writers corrupt state.

Remote state backend

Encrypted, versioned, locked remote state in S3.

terraform {
  required_version = ">= 1.10"
  backend "s3" {
    bucket       = "acme-terraform-state"
    key          = "prod/network/terraform.tfstate"
    region       = "eu-west-1"
    encrypt      = true
    use_lockfile = true
  }
}
key
One state file per environment and stack keeps the blast radius small.
use_lockfile
Native S3 locking (Terraform 1.10+); older setups use dynamodb_table.
bucket versioning
Enable it so a bad write can be rolled back.

Safety & debugging

lifecycle block

Protect or shape how a resource changes.

resource "aws_db_instance" "main" {
  # ...
  lifecycle {
    prevent_destroy       = true
    create_before_destroy = false
    ignore_changes        = [tags["LastBackup"]]
  }
}
prevent_destroy
Any plan that would destroy the resource fails.
create_before_destroy
Build the replacement before removing the old one.
ignore_changes
Stop fighting attributes that something else manages.

-var / -var-file / TF_VAR_

Where variable values come from, lowest to highest precedence.

TF_VAR_region=eu-west-1 terraform plan -var-file=prod.tfvars -var 'replicas=3'
1. default
The default in the variable block.
2. TF_VAR_name
Environment variables.
3. terraform.tfvars, *.auto.tfvars
Loaded automatically, auto files in lexical order.
4. -var-file / -var
Command line, in the order given; the last one wins.

terraform workspace select <name>

Switch between separate state files for the same configuration.

terraform workspace list
new NAME
Create a workspace.
show
Print the current workspace.
terraform.workspace
The name, usable inside configuration.

Gotcha Workspaces share one backend and credentials. For truly separate prod and staging, prefer separate directories or state keys.

TF_LOG=DEBUG terraform plan

Log provider API calls and internal decisions when a plan or apply misbehaves.

TF_LOG=DEBUG TF_LOG_PATH=tf-debug.log terraform plan
TF_LOG
TRACE, DEBUG, INFO, WARN or ERROR.
TF_LOG_PATH
Write the log to a file.
TF_LOG_PROVIDER
Provider logs only.

Gotcha Debug logs can include credentials and secrets. Scrub them before attaching to a ticket.

terraform providers lock -platform=<os_arch>

Record provider checksums for every platform your team and CI use.

terraform providers lock -platform=linux_amd64 -platform=darwin_arm64
-platform
Add hashes for another OS and architecture.
terraform providers
Show which providers each module requires.

Your pager is ready.

Free, instant, and it works on your phone. No signup: start as a guest and save your progress later.