The Ultimate Terraform Cheatsheet for DevOps Engineers
Whether you are spinning up your very first AWS EC2 instance or managing a massive, multi-region Kubernetes cluster, Terraform is the undisputed king of Infrastructure as Code (IaC).
However, remembering every CLI flag, HCL block type, and state management command is nearly impossible. I built this comprehensive cheatsheet as a quick reference guide for my own daily DevOps workflows, and I am sharing it here so you can bookmark it and use it too.
☕ If this cheatsheet saves you time at work today, consider buying me a coffee to support the blog!
⌨️CORE CLI COMMANDS
Workflow Commands
| ⌨ Command | ℹ️ Description |
|---|---|
terraform init |
Initialize working directory, download providers & modules |
terraform init -upgrade |
Upgrade providers to latest allowed versions |
terraform init -reconfigure |
Reconfigure backend (ignores existing state) |
terraform validate |
Validate configuration syntax & internal consistency |
terraform fmt |
Reformat .tf files to canonical HCL style |
terraform fmt -recursive |
Format all files in directory tree |
terraform plan |
Preview changes before applying |
terraform plan -out=plan.tfplan |
Save plan to file for later apply |
terraform apply |
Apply changes to infrastructure |
terraform apply plan.tfplan |
Apply a saved plan file |
terraform apply -auto-approve |
Apply without interactive approval prompt |
terraform destroy |
Destroy all managed infrastructure |
terraform destroy -auto-approve |
Destroy without interactive approval |
Inspect & Query Commands
| ⌨ Command | ℹ️ Description |
|---|---|
terraform show |
Display current state or saved plan (human-readable) |
terraform show -json plan.tfplan |
Output plan as JSON for machine parsing |
terraform output |
Display all output values |
terraform output <name> |
Display a specific output value |
terraform output -json |
Display outputs as JSON |
terraform state list |
List all resources in state file |
terraform state show <resource> |
Show detailed attributes of a resource |
terraform state pull |
Download & print current remote state |
terraform state push |
Manually upload local state to remote backend |
terraform providers |
Show providers required by configuration |
terraform version |
Print Terraform and provider versions |
terraform graph |
Generate DOT-format dependency graph |
Planning & Targeting Flags
| Flag | ℹ️ Description |
|---|---|
terraform plan -var 'k=v' |
Pass a single variable on CLI |
terraform plan -var-file=f.tfvars |
Load variables from a file |
terraform plan -target=aws_instance.web |
Plan only specific resource |
terraform plan -replace=aws_instance.web |
Force replace a specific resource |
terraform plan -refresh=false |
Skip refreshing state from real infra |
terraform plan -parallelism=10 |
Set concurrency for resource operations |
terraform apply -compact-warnings |
Show condensed warning messages |
terraform apply -lock=false |
Disable state locking (use with caution) |
terraform apply -lock-timeout=60s |
Wait up to N seconds to acquire lock |
📄HCL LANGUAGE REFERENCE
Block Types
| Block Type | Kind | Purpose |
|---|---|---|
terraform {} |
terraform | Global Terraform settings, required_providers, backend |
provider {} |
provider | Configure a provider (AWS, Azure, GCP, etc.) |
resource {} |
resource | Declare a managed infrastructure resource |
data {} |
data source | Read existing infrastructure or external data |
variable {} |
variable | Declare an input variable |
output {} |
output | Expose values to the caller or user |
locals {} |
locals | Define local computed values (like constants) |
module {} |
module | Embed a reusable Terraform module |
moved {} |
moved | Rename/move resources without destroy & recreate |
import {} |
import | Import existing infra into Terraform state (v1.5+) 🆕 |
check {} |
check | Define custom validation assertions (v1.5+) 🆕 |
Data Types & Values
| Type | Example | ℹ️ Description |
|---|---|---|
| string | "hello" | UTF-8 text |
| number | 42 / 3.14 | Integer or float |
| bool | true / false | Boolean value |
| list(type) | ["a", "b"] | Ordered sequence of values |
| set(type) | toset(["a","b"]) | Unordered unique values |
| map(type) | {key = "value"} | Key-value lookup table |
| object({}) | {name="x", count=1} | Named attribute collection |
| tuple([]) | ["a", 1, true] | Fixed-length mixed-type sequence |
| any | any type | Type constraint that accepts any type |
| null | null | Absence of a value |
Expressions & Built-in Functions
String & Template
name = "server-\({var.env}-\){count.index}"String interpolation desc = <<-EOT multi-line text here EOTHeredoc string upper(var.region)"us-east-1" → "US-EAST-1" replace("us-east-1", "-", "_")"us_east_1" split(",", "a,b,c")["a","b","c"] join("-", ["a","b","c"])"a-b-c" templatefile("tpl.tftpl", { port = 8080 })Render template file Collection Functions
length(var.items)Count items flatten([[1,2],[3]])[1,2,3] distinct(["a","b","a"])["a","b"] concat(["a"], ["b","c"])["a","b","c"] merge({a=1}, {b=2}){a=1, b=2} toset(["a","b","a"])set("a","b") tolist(toset(["b","a"]))["a","b"] zipmap(["a","b"], [1,2]){a=1, b=2} lookup(var.map, "key", "default")Safe map lookup contains(["a","b"], "a")true index(["x","y","z"], "y")1 element(["a","b","c"], 2)"c" (wraps around) slice(["a","b","c","d"], 1, 3)["b","c"] Numeric & Encoding
min(5, 2, 9) / max(5, 2, 9) / abs(-3) / ceil(1.2) / floor(1.9) pow(2, 10) # 1024 base64encode("hello") / base64decode("aGVsbG8=") jsonencode({key = "val"}) / jsondecode("{"key":"val"}") yamlencode({key = "val"}) / yamldecode("key: val") md5("content") / sha256("content") / bcrypt("password") filebase64("path/to/file") / filemd5("file.txt")Filesystem & Date
file("path/to/file.txt")Read file as string
fileexists("optional.tf")true/false
fileset(path.module, "scripts/*.sh")List matching files
timestamp()"2024-01-15T10:30:00Z"
formatdate("YYYY-MM-DD", timestamp())"2024-01-15"
timeadd(timestamp(), "24h")Add duration to time
Type Conversion & Conditionals
can(var.optional_attr)true if no error
try(resource.name, "fallback")Return first non-error
coalesce("", null, "first-non-empty")"first-non-empty"
coalescelist([], null, ["a","b"])["a","b"]
condition ? true_val : false_valTernary operator
one([])null | one(["x"])
"x"
Assert single elementsensitive("secret") / nonsensitive(local.val)Sensitivity mark
🧱 RESOURCES & META-ARGUMENTS
Meta-Arguments Reference
| Meta-Argument | ℹ️ Description |
|---|---|
count = N |
Create N identical copies of resource; use count.index to differentiate |
count = var.enabled ? 1 : 0 |
Conditionally create a resource (0 = don't create) |
for_each = var.map |
Create one resource per map entry; access key/value via each.key / each.value |
for_each = toset(var.list) |
Create one resource per list item; each.key = each.value = item |
depends_on = [res.x] |
Explicit dependency when implicit reference isn't sufficient |
lifecycle { create_before_destroy = true } |
Create replacement before destroying old resource |
lifecycle { prevent_destroy = true } |
Block resource from being destroyed (terraform error) |
lifecycle { ignore_changes = [tags] } |
Ignore drift in specific attributes |
lifecycle { replace_triggered_by = [var.x] } |
Replace resource when variable changes |
provider = aws.useast2 |
Use non-default provider alias for this resource |
timeouts { create = '30m' } |
Override default API timeout for create/update/delete |
Resource References & Expressions
# Reference another resource
subnet_id = aws_subnet.main.id
# Reference a data source
ami = data.aws_ami.ubuntu.id
# Self reference inside resource
name = self.id
# Module output reference
vpc_id = module.network.vpc_id
# For-each resource reference
bucket = aws_s3_bucket.buckets[each.key].id
# Count resource reference (index)
security_groups = [aws_security_group.sg[count.index].id]
# Splat operator - get all IDs from count/for_each
instance_ids = aws_instance.servers[*].id
# Dynamic block - repeat nested blocks
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
}
}
📥VARIABLES & OUTPUTS
Variable Block Syntax
variable "instance_type" {
type = string # Data type constraint
default = "t3.micro" # Default value (optional var)
description = "EC2 instance type"
sensitive = false # Hide from logs/output
nullable = false # Disallow null (v1.1+)
validation { # Custom validation rule
condition = contains(["t3.micro","t3.small"], var.instance_type)
error_message = "Must be t3.micro or t3.small."
}
}
# Complex type constraints
variable "tags" {
type = map(string)
}
variable "subnets" {
type = list(object({
cidr = string
az = string
}))
}
Variable Precedence (Lowest to Highest)
| Source | Priority |
|---|---|
| default value | 1. Default in variable block (lowest priority) |
| terraform.tfvars | 2. Auto-loaded variable file (alphabetical order) |
| *.auto.tfvars | 3. Auto-loaded .auto.tfvars files |
| -var-file=custom.tfvars | 4. Explicitly loaded var file (CLI flag) |
| -var 'key=value' | 5. CLI -var flag |
| TF_VAR_name env variable | 6. Environment variable TF_VAR_ (highest) |
Output Block Syntax
output "instance_ip" {
value = aws_instance.web.public_ip
description = "Public IP of the web server"
sensitive = true # Mask value in CLI output
depends_on = [aws_eip.web] # Explicit output dependency
}
# Reference module output in parent
module.mymodule.output_name
🔍LOCALS & DATA SOURCES
Locals
locals {
# Computed values — evaluated once, referenced many times
env = terraform.workspace
region = var.aws_region
name_prefix = "\({var.project}-\){local.env}"
common_tags = {
Project = var.project
Environment = local.env
ManagedBy = "Terraform"
}
# Conditional logic
instance_type = local.env == "prod" ? "t3.large" : "t3.micro"
}
Data Sources
# Query existing AWS resources
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-*-22.04-amd64-server-*"]
}
}
data "aws_vpc" "default" { default = true }
data "aws_subnets" "public" { filter { name="vpc-id" values=[data.aws_vpc.default.id] } }
data "aws_caller_identity" "current" {} # Get account ID
data "aws_region" "current" {} # Get current region
data "aws_availability_zones" "available" { state = "available" }
# HTTP data source (requires hashicorp/http provider)
data "http" "my_ip" { url = "https://api.ipify.org" }
# Template file data source
data "template_file" "init" {
template = file("${path.module}/init.sh.tpl")
vars = { server_name = var.name }
}
📦MODULES
Module Sources
| Source Pattern | ℹ️ Description |
|---|---|
| source = "./modules/vpc" | Local path module |
| source = "github.com/org/repo//vpc" | GitHub (// = subdir) |
| source = "git::https://...//mod" | Any Git repo via HTTPS |
| source = "git::ssh://...//mod?ref=v2" | Git with specific branch/tag |
| source = "registry.terraform.io/org/mod/aws" | Public Terraform Registry |
| source = "app.terraform.io/org/mod/aws" | Private Terraform Registry |
| source = "s3::https://s3.amazonaws.com/bucket/module.zip" | S3 archive |
| source = "gcs::https://storage.googleapis.com/bucket/mod.zip" | GCS archive |
Module Block Example
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0" # Version constraint
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
single_nat_gateway = var.environment != "prod"
}
# Access module outputs
resource "aws_instance" "web" {
subnet_id = module.vpc.private_subnets[0]
}
Version Constraints
| Constraint | Meaning |
|---|---|
= 1.2.0 |
Exact version only |
!= 1.2.0 |
Exclude this version |
> 1.2.0 |
Greater than |
>= 1.2.0 |
Greater than or equal |
< 2.0.0 |
Less than |
~> 1.2 |
Allow patch: >= 1.2, < 2.0 (pessimistic constraint) |
~> 1.2.3 |
Allow patch only: >= 1.2.3, < 1.3.0 |
>= 1.0, < 2.0 |
Range (multiple constraints combined) |
🗄️BACKENDS & STATE MANAGEMENT
Backend Configuration Examples
☁️ S3 Backend (AWS)
terraform { backend "s3" { bucket = "my-tfstate-bucket" key = "envs/prod/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "terraform-state-lock" # State locking table role_arn = "arn:aws:iam::123:role/TerraformRole" } }☁️ Azure Blob Storage Backend
terraform { backend "azurerm" { resource_group_name = "rg-terraform-state" storage_account_name = "tfstatestorage" container_name = "tfstate" key = "prod.terraform.tfstate" } }☁️ GCS Backend (GCP)
terraform { backend "gcs" { bucket = "my-terraform-state" prefix = "terraform/state" } }Terraform Cloud / HCP Terraform
terraform { cloud { organization = "my-org" workspaces { name = "my-workspace" } } }
State Management Commands
| ⌨ Command | ℹ️ Description |
|---|---|
terraform state list |
List all resources tracked in state |
terraform state show <addr> |
Show attributes of a specific resource |
terraform state mv <src> <dst> |
Move/rename a resource in state |
terraform state rm <addr> |
Remove resource from state (without destroying) |
terraform state replace-provider |
Change provider for tracked resources |
terraform import <addr> <id> |
Import existing resource into state (CLI) |
terraform force-unlock <lock-id> |
Break a stuck state lock (use carefully) |
terraform refresh |
Sync state with real infrastructure (⚠️ deprecated) |
terraform taint <addr> |
Mark resource for recreation (⚠️ deprecated, use -replace) |
terraform untaint <addr> |
Remove taint from resource (⚠️ deprecated) |
🗂️WORKSPACES
Workspace Commands
| ⌨ Command | ℹ️ Description |
|---|---|
terraform workspace list |
List all workspaces (* marks current) |
terraform workspace new staging |
Create and switch to new workspace |
terraform workspace select prod |
Switch to existing workspace |
terraform workspace show |
Print current workspace name |
terraform workspace delete staging |
Delete a workspace (must not be current) |
Workspace Usage in Config
locals {
env = terraform.workspace # "default", "staging", "prod"
instance_count = {
default = 1
staging = 1
prod = 3
}
}
resource "aws_instance" "web" {
count = local.instance_count[terraform.workspace]
# ...
}
🔌PROVIDERS & MULTI-REGION / MULTI-ACCOUNT
required_providers Block
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = ">= 2.20"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.10"
}
}
}
Provider Aliases (Multi-Region / Multi-Account)
# Default provider
provider "aws" {
region = "us-east-1"
}
# Alias for second region
provider "aws" {
alias = "eu_west"
region = "eu-west-1"
}
# Alias for different account (using role assumption)
provider "aws" {
alias = "prod_account"
assume_role {
role_arn = "arn:aws:iam::999999999999:role/TerraformRole"
}
region = "us-east-1"
}
# Use specific alias in a resource
resource "aws_s3_bucket" "eu_bucket" {
provider = aws.eu_west
bucket = "my-eu-bucket"
}
# Pass alias into module
module "replica" {
source = "./modules/replica"
providers = { aws = aws.eu_west }
}
🔄FOR EXPRESSIONS & LOOPS
For Expressions
# List comprehension
[for s in var.list : upper(s)]
[for s in var.list : upper(s) if s != "skip"] # with filter
# Map comprehension
{for k, v in var.map : k => upper(v)}
{for i, v in var.list : i => v} # list to indexed map
# Invert a map (values become keys)
{for k, v in var.map : v => k}
# Group values into list by key
{for item in var.items : item.type => item... } # => {type: [item,...]}
# Use for expression in resource
resource "aws_iam_user" "users" {
for_each = toset(var.usernames)
name = each.value
}
# Output all created user ARNs
output "user_arns" {
value = {for k, v in aws_iam_user.users : k => v.arn}
}
Common Loop Patterns
| Pattern | Use Case |
|---|---|
| count = length(var.list) | Simple counted loop over list |
| for_each = var.map | Loop with named keys from map |
| for_each = toset(var.list) | Loop over list (deduped), key=value |
| for_each = { for item in var.list : item.name => item } | List of objects to map |
| [for x in aws_instance.web : x.id] | Collect attribute from all instances |
| aws_instance.web[*].id | Splat: shorthand for list of attribute |
| aws_instance.web["key"].id | For-each: access by key |
| aws_instance.web[0].id | Count: access by index |
🏗️COMMON PATTERNS & BEST PRACTICES
Project Structure
project/
environments/
dev/
main.tf # Module calls
variables.tf # Input variables
outputs.tf # Output values
terraform.tfvars # Dev-specific values
backend.tf # Dev backend config
staging/
prod/
modules/
vpc/
main.tf variables.tf outputs.tf README.md
eks/
rds/
.terraform.lock.hcl # Dependency lock file (commit this!)
.terraformignore # Exclude from TF Cloud uploads**
Conditional Resource Creation
# Create resource only if var is true (count approach)
resource "aws_eip" "web" {
count = var.create_eip ? 1 : 0
instance = aws_instance.web.id
}
# Create resource only if string is not empty
resource "aws_route53_record" "dns" {
count = var.domain_name != "" ? 1 : 0
}
# Reference conditionally-created resource safely
output "eip" {
value = length(aws_eip.web) > 0 ? aws_eip.web[0].public_ip : null
}
Moved Block (Refactoring Without Destroy)
# Rename a resource without destroy/recreate
moved {
from = aws_instance.old_name
to = aws_instance.new_name
}
# Move resource into a module
moved {
from = aws_instance.web
to = module.webserver.aws_instance.web
}
# Move between for_each keys
moved {
from = aws_s3_bucket.buckets["old-key"]
to = aws_s3_bucket.buckets["new-key"]
}
Import Block (v1.5+) 🆕
# Declarative import — put in main.tf, run terraform plan
import {
to = aws_instance.web
id = "i-0abc12345def67890" # AWS resource ID
}
# Generate config for imported resource
terraform plan -generate-config-out=generated.tf
# Legacy CLI import (still valid)
terraform import aws_instance.web i-0abc12345def67890
Check Blocks (v1.5+) 🆕
check "website_is_up" {
data "http" "health" {
url = "https://${aws_lb.web.dns_name}/health"
}
assert {
condition = data.http.health.status_code == 200
error_message = "Website health check failed: ${data.http.health.status_code}"
}
}
🌐ENVIRONMENT VARIABLES
Terraform CLI Environment Variables
| Variable | ℹ️ Description |
|---|---|
TF_VAR_<name>=value |
Set input variable from environment |
TF_CLI_ARGS=-auto-approve |
Add CLI flags globally (or per-command: TF_CLI_ARGS_apply) |
TF_DATA_DIR=/tmp/tfdata |
Override .terraform working directory location |
TF_WORKSPACE=staging |
Select workspace without terraform workspace select |
TF_LOG=DEBUG |
Log level: TRACE, DEBUG, INFO, WARN, ERROR, OFF |
TF_LOG_PATH=terraform.log |
Write logs to file instead of stderr |
TF_INPUT=false |
Disable interactive prompts (for CI/CD) |
TF_IN_AUTOMATION=true |
Adjust output for automated environments |
TF_REGISTRY_HOST=... |
Override default registry host |
TF_TOKEN_app_terraform_io=... |
Auth token for HCP Terraform / Terraform Cloud |
TF_PLUGIN_CACHE_DIR=~/.tf-cache |
Cache downloaded provider plugins |
AWS Provider Auth (Environment)
| Variable | ℹ️ Description |
|---|---|
AWS_ACCESS_KEY_ID |
AWS access key (avoid in prod — prefer roles) |
AWS_SECRET_ACCESS_KEY |
AWS secret key |
AWS_SESSION_TOKEN |
Temporary session token (for assumed roles) |
AWS_DEFAULT_REGION |
Default region (overridden by provider block) |
AWS_PROFILE |
Use named profile from ~/.aws/credentials |
AWS_ROLE_ARN |
IAM role ARN for aws-sdk to assume |
🧪 TESTING & DEBUGGING
Built-in Testing (v1.6+) 🆕
# File: tests/main.tftest.hcl
run "creates_vpc" {
command = plan # or "apply"
variables {
environment = "test"
}
assert {
condition = aws_vpc.main.cidr_block == "10.0.0.0/16"
error_message = "VPC CIDR does not match expected value"
}
}
terraform test # Run all .tftest.hcl files
terraform test -filter=tests/main.tftest.hcl
Console & Debugging
terraform console # Interactive expression evaluator
> var.environment # Inspect variable values
> aws_instance.web.public_ip # Inspect resource attributes
> [for s in ["a","b"] : upper(s)] # Test expressions
> jsondecode(file("data.json")) # Test functions
# Enable debug logging
TF_LOG=DEBUG terraform plan 2>&1 | tee debug.log
# Validate without network calls
terraform validate
# Check formatting
terraform fmt -check -diff # Show what would change
terraform plan -json | jq '.resource_changes[] | select(.change.actions[] == "delete")' to inspect planned deletions in CI/CD pipelines.⚙️CI/CD PATTERNS
🐙 GitHub Actions Workflow
name: Terraform
on:
push: { branches: [main] }
pull_request: {}
jobs:
terraform:
runs-on: ubuntu-latest
permissions:
id-token: write # For OIDC auth
contents: read
pull-requests: write # To post plan comments
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/GitHubActions
aws-region: us-east-1
- uses: hashicorp/setup-terraform@v3
with: { terraform_version: "1.9.0" }
- run: terraform init
- run: terraform validate
- run: terraform plan -no-color -out=tfplan
if: github.event_name == "pull_request"
- run: terraform apply -auto-approve
if: github.ref == "refs/heads/main"
Best Practices for CI/CD
Always run
terraform validateandterraform fmt -checkas first stepsSave plan to file (
terraform plan -out=tfplan) and apply the saved plan, not re-planUse OIDC-based authentication (no long-lived access keys stored in secrets)
Enable
TF_IN_AUTOMATION=trueto suppress interactive prompts and adjust outputStore state remotely with locking (S3+DynamoDB, Azure Blob, GCS, HCP Terraform)
Pin provider versions in
.terraform.lock.hcland commit it to version controlUse separate workspaces or directories per environment (not branches)
Implement PR-based workflow: plan on PR, apply on merge to main
Use Atlantis or HCP Terraform for PR-based GitOps workflows
Scan IaC with Checkov, tfsec, or Snyk before applying
⚡QUICK REFERENCE CARD
Path & Special Variables
| Expression | Value |
|---|---|
path.module |
Filesystem path to current module directory |
path.root |
Filesystem path to root module directory |
path.cwd |
Current working directory of Terraform process |
terraform.workspace |
Name of current workspace |
Lifecycle Quick Reference
| Lifecycle Argument | When to Use |
|---|---|
create_before_destroy = true |
For zero-downtime replacements (LBs, certs) |
prevent_destroy = true |
Block terraform destroy for critical resources (DBs) |
ignore_changes = [tags, ami] |
Ignore drift in listed attributes |
replace_triggered_by = [null_resource.trigger] |
Force replace when trigger changes |
precondition { condition = ... } |
Validate inputs before resource operation |
postcondition { condition = ... } |
Validate outputs after resource operation |
Useful One-Liners
# Get all resource addresses in state
terraform state list | grep aws_
# Delete all resources of a type
terraform state list | grep aws_instance | xargs -I{} terraform state rm {}
# Plan with JSON output and count changes by action
terraform plan -json -out=p.tfplan | jq -r .type | sort | uniq -c
# Show all outputs as JSON
terraform output -json | jq .
# Refresh a single resource
terraform apply -target=aws_instance.web -refresh-only
# Upgrade all providers
terraform init -upgrade && terraform providers lock \
-platform=linux_amd64 -platform=darwin_arm64
# Generate dependency graph and visualize
terraform graph | dot -Tsvg > graph.svg
.terraform.lock.hcl in version control to ensure reproducible builds across team members and CI environments. Run terraform providers lock with -platform flags to support all target platforms.The Takeaway
Terraform is an incredibly powerful tool, but it requires discipline. Keep this cheatsheet handy, always double-check your terraform plan output before applying, and never commit your state file to a public repository!
If you want to see how to put these commands into action, check out my tutorial on Provisioning an AWS EC2 Instance with Terraform.
☕ Did this reference guide help you out? Buy me a coffee to help keep Atul Codes running!






