---
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.
