Terraform Cheatsheet - Infrastructure as Code Command Reference

The Terraform rhythm is fixed: plan to see the diff, apply to commit, destroy with real care. Everything else follows from the state file, which records how declared resources map to real ones and acts as the source of truth for the tool. Lose the state and the next plan proposes recreating everything; drift between state and reality can trigger unexpected replacement. That is why shared workspaces belong on a remote backend with locking enabled. Sections cover the core workflow, state management, variables and outputs, modules, remote backends, advanced features, and debugging. A few rules hold up in practice: pass secrets as variables or mark them sensitive rather than inlining them, keep state out of Git because it carries credentials, and run terraform fmt so reviews stay readable. When something looks wrong, state list and state show come first, and refresh-only is the smallest tool for correcting drift. Import turns an existing hand-built resource into a managed one without destroying anything, which is the safe first step when adopting Terraform on a live stack.

SysOps·53 commands·Last updated 2026-07-21
terraformiacinfrastructuredevopsCloud Native

Typical Use Case

For engineers responsible for infrastructure as code in the cloud. The familiar rhythm is init, plan, apply, but the time actually goes into four other things this sheet covers: state management, where list, show, mv, rm and import are the tools for investigating drift and mismatches between config and reality, plus taint to force replacement; remote backends, with S3 for state and DynamoDB for locking, which is what makes team and concurrent apply safe; choosing between count and for_each for bulk creation; and debugging through the console, TF_LOG and output logs. Workspace covers keeping staging and production state apart.

Core Workflow 7

terraform init
Initialize the working dir; download providers and modules
terraform plan
Preview changes: resources to create/modify/destroy
terraform apply
Apply changes to infrastructure
terraform destroy
Destroy all managed infrastructure
terraform fmt
Format .tf files to the standard style
terraform validate
Validate config syntax and internal consistency
terraform apply -auto-approve
Auto-approve (for CI/CD)

State Management 9

terraform state list
List all resources in the state file
terraform state show <resource>
Show detailed state of a resource
terraform state mv <src> <dst>
Move a resource's address in state
terraform state rm <resource>
Remove a resource from state (keeps the real one)
terraform state pull
Pull remote state to local stdout
terraform state push
Push local state to the remote backend
terraform import <address> <id>
Import an existing resource into Terraform
terraform taint <resource>
Mark a resource as 'tainted'; rebuilt on next apply
terraform untaint <resource>
Clear the 'tainted' mark

Variables & Outputs 8

variable "region" { type = string default = "us-east-1" }
Define a variable (type, default)
var.region
Reference a defined variable
TF_VAR_region=us-west-2
Set a variable via environment variable
terraform plan -var="region=us-west-2"
Set a variable via CLI flag
output "ip" { value = aws_instance.web.public_ip }
Define an output value
terraform output
Show all output values
terraform output -json
Show outputs as JSON
locals { name = "${var.prefix}-web" }
Local values to simplify expressions

Modules 6

module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "5.0.0" }
Reference a Registry module, pin version
module "vpc" { source = "./modules/vpc" }
Reference a local module (relative path)
module "vpc" { source = "git::https://example.com/vpc.git" }
Reference a module in a Git repo
module.vpc.vpc_id
Reference a module's output
terraform init -upgrade
Upgrade modules to the latest allowed version
terraform get
Download and update modules

Remote Backends 6

terraform { backend "s3" { bucket = "my-tf-state" key = "prod/terraform.tfstate" region = "us-east-1" } }
Store state in an S3 backend
terraform { backend "s3" { dynamodb_table = "tf-lock" } }
DynamoDB state lock against concurrency
data "terraform_remote_state" "db" { backend = "s3" config = { bucket = "shared-state" key = "db/terraform.tfstate" } }
Read remote state across configurations
data.terraform_remote_state.db.outputs.db_endpoint
Reference a remote state output
terraform init -reconfigure
Reconfigure the backend (migrate state)
terraform init -migrate-state
Migrate the state file to a new backend

Advanced Features 9

terraform workspace new staging
Create a new workspace
terraform workspace list
List all workspaces
terraform workspace select staging
Switch to a workspace
resource "aws_instance" "web" { for_each = var.subnets }
for_each creates resources from a set
resource "aws_instance" "web" { count = var.instance_count }
count creates resources by number
depends_on = [aws_s3_bucket.data]
Explicit dependency to fix creation order
lifecycle { create_before_destroy = true }
Lifecycle: create before destroy
lifecycle { prevent_destroy = true }
Lifecycle: prevent accidental destroy
lifecycle { ignore_changes = [tags] }
Lifecycle: ignore changes to specific attributes

Debugging 8

terraform console
Interactive console to test expressions
terraform graph | dot -Tpng > graph.png
Generate a resource dependency graph
terraform show
Readable dump of the current state
terraform show -json
JSON state output for jq
TF_LOG=DEBUG terraform apply
Enable DEBUG-level logging
TF_LOG=TRACE terraform plan
TRACE-level logging, most verbose
TF_LOG_PATH=./tf.log terraform apply
Write logs to a file
export TF_LOG=WARN
Set log level (TRACE/DEBUG/INFO/WARN/ERROR)

Parameter matrix

ParameterEffectExample
initInitialize the working directory and download providers and modulesterraform init
init -upgradeUpgrade modules to the latest version currently allowedterraform init -upgrade
init -reconfigureReconfigure the backend (migrate state)terraform init -reconfigure
init -migrate-stateMigrate the state file to a new backendterraform init -migrate-state
plan -var="k=v"Pass variable values on the command lineterraform plan -var="region=us-west-2"
apply -auto-approveSkip manual confirmation; for CI useterraform apply -auto-approve
validate / fmtValidate config consistency / unify code formattingterraform fmt
state list / showList resources in state / show details of a single resourceterraform state list
state mv <src> <dst>Move a resource's address in stateterraform state mv aws_instance.a aws_instance.b
state rm <res>Remove from state only; does not destroy the real resourceterraform state rm aws_s3_bucket.old
import <addr> <id>Bring existing resources under Terraform managementterraform import aws_instance.web i-0123456789
taint / untaint <res>Mark as damaged for rebuild next time / unmarkterraform taint aws_instance.web
output / output -jsonView output values / view them as JSONterraform output -json
workspace new/select/listCreate / switch / list workspacesterraform workspace select staging
count / for_eachCreate resources in bulk by count or by collectioncount = var.instance_count
consoleInteractive console for testing expressionsterraform console
TF_LOG / TF_LOG_PATHSet the log level (TRACE/DEBUG/INFO/WARN/ERROR) and output pathTF_LOG_PATH=./tf.log terraform apply

Common pitfalls

Symptomapply reports the state is already locked / lock timeout, and people block each other when operating at the same time

CauseThe remote backend has no state locking configured, or uses S3 without a lock table.

FixAdd dynamodb_table = "tf-lock" inside backend "s3", then run terraform init -migrate-state.

Symptomplan shows a large number of differences that were not caused by this change

CauseRemote resources were modified by hand, so the local state drifts from reality.

FixFirst confirm with terraform state list and terraform state show <resource>, then run terraform apply to realign state with config; for major differences, archive and review first with terraform plan -out.

SymptomAfter renaming a resource, Terraform wants to "destroy the old and create a new" one, but the old resource cannot be deleted

CauseThe resource address changed, so Terraform cannot tell that the two are the same thing.

FixUse terraform state mv <old address> <new address> to claim the resource under the new address; do not force it through by copying the config.

SymptomThe resource was already deleted by hand, but apply reports a creation conflict

CauseThe state still records this now-nonexistent resource.

FixUse terraform state rm <resource> to remove it from state (this command does not delete the real resource), then apply again.

SymptomThe output exposes sensitive information such as database passwords and keys

CauseThe state file itself contains all attributes of the resources, including sensitive fields; committing it to Git spreads them.

FixPut sensitive values in a variable without a default and inject them via environment variables or CI secrets; add the state directory to .gitignore and enable encryption on the remote backend.

Symptomapply hangs waiting for confirmation in CI and the pipeline times out

CauseThe auto-approve flag was not added, so the command stops at interactive confirmation.

FixAlways use terraform apply -auto-approve in non-interactive environments, and make sure the pipeline has completed the init and plan review steps.

SymptomAfter switching count to for_each (or vice versa), Terraform wants to destroy and rebuild the entire batch of resources

CauseThe two use different resource address forms, so switching is equivalent to changing identity.

FixBefore switching, use terraform state mv to migrate existing resource addresses where possible; or, when accepting the rebuild cost, evaluate the downtime window first.

Troubleshooting

  1. 1Confirm exactly which resources are in the local state

    terraform state list

    The resource addresses in the output are the first-hand evidence for troubleshooting "missing resources / extra resources".

  2. 2View the actual state attributes of a single resource

    terraform state show aws_instance.web

    Use it to compare config with the real resource and locate the source of drift.

  3. 3Output state as JSON for grep/jq analysis

    terraform show -json

    Good for counting resources, comparing IDs in scripts, or diffing against plan results.

  4. 4Interactively verify whether an expression evaluates correctly

    terraform console

    Enter expressions like var.region or local.name and see the result directly—much faster than editing and running plan.

  5. 5Turn on detailed logging and write it to disk

    TF_LOG=DEBUG TF_LOG_PATH=./tf.log terraform plan

    The log shows the API requests and responses the provider actually sends—key to troubleshooting "the external system cannot change the resource".

  6. 6Expose potential syntax problems with a code-format check

    terraform fmt -check -diff

    Used as a CI gate; the diff output is the set of files that do not conform to the standard.

Command Examples

Review the diff with plan before applying

terraform plan -out=tfplan terraform show tfplan

With plan -out saved to a file, you can review it with terraform show tfplan, then run terraform apply tfplan to execute the same plan—guaranteeing that what runs is the version you reviewed.

Output

  # aws_instance.web will be updated in-place
  ~ resource "aws_instance" "web" {
      ~ instance_type = "t2.micro" -> "t3.medium"
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Detect drift between reality and state

terraform plan -detailed-exitcode echo $?

Exit codes: 0 = no changes, 1 = error, 2 = changes that are non-destructive. This convention lets it be used directly in CI for infrastructure drift detection.

Output

2

Make Terraform forget a resource

terraform state rm aws_instance.web terraform plan

state rm only changes state without touching the real resource, and the following plan then shows a rebuild—exactly "delete and recreate" rather than accidental deletion. Using destroy directly really does delete the machine.

Output

  # aws_instance.web will be created
  + resource "aws_instance" "web" {
      + id = "i-0abc123"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Common Pitfalls

  • Always review plan before apply, and never pass -auto-approve to a plan containing destroys without reading it.
  • State contains sensitive data and must not be committed; production needs a remote backend with locking enabled.
  • terraform destroy removes every managed resource including databases; use -target or remove the resource from config to drop one.
  • Changing an for_each key destroys and recreates the whole resource — keys must be stable identifiers.
  • Run terraform workspace show before acting; operating on the wrong workspace between prod and staging is a real incident.
  • state rm makes a hand-created resource look missing and get recreated — use it to correct drift, not to hide it.

Tips

  • terraform plan never modifies resources — use it freely
  • Keep secrets in variables; never hardcode them in .tf files
  • Use terraform fmt to standardize formatting — essential for teams
  • The state file holds sensitive data; never commit it to Git
  • Use a remote backend (e.g. S3 + DynamoDB) for collaboration and state locking

FAQ

Why run terraform plan separately from apply?

plan computes and displays the diff without touching anything; apply performs the create, change or destroy. Running plan first inserts a human checkpoint, which matters most for changes that rebuild or delete resources. A stronger workflow is to save the plan to a file and re-apply that exact plan, guaranteeing you execute precisely what you reviewed.

What is the difference between terraform state rm and terraform destroy?

destroy actually deletes the cloud resource and drops it from state; state rm only removes the entry from the state file while leaving the real resource running. They solve different problems. When state has drifted from reality, state rm makes Terraform forget the resource so it does not plan a destroy-then-recreate. Use destroy only when you actually want to reclaim the infrastructure.

When should I switch Terraform workspaces?

Use one workspace per environment — staging, prod — created with terraform workspace new staging, switched with select, listed with list. Note that all workspaces share the same backend state file, so you must ensure resource names do not collide across them. Because workspaces are easy to get wrong, always run terraform workspace show before applying to confirm which environment you are actually in.

Should I use count or for_each to create resources in bulk?

Use count when the instances are identical and only the quantity differs; use for_each when each instance needs different configuration, such as its own subnet or size. A crucial caveat: changing an for_each key destroys and recreates that entire resource, so design keys to be stable identifiers. Likewise, reordering a list used with count shifts indices and triggers recreation.

Should the Terraform state file be committed to Git?

No. State records the mapping to real resources, contains sensitive data, and produces painful merge conflicts. Configure a remote backend (backend "s3" or similar) so it lives in object storage or Terraform Cloud, keeping only configuration in the repository. Teams should also enable state locking — a DynamoDB lock for S3, or Terraform Cloud — or two concurrent applies will overwrite each other.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us