Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

terraform plan

terraform plan computes the difference between your configuration and the current infrastructure state, then presents a human-readable diff of what Terraform will do on the next apply. It makes zero changes to real infrastructure — it’s purely informational.


What plan Actually Does

When you run terraform plan, Terraform:

  1. Reads configuration — loads all .tf files in the directory
  2. Refreshes state — queries the cloud provider to get the current real state of managed resources
  3. Computes the diff — compares desired config vs. current state
  4. Generates the plan — lists resources to create, update, destroy, or leave unchanged
Terminal window
terraform plan

Reading Plan Output

Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
+ create
~ update in-place
- destroy
-/+ destroy and then create replacement
Terraform will perform the following actions:
# aws_instance.app will be created
+ resource "aws_instance" "app" {
+ ami = "ami-0abcdef1234567890"
+ instance_type = "t3.micro"
+ id = (known after apply)
+ public_ip = (known after apply)
+ tags = {
+ "Environment" = "production"
+ "Name" = "app-server"
}
}
# aws_instance.old will be updated in-place
~ resource "aws_instance" "old" {
~ instance_type = "t3.small" -> "t3.medium" # Change
id = "i-1234567890abcdef0" # Unchanged
}
# aws_security_group.web must be replaced
-/+ resource "aws_security_group" "web" {
~ name = "old-sg" -> "new-sg" # Forces replacement
- id = "sg-abc123" -> (known after apply)
}
Plan: 1 to add, 1 to change, 0 to destroy. (1 to replace)

Output symbols:

SymbolMeaning
+Resource will be created
-Resource will be destroyed
~Resource will be updated in-place
-/+Resource will be destroyed and recreated
<=Data source will be read

Saving a Plan File

The most important production pattern — save the plan and apply exactly that plan:

Terminal window
# Save plan to a binary file
terraform plan -out=tfplan
# Apply the saved plan (no re-plan, no surprises)
terraform apply tfplan

Why this matters: between plan and apply, someone else could apply a change. Applying a saved plan file guarantees that what you reviewed is exactly what gets executed — critical for compliance and change management.


Key Flags

Terminal window
# Override a variable
terraform plan -var="environment=production"
terraform plan -var="instance_count=3" -var="region=us-west-2"
# Use a variable file
terraform plan -var-file="environments/production.tfvars"
# Plan only specific resource(s)
terraform plan -target=aws_instance.app
terraform plan -target=module.networking
# Show a destroy plan
terraform plan -destroy
# Skip state refresh (faster; use only when you know state is accurate)
terraform plan -refresh=false
# JSON output for tooling
terraform plan -json -out=tfplan.json
# Compact output for large plans
terraform plan -compact-warnings
# Non-interactive (CI/CD)
terraform plan -input=false -out=tfplan

Plan Output Sections

A complete plan shows five sections:

1. Changes to Outputs: # Output values that will change
2. Terraform will perform: # Resource-level changes
3. Plan summary: # X to add, Y to change, Z to destroy
4. Note about sensitive: # (if sensitive values are involved)
5. Saved plan path: # (if -out was used)

Understanding “Forces Replacement”

Some attribute changes require destroying and recreating the resource instead of updating in-place. Terraform marks these with # forces replacement:

# aws_instance.app must be replaced
-/+ resource "aws_instance" "app" {
~ ami = "ami-old" -> "ami-new" # forces replacement
}

Common attributes that force replacement on EC2:

For stateless resources, this is usually fine. For databases, a forced replacement is catastrophic — protect with prevent_destroy = true in the lifecycle block.


Plan in CI/CD Pipelines

.github/workflows/terraform.yml
name: Terraform Plan
on:
pull_request:
paths: ['infrastructure/**']
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.8.0"
- name: Init
run: terraform init -input=false
working-directory: infrastructure/
- name: Validate
run: terraform validate
- name: Plan
id: plan
run: terraform plan -input=false -no-color -out=tfplan
working-directory: infrastructure/
- name: Post plan to PR
uses: actions/github-script@v7
with:
script: |
const output = `${{ steps.plan.outputs.stdout }}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan\n\`\`\`\n${output}\n\`\`\``
});

Using Infracost with Plan

Terminal window
# Install infracost, then:
terraform plan -out=tfplan
infracost breakdown --path tfplan
# Output:
# Monthly estimate:
# aws_instance.app: $8.47/mo (t3.micro, us-east-1)
# aws_db_instance.main: $26.28/mo (db.t3.micro, 20GB gp2)
# Total: $34.75/mo

Cost estimation from plan output before applying is a 2025 best practice for catching expensive mistakes early.