---
title: "Terraform Cheatsheet"
description: "Practical Terraform cheatsheet covering essential CLI commands, state management, import, workspaces, and key HCL patterns for variables, locals, outputs, dynamic blocks, and more. Perfect for daily IaC workflows."
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/cheatsheets/terraform
---

# Terraform Cheatsheet

A practical cheatsheet covering the Terraform CLI commands engineers reach for constantly — init, plan, apply, import, taint, and state operations — alongside essential HCL patterns for variables, locals, outputs, and dynamic blocks.

Browse the sections below to explore Terraform cheatsheet commands, syntax, and examples.

## Core CLI Commands

Essential Terraform commands for initialization, validation, planning, applying, and destroying infrastructure.

### Initialization & Validation

Commands to set up your Terraform workspace and check configuration validity.

**Keywords:** terraform init

#### Initializes the working directory, downloads providers and modules.

```bash
terraform init
```

_output_
```text
Terraform has been successfully initialized!
```

#### Checks whether the configuration is valid (run after init).

```bash
terraform validate
```

_output_
```text
Success! The configuration is valid.
```

#### Upgrades modules and providers to the latest allowed versions.

```bash
terraform init -upgrade
```

**Best practices:**

- Run terraform init early and often. Commit .terraform.lock.hcl to version control.
- Use -input=false in automation to avoid interactive prompts.

**Common errors:**

- **Provider not found — ensure required_providers block is declared.**: Review documentation or configuration.

**Advanced notes:**

- **Note:** terraform init -migrate-state helps when changing backends.

### Plan & Apply

Preview and execute infrastructure changes safely.

**Keywords:** terraform plan, terraform apply

#### Shows what Terraform will create, change, or destroy.

```bash
terraform plan
```

#### Saves the plan to a file for later apply.

```bash
terraform plan -out=plan.tfplan
```

#### Applies a saved plan without prompting.

```bash
terraform apply plan.tfplan
```

#### Applies changes without confirmation (use in CI/CD).

```bash
terraform apply -auto-approve
```

**Best practices:**

- Never run terraform apply in production without reviewing the plan first.
- Use -target for surgical changes, but prefer smaller modules for isolation.

**Common errors:**

- **Permission denied or authentication issues with the provider.**: Review documentation or configuration.

**Advanced notes:**

- **Note:** terraform apply -replace=resource.address forces replacement without tainting.

### Destroy

Safely remove all managed infrastructure.

**Keywords:** terraform destroy

#### Destroys all resources managed by Terraform.

```bash
terraform destroy
```

#### Destroys only the targeted resource.

```bash
terraform destroy -target=aws_instance.example
```

**Best practices:**

- Use with -auto-approve only in controlled environments.

**Common errors:**

- **Resources with prevent_destroy lifecycle rule will block destruction.**: Review documentation or configuration.

**Advanced notes:**

- **Note:** terraform plan -destroy shows what will be removed.

## State Management

Commands for inspecting, manipulating, and synchronizing Terraform state.

### State Commands

Core operations on the Terraform state file.

**Keywords:** state management

#### Lists all resources currently tracked in state.

```bash
terraform state list
```

#### Shows detailed attributes of a specific resource in state.

```bash
terraform state show aws_instance.example
```

#### Moves or renames a resource in state without recreating it.

```bash
terraform state mv aws_instance.old aws_instance.new
```

#### Removes a resource from state (does not delete the actual infrastructure).

```bash
terraform state rm aws_instance.example
```

**Best practices:**

- Use terraform state mv when refactoring resource addresses or module structure.
- Pull state before manual edits and push after.

**Common errors:**

- **Moving resources incorrectly can lead to orphaned infrastructure.**: Review documentation or configuration.

**Advanced notes:**

- **Note:** terraform state pull > state.tfstate && terraform state push state.tfstate for manual sync.

### Import & Taint

Bring existing resources under management and force recreation.

**Keywords:** terraform import

#### Imports an existing EC2 instance into Terraform state.

```bash
terraform import aws_instance.example i-1234567890abcdef0
```

#### Replaces (recreates) a resource on the next apply (preferred over taint).

```bash
terraform apply -replace=aws_instance.example
```

**Best practices:**

- After import, run terraform plan and update configuration to match imported state.
- Prefer import blocks (Terraform 1.5+) for declarative imports.

**Common errors:**

- **Import succeeds but plan shows drift — configuration must match reality.**: Review documentation or configuration.

**Advanced notes:**

- **Note:** For count/for_each resources, use proper addressing like aws_instance.example["key"].

### State Pull & Push

Synchronize local and remote state files.

**Keywords:** state management

#### Downloads the current remote state.

```bash
terraform state pull
```

#### Uploads a local state file to the remote backend.

```bash
terraform state push state.tfstate
```

**Best practices:**

- Use only when necessary; let Terraform handle state automatically with backends.

**Common errors:**

- **Pushing an outdated state can cause conflicts or data loss.**: Review documentation or configuration.

**Advanced notes:**

- **Note:** Always backup state before push operations.

## Workspaces

Manage multiple isolated state environments (dev, staging, prod) with the same code.

### Workspace Commands

Create, switch, list, and delete workspaces.

**Keywords:** terraform workspace

#### Creates and switches to a new workspace named 'dev'.

```bash
terraform workspace new dev
```

#### Lists all workspaces (* indicates current).

```bash
terraform workspace list
```

#### Switches to the 'prod' workspace.

```bash
terraform workspace select prod
```

#### Deletes the 'dev' workspace (must not be current).

```bash
terraform workspace delete dev
```

**Best practices:**

- Use workspaces for environment isolation when using the same configuration.
- Combine with variable files: terraform apply -var-file=dev.tfvars

**Common errors:**

- **Deleting a workspace with resources leaves orphaned infrastructure.**: Review documentation or configuration.

**Advanced notes:**

- **Note:** Workspaces are not recommended for large team or complex environment separation — consider modules or separate repositories.

## HCL Essentials

Core HashiCorp Configuration Language patterns used in every Terraform project.

### Variables, Locals & Outputs

Input variables, computed locals, and exposed outputs.

**Keywords:** variables

#### Defines a variable with default, a local for reuse, and an output.

```bash
variable "region" {
  type = string
  default = "us-east-1"
}

locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}

output "vpc_id" {
  value       = aws_vpc.main.id
  description = "The ID of the VPC"
}
```

**Best practices:**

- Keep variables minimal and descriptive; use locals for derived values to reduce repetition.
- Always add descriptions to outputs and sensitive = true where needed.

**Common errors:**

- **Variable not defined — declare it or pass via -var or .tfvars file.**: Review documentation or configuration.

**Advanced notes:**

- **Note:** Use data sources to fetch dynamic values instead of hardcoding in variables.

### Data Sources

Read-only queries for external data.

**Keywords:** data sources

#### Fetches the latest Ubuntu AMI for use in resources.

```bash
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }
}
```

**Best practices:**

- Use data sources to avoid hardcoding IDs or dynamic values.

**Common errors:**

- **Data source returns empty — check filters and permissions.**: Review documentation or configuration.

**Advanced notes:**

- **Note:** Data sources run during plan and can depend on resources.

## Dynamic Blocks & for_each

Advanced HCL patterns for repeatable configuration.

### for_each & Dynamic Blocks

Create multiple resource instances and nested blocks dynamically.

**Keywords:** for_each

#### Creates one IAM user per item in the set using for_each.

```bash
resource "aws_iam_user" "users" {
  for_each = toset(["alice", "bob"])
  name     = each.key
}
```

#### Generates multiple ingress blocks dynamically from a list/map.

```bash
resource "aws_security_group" "example" {
  name = "example"

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}
```

**Best practices:**

- Use for_each over count for readability and stability (keys instead of indices).
- Limit dynamic blocks to improve readability; prefer explicit blocks when possible.

**Common errors:**

- **for_each value must be a map or set — convert lists with toset().**: Review documentation or configuration.

**Advanced notes:**

- **Note:** Nested dynamic blocks are supported with different iterator names.

## Provider Configuration

Common patterns for configuring Terraform providers.

### Provider Patterns

Declaring and configuring providers, including aliases.

**Keywords:** modules

#### Required providers block and basic + aliased configuration.

```bash
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.region
}

# Aliased provider for multi-region
provider "aws" {
  alias  = "west"
  region = "us-west-2"
}
```

**Best practices:**

- Declare required_providers in the root module. Avoid configuring providers inside child modules.
- Use aliases for multi-region or multi-account deployments.

**Common errors:**

- **Provider not initialized — run terraform init after adding providers.**: Review documentation or configuration.

**Advanced notes:**

- **Note:** Pass aliased providers to modules explicitly via the providers = { ... } meta-argument.

## Debugging

Tools and techniques for troubleshooting Terraform issues.

### Logging & Terraform Console

Enable detailed logs and test expressions interactively.

**Keywords:** terraform console

#### Enables the most verbose logging (TRACE, DEBUG, INFO, WARN, ERROR).

```bash
TF_LOG=TRACE terraform plan
```

#### Interactive REPL to test expressions and functions.

```bash
terraform console
```

_output_
```text
> join(", ", ["a", "b"])
"a, b"
```

**Best practices:**

- Start with DEBUG or TRACE only when needed; logs can be extremely verbose.
- Use terraform console to validate complex expressions before using them in code.

**Common errors:**

- **No output from console — ensure you're in a directory with initialized Terraform config.**: Review documentation or configuration.

**Advanced notes:**

- **Note:** Set TF_LOG_PATH to write logs to a file.

## Modules

Package and reuse configurations with module blocks sourced from local paths, Git, or public/private registries with version pinning.

### Calling a Module

A module block instantiates reusable config; source is required and version applies only to registry sources.

**Keywords:** module, source, outputs

#### Call a registry module with a version constraint

```hcl
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws" # NAMESPACE/NAME/PROVIDER
  version = "~> 5.0"                        # pessimistic constraint
  name    = "prod-vpc"
  cidr    = "10.0.0.0/16"
}
```

Instantiates a child module from the public registry; version is honored only for registry sources.

- Modules also accept count, for_each, depends_on, and an explicit providers mapping.

#### Reference a module output

```hcl
resource "aws_instance" "web" {
  subnet_id = module.vpc.private_subnet_ids[0] # module.<NAME>.<OUTPUT>
}
```

A module exposes only values declared as output blocks; reach them as module.<name>.<output>.

**Best practices:**

- Keep module interfaces small - expose IDs and ARNs via output, take everything configurable via variable, and never reach into a module's internal resources from the caller.

**Common errors:**

- **Module not installed or "Module source has changed" on plan.**: Run terraform init (or terraform get) to fetch modules. After changing source or version you must re-run terraform init - plain terraform get will not update the lock.

### Source Types & terraform get

Modules load from local paths, Git, or registries; terraform get and init keep them current.

**Keywords:** git, registry, terraform get

#### Source a module from Git at a pinned tag

```hcl
module "network" {
  # // separates the repo from a subdirectory, ?ref pins a tag/branch/commit
  source = "git::https://github.com/org/repo.git//modules/vpc?ref=v1.4.0"
}
```

Git source with a subdirectory and a pinned ref for reproducible init.

- The GitHub shorthand github.com/org/repo//modules/vpc?ref=v1.4.0 expands to this HTTPS form.

#### Download and upgrade modules

```bash
terraform get            # download modules into .terraform/modules
terraform get -update    # upgrade modules to the newest allowed
terraform init -upgrade  # upgrade modules AND providers, rewrite the lock
```

terraform get fetches modules only; init -upgrade also handles providers and rewrites .terraform.lock.hcl.

**Best practices:**

- Use terraform init -upgrade when intentionally bumping versions and commit the updated lock; use plain terraform init in CI to enforce the locked versions.

## Variables & Outputs

Parameterize configurations with typed, validated inputs and expose results through outputs, supplied via tfvars, flags, or environment.

### Declaration, Types & Validation

Declare typed input variables with optional defaults and custom validation rules checked at plan time.

**Keywords:** variable, validation, type

#### Typed variable with a default

```hcl
variable "instance_count" {
  type    = number # string | number | bool | list()/set()/map()/object()
  default = 2      # omit default to make the variable required
}
```

Without a default the variable is required; complex types use list(...), map(...), object({...}).

#### Enforce allowed values with validation

```hcl
variable "env" {
  type = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.env)
    error_message = "env must be dev, staging, or prod."
  }
}
```

The validation block fails fast at plan time; error_message must be a full sentence ending in a period.

**Best practices:**

- Give every input a type and a description, and use validation to reject bad input up front rather than letting the provider fail mid-apply.

### Supplying Values & Outputs

Values come from tfvars, -var flags, or TF_VAR_ env vars by precedence; outputs surface results and can be marked sensitive.

**Keywords:** tfvars, TF_VAR, output

#### Pass variables by flag, file, or environment

```bash
terraform apply -var="instance_count=3"   # inline (highest precedence)
terraform apply -var-file="prod.tfvars"    # from a file
export TF_VAR_instance_count=3             # environment form
```

Precedence low to high - TF_VAR_* < terraform.tfvars < *.auto.tfvars < -var/-var-file (last on the line wins).

- terraform.tfvars and *.auto.tfvars load automatically; named files need -var-file.

#### Declare an output and read it for scripts

```bash
# output "db_endpoint" { value = aws_db_instance.main.address }
terraform output                  # all outputs, human-readable
terraform output -json            # machine-readable
terraform output -raw db_endpoint # raw string, no quotes
```

-raw and -json are the scripting-friendly forms; mark secrets sensitive = true to redact them from CLI and plan output.

**Best practices:**

- Mark credential outputs sensitive = true, but remember state is plaintext - protect the backend rather than relying on sensitive as encryption.

**Common errors:**

- **"No value for required variable" in non-interactive CI.**: Pass -var/-var-file or set TF_VAR_*, and add -input=false so the run fails fast instead of hanging on a prompt.

## Backends & Remote State

Store state remotely with locking (S3, HCP Terraform) and consume other stacks' outputs via the terraform_remote_state data source.

### S3 Backend with Native Locking

Store state in S3 with encryption and a native lockfile (Terraform 1.10+), replacing the deprecated DynamoDB lock table.

**Keywords:** backend, s3, locking

#### S3 backend with native lockfile (TF 1.10+)

```hcl
terraform {
  backend "s3" {
    bucket       = "my-tf-state"
    key          = "prod/network/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true # native S3 lock, no DynamoDB (TF 1.10+)
  }
}
```

use_lockfile uses an S3 conditional-write lock object; requires bucket versioning. dynamodb_table is deprecated as of 1.11.

- Enable bucket versioning and encrypt = true; you can run use_lockfile and dynamodb_table together during migration, then drop DynamoDB.

**Best practices:**

- Back up state before any backend change with terraform state pull > backup.tfstate.

**Common errors:**

- **Error acquiring the state lock (stale lock).**: Resolve the interrupted run, then terraform force-unlock <LOCK_ID>. Never force-unlock while another apply may still be live.

### Remote State & Migration

Read another config's outputs with terraform_remote_state, connect to HCP Terraform, and migrate between backends.

**Keywords:** terraform_remote_state, cloud, migrate-state

#### Consume another stack's outputs

```hcl
data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "my-tf-state"
    key    = "prod/network/terraform.tfstate"
    region = "us-east-1"
  }
}
# use: data.terraform_remote_state.network.outputs.private_subnet_ids[0]
```

Read-only access to another configuration's published outputs; only declared output values are visible.

#### Migrate or reconfigure the backend

```bash
terraform init -migrate-state   # copy state into a newly changed backend
terraform init -reconfigure     # reinit backend, ignore existing state
terraform init -backend-config=backend.hcl # partial config from a file
```

-migrate-state copies existing state into the new backend; -reconfigure discards the old association without copying.

**Best practices:**

- Use the cloud block (HCP Terraform, renamed from Terraform Cloud) for managed runs; it is mutually exclusive with a backend block. Run terraform login first.

## Functions & Expressions

Transform and compute values with built-in functions, for/conditional/splat expressions, and the interactive console.

### Common Built-in Functions

Encode/decode data, merge maps, provide defaults, and compute network ranges with built-in functions.

**Keywords:** functions, templatefile, jsonencode

#### Everyday functions

```hcl
user_data = templatefile("${path.module}/init.tftpl", { port = var.port })
config    = jsonencode({ name = var.name, tags = var.tags })
merged    = merge(var.default_tags, var.extra_tags) # right map wins
name      = coalesce(var.name, "unnamed")           # first non-empty
port      = try(var.settings.port, 8080)            # first that succeeds
subnet    = cidrsubnet("10.0.0.0/16", 8, 4)         # -> 10.0.4.0/24
```

templatefile renders external templates (use it instead of the removed template_file data source); try swallows evaluation errors.

- jsondecode/yamldecode parse strings back into HCL values; lookup(map, key, default) reads a map with a fallback.

**Best practices:**

- Reach for try() to handle optional nested attributes gracefully, but do not let it silently mask real configuration errors.

### for, Conditionals & Console

Reshape collections with for expressions, choose with conditionals, collect with splat, and prototype in terraform console.

**Keywords:** for, splat, console

#### for, conditional, and splat expressions

```hcl
upper_names   = [for n in var.names : upper(n) if n != ""] # list + filter
by_id         = { for s in var.subnets : s.id => s.cidr }  # produce a map
instance_type = var.env == "prod" ? "m5.large" : "t3.micro" # ternary
all_ips       = aws_instance.web[*].private_ip             # splat
```

[...] yields a list, {...} yields a map; splat [*] pulls one attribute across every instance.

#### Prototype expressions in the console

```bash
terraform console                                  # interactive REPL
echo 'cidrsubnet("10.0.0.0/16", 8, 2)' | terraform console # pipe input
```

Evaluates functions, variables, and resource attributes against real state without running a plan.

**Best practices:**

- Prototype tricky for and function expressions in terraform console before wiring them into config - it evaluates against actual state values.

## Testing & Validation

Validate, format, and test configurations with the native test framework, lifecycle assertions, check blocks, and ecosystem linters.

### validate & fmt

Check syntax and enforce canonical style, with non-mutating forms for CI gates.

**Keywords:** validate, fmt, ci

#### Validate and format

```bash
terraform validate              # syntax + internal consistency
terraform fmt -check -recursive  # exit non-zero if anything is unformatted
terraform fmt -diff              # show changes without writing
```

validate needs init first; fmt -check -recursive is the CI-friendly, non-mutating form.

**Best practices:**

- Run terraform fmt -check -recursive and terraform validate as required PR checks so style and basic errors never reach review.

**Common errors:**

- **terraform validate reports "Backend initialization required".**: Run terraform init -backend=false first - validate needs providers and modules installed but not a live backend.

### terraform test & Assertions

Write native tests in .tftest.hcl and enforce invariants with precondition, postcondition, and check blocks.

**Keywords:** test, precondition, check

#### A native test file (TF 1.6+)

```hcl
# tests/vpc.tftest.hcl
run "creates_vpc" {
  command   = plan # "plan" (fast) or "apply" (real infra)
  variables { cidr = "10.0.0.0/16" }
  assert {
    condition     = aws_vpc.this.cidr_block == "10.0.0.0/16"
    error_message = "VPC CIDR did not match input."
  }
}
```

The native framework is stable in Terraform 1.6+; run terraform test to execute every *.tftest.hcl file.

- Prefer command = plan for fast unit-style assertions; command = apply creates then destroys real infrastructure.

#### Lifecycle and check assertions

```hcl
lifecycle {
  precondition {
    condition     = data.aws_ami.selected.architecture == "x86_64"
    error_message = "AMI must be x86_64."
  }
}
# check "health" { ... } -> failed check is a WARNING, not an error (TF 1.5+)
```

precondition/postcondition (TF 1.2+) hard-stop on failure; check blocks (TF 1.5+) only warn, good for post-deploy validation.

**Best practices:**

- Layer the tools - terraform validate for correctness, tflint for provider-aware linting, and checkov for security policy - rather than expecting one tool to cover all three.
