CLI #
Terraform is operated entirely through the command line. There’s no official GUI, no local dashboard — all interaction with Terraform happens through CLI commands. This isn’t a weakness — it’s a strength. The CLI allows every Terraform operation to be automated, scripted, and run in CI/CD pipelines. Understanding Terraform’s CLI commands — not just how to run them, but also how to read their output and when to use specific flags — is a foundational skill you’ll use every day.
The Core Workflow #
These four commands form Terraform’s main working cycle. Almost everything you do in Terraform revolves around them.
flowchart LR
INIT["terraform init\nDownload providers\nSet up backend"] --> PLAN["terraform plan\nPreview changes\nReview the diff"]
PLAN --> APPLY["terraform apply\nExecute changes\nCreate/Update/Delete"]
APPLY --> DESTROY["terraform destroy\nRemove all resources\n(teardown)"]
PLAN -.->|"Something wrong?"| PLAN
APPLY -.->|"New changes"| PLAN
style INIT fill:#e3f2fd,stroke:#1565c0
style PLAN fill:#fff3e0,stroke:#e65100
style APPLY fill:#e8f5e9,stroke:#2e7d32
style DESTROY fill:#ffebee,stroke:#c62828| Command | Function | When to use | Frequency |
|---|---|---|---|
terraform init | Download providers, set up backend | First time / after changing providers | Once per project |
terraform plan | Preview changes | Before every apply | Very often |
terraform apply | Execute changes | After reviewing the plan | Often |
terraform destroy | Remove all resources | Environment teardown | Rarely |
terraform init
#
terraform init is the first command you must run in a new Terraform directory, and every time there are changes to the provider or backend configuration. It does three things: downloads provider plugins, initializes the backend for state, and downloads any required modules.
# Basic initialization
terraform init
# Expected output:
# Initializing the backend...
# Initializing provider plugins...
# - Finding hashicorp/aws versions matching "~> 5.0"...
# - Installing hashicorp/aws v5.31.0...
# - Finding cloudflare/cloudflare versions matching "~> 4.0"...
# - Installing cloudflare/cloudflare v4.22.0...
# Terraform has been successfully initialized!
Important Flags #
# Update providers to the latest version satisfying the constraint
terraform init -upgrade
# Use this when you want the newest provider versions
# Initialize without backend setup (for testing or migration)
terraform init -backend=false
# Force a backend reconfigure (when changing backends or moving state)
terraform init -reconfigure
# Migrate state from an old backend to a new one
terraform init -migrate-state
# Only download modules, skip backend and provider setup
terraform init -get-plugins=false
After init, Terraform creates a .terraform/ directory containing the downloaded provider binaries. This directory must not be committed to Git — it holds large binaries that differ per OS. However, the .terraform.lock.hcl file must be committed — it guarantees everyone uses the same provider versions.
# After init, the directory will contain:
# .
# ├── .terraform/
# │ ├── providers/ # Provider binaries (DON'T commit)
# │ │ └── registry.terraform.io/
# │ └── modules/ # Downloaded modules
# ├── .terraform.lock.hcl # Lock file (MUST commit)
# ├── main.tf
# └── variables.tf
terraform plan
#
terraform plan is the command you’ll use most often. It reads the configuration, compares it with the state, and shows what would change without executing anything. It’s Terraform’s main safety net.
# Basic plan
terraform plan
# Save the plan to a file (for deterministic applies)
terraform plan -out=tfplan
terraform apply tfplan # Apply exactly the saved plan
# Override variables during plan
terraform plan -var="environment=production"
terraform plan -var-file="prod.tfvars"
# Plan only specific resources (useful for debugging)
terraform plan -target=aws_instance.web
# Skip state refresh (faster, but less accurate)
terraform plan -refresh=false
Reading Plan Output #
Every symbol in the plan output has a specific meaning. Being able to read plan output quickly is a crucial skill.
flowchart TD
A["Plan Symbols"] --> B["+ create\nA new resource will be created"]
A --> C["- destroy\nThe resource will be deleted"]
A --> D["~ update in-place\nThe resource is modified without replacement"]
A --> E["-/+ replace\nThe resource is deleted then recreated"]
A --> F["<= read\nThe data source will be read"]
E --> G["⚠️ BE CAREFUL\nCan cause downtime\nfor stateful resources"]
style B fill:#e8f5e9,stroke:#2e7d32
style C fill:#ffebee,stroke:#c62828
style D fill:#fff3e0,stroke:#e65100
style E fill:#fff3e0,stroke:#e65100
style F fill:#e3f2fd,stroke:#1565c0
style G fill:#ffebee,stroke:#c62828# Example plan output with various symbols:
# + create (a new resource will be created)
+ resource "aws_instance" "web" {
+ ami = "ami-0abcdef1234567890"
+ instance_type = "t3.micro"
+ id = (known after apply)
}
# - destroy (the resource will be deleted)
- resource "aws_instance" "old" {
- id = "i-0abcdef1234567890"
}
# ~ update in-place (the resource is modified without replacement)
~ resource "aws_instance" "web" {
~ instance_type = "t3.micro" -> "t3.small"
id = "i-0abcdef1234567890"
}
# -/+ destroy and recreate (a change that requires replacement)
-/+ resource "aws_instance" "web" {
~ ami = "ami-old" -> "ami-new" # forces replacement
- id = "i-0abcdef1234567890"
+ id = (known after apply)
}
Notice the -/+ symbol — it means the resource will be deleted first, then recreated. If that resource is a production server, that means downtime. Always scrutinize the plan output carefully before typing yes.
terraform apply
#
terraform apply executes the planned changes. By default, it re-runs the plan and asks for confirmation before making changes.
# Apply with interactive confirmation (default)
terraform apply
# Terraform will perform the following actions:
# ... (plan output) ...
# Do you want to perform these actions?
# Terraform will perform the actions described above.
# Only 'yes' will be accepted to approve.
# Enter a value: yes
# Apply without confirmation (for CI/CD)
terraform apply -auto-approve
# Apply from a saved plan (safest for production)
terraform plan -out=tfplan
terraform apply tfplan # Exactly the saved plan, no confirmation prompt
Important Flags #
# Override variables during apply
terraform apply -var="instance_type=t3.small"
terraform apply -var-file="prod.tfvars"
# Apply only specific resources (for targeted fixes)
terraform apply -target=aws_instance.web
# Limit the number of concurrent operations (default: 10)
terraform apply -parallelism=5
# Useful when the provider API has rate limits
# Refresh state before applying
terraform apply -refresh=true # Default
terraform apply -refresh=false # Skip refresh (faster)
Use-auto-approveonly in CI/CD pipelines that already have a prior review process (like PR approval before merging to main). Running-auto-approvedirectly in a production terminal is an unnecessary risk — one typo in the configuration could delete critical resources.
-target — Use with Caution
#
-target lets you apply only specific resources. It’s useful for debugging or targeted fixes, but not for routine use.
# Useful for targeted fixes
terraform apply -target=aws_instance.web
# ANTI-PATTERN: Using -target routinely
# - Makes state out of sync with the configuration
# - Untargeted resources can become inconsistent
# - Better to use -target only for debugging
State Inspection Commands #
These commands help you understand the current state and what Terraform is managing.
# List all resources in state
terraform state list
# aws_instance.web
# aws_security_group.web
# aws_subnet.public[0]
# aws_subnet.public[1]
# aws_vpc.main
# View details of one resource
terraform state show aws_instance.web
# resource "aws_instance" "web" {
# ami = "ami-0abcdef1234567890"
# instance_type = "t3.micro"
# public_ip = "54.123.45.67"
# ...
# }
# Refresh state from the actual cloud condition
terraform refresh
# Re-reads all resources from the provider APIs
# Useful if there were manual changes outside Terraform
# Show output values
terraform output
terraform output -raw instance_public_ip # Specific output, no quotes
terraform output -json # JSON format
| Command | Function | When to use |
|---|---|---|
state list | List resources | Audit, check what’s managed |
state show <addr> | Resource attribute details | Debugging, checking values |
state rm <addr> | Remove from state | Unmanage a resource without destroying it |
state mv <src> <dst> | Rename/move in state | Refactoring |
state pull | Download state | Backup, debugging |
refresh | Sync state with the cloud | After manual changes |
Formatting and Validation Commands #
These commands keep configuration quality high and are very important to integrate into your daily workflow.
# Format configuration files consistently
terraform fmt # Format all .tf files in the current directory
terraform fmt -recursive # Include subdirectories
terraform fmt -check # Check without changing (exit code 1 if formatting is needed)
terraform fmt -diff # Show the formatting diff
# Validate configuration syntax
terraform validate
# Success! The configuration is valid.
# Validation doesn't connect to providers — it only checks HCL syntax
# RECOMMENDATION: Run before every commit
terraform fmt -check && terraform validate
# In CI/CD pipelines:
terraform fmt -check -recursive # Fail if formatting is inconsistent
terraform validate # Fail if there are syntax errors
Debugging Commands #
When things aren’t working as expected, these commands are very helpful.
# Enable detailed logging
TF_LOG=TRACE terraform apply # Most verbose
TF_LOG=DEBUG terraform apply # Very detailed
TF_LOG=INFO terraform apply # General information
TF_LOG=WARN terraform apply # Only warnings
TF_LOG=ERROR terraform apply # Only errors
# Save logs to a file (useful for very long logs)
TF_LOG=DEBUG TF_LOG_PATH=terraform.log terraform apply
# Logs go to terraform.log, the terminal stays clean
# View the dependency graph (DOT format for Graphviz)
terraform graph
terraform graph | dot -Tpng > graph.png # Visualize as an image
terraform graph -type=plan # Graph of the plan, not the config
Complete Reference Table #
Here’s a summary of all the commands covered, categorized by function.
| Category | Command | Function |
|---|---|---|
| Init & Setup | init | Download providers, set up backend |
init -upgrade | Update providers to the latest version | |
init -reconfigure | Reconfigure the backend | |
| Plan & Apply | plan | Preview changes |
plan -out=tfplan | Save the plan to a file | |
apply | Execute changes | |
apply tfplan | Apply from a saved plan | |
apply -auto-approve | Apply without confirmation | |
destroy | Remove all resources | |
| State | state list | View resources in state |
state show | Resource details | |
state rm | Remove from state | |
state mv | Rename/move in state | |
state pull/push | Download/upload state | |
refresh | Sync state with the cloud | |
| Quality | fmt | Format the configuration |
validate | Validate syntax | |
graph | Dependency graph | |
| Debug | TF_LOG=DEBUG | Enable verbose logging |
providers | View the providers in use | |
version | Check the Terraform version |
Summary #
- Core workflow:
init→plan→apply— run them in this order every time you work with new or changed configurations.- Always read the
planoutput beforeapply— understand the+(create),-(destroy),~(update), and-/+(replace) symbols.-/+means replace — the resource is deleted and recreated, which can cause downtime for stateful resources like databases.- Save plans with
-out=tfplanfor production — make sure what you apply is exactly what was reviewed.terraform fmtandterraform validateare good habits before committing — integrate them into the CI pipeline.-targetis for debugging, not routine use — overuse can make state inconsistent.TF_LOG=DEBUGis your best friend when something isn’t working as expected.