Plan Changes with Terraform Plan
Plan changes with terraform plan — Terraform foundations tutorial, lesson 6. Learn to preview infrastructure changes before applying, with hands-on steps and troubleshooting.
Focus: plan changes with terraform plan
You are about to change your infrastructure — but what if you hit apply and discover you are about to delete a production database? That is exactly the kind of surprise terraform plan is designed to prevent. In this lesson, you will learn how to plan changes with terraform plan, interpret the output, and use it as a safety net before any mutation happens. It is the sixth step in your Terraform foundations track, and it will turn you from someone who blindly applies into an engineer who reviews and approves changes with confidence.
The Problem: Blindly Applying Infrastructure Changes
Imagine you have been working on a Terraform configuration for weeks. You run terraform apply without thinking, and it works — until one day, a teammate removes an resource block, and your apply deletes a critical load balancer in production. No warning, no undo. This is the pain terraform plan solves: it gives you a preview of every change Terraform will make before you commit to it.
Without a plan step, you are navigating infrastructure changes blind. You might think you are updating an AMI, but Terraform might be planning to replace the entire EC2 instance. Or you might think you are adding a security group rule, but you are actually about to destroy the whole security group. The cost of a mistake here is downtime, data loss, or a security incident — all avoidable.
terraform plan is not an optional safeguard; it is a required habit for anyone serious about infrastructure-as-code. It is the difference between shipping changes with confidence and shipping changes with hope.
Core Concept / Mental Model
Think of terraform plan as a dry run or a simulation of your infrastructure changes. It reads your configuration, compares it against the current state (stored in the state file), and computes a detailed list of actions that would bring your infrastructure in line with your code. It does not actually change anything — it only reports.
Here is the mental model:
- Configuration = the desired state (your
.tffiles) - State = the current reality (your
terraform.tfstatefile) - Plan = the diff between the two
A plan output shows three types of actions:
- Create: resource exists in configuration but not in state — Terraform will add it
- Update in-place: resource exists in both, but attributes differ — Terraform will modify it (no resource replacement)
- Replace: resource cannot be updated in-place (e.g., AMI change) — Terraform will destroy the old and create a new one
Additionally, a plan can show Destroy actions when a resource is present in state but absent from configuration.
The plan is not just a list; it is an audit trail. You can save it to a file and review it, share it in a pull request, or feed it into terraform apply to guarantee that what you planned is exactly what gets applied.
How It Works Step by Step
Here is the cause-and-effect sequence of how terraform plan works under the hood:
- Refresh state — Terraform reads the current state file and, by default, refreshes the actual infrastructure to detect any drift (resources changed outside of Terraform). This updates the state to reflect reality.
- Read configuration — Terraform parses all
.tffiles in your working directory, including variables, modules, and providers. - Compute the diff — Terraform evaluates the desired state from the configuration and compares it to the refreshed state. It calculates the minimal set of actions needed to reconcile the two.
- Generate the plan — Terraform creates an execution plan that lists every action with details, including resource addresses and attribute changes.
- Persist the plan (optional) — You can save the plan to a file with
-out=plan.tfplanfor later use. - Present the output — The plan is displayed in a human-readable format, with additions in green (
+), changes in amber (~), destructions in red (-), and replacements as both-and+.
Pro tip: Always use
terraform plan -out=plan.tfplanto save your plan. That way, when you apply, you are applying exactly the reviewed plan — no surprises from a configuration edit between plan and apply.
Now, let’s see this in action in the hands-on walkthrough.
Hands-On Walkthrough
Let’s create a simple setup to experience terraform plan firsthand. We’ll assume you already have Terraform installed and configured with AWS credentials.
Step 1: Create a basic configuration
Create a directory and add a file named main.tf with the following content. We’ll use the AWS provider and create an S3 bucket.
# main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "example" {
bucket = "my-demo-bucket-2025"
tags = {
Name = "my-demo-bucket"
}
}
Step 2: Initialize and apply the initial state
Run these commands in your terminal:
terraform init
terraform apply -auto-approve
You will see output showing the bucket being created. This establishes a baseline state for our plan demonstration.
Step 3: Run terraform plan after making a change
Now, modify the main.tf to add a second bucket and change the tags of the first bucket. Let’s update the file:
# main.tf (updated)
resource "aws_s3_bucket" "example" {
bucket = "my-demo-bucket-2025"
tags = {
Name = "my-demo-bucket"
Environment = "dev"
}
}
resource "aws_s3_bucket" "second" {
bucket = "my-demo-bucket-2025-second"
}
Now run:
terraform plan
You’ll see output like this (abridged):
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
~ update in-place
Terraform will perform the following actions:
# aws_s3_bucket.example will be updated in-place
~ resource "aws_s3_bucket" "example" {
id = "my-demo-bucket-2025"
~ tags = {
+ "Environment" = "dev"
}
+ versioning = []
}
# aws_s3_bucket.second will be created
+ resource "aws_s3_bucket" "second" {
+ bucket = "my-demo-bucket-2025-second"
+ force_destroy = false
+ tags = {}
}
Plan: 1 to add, 1 to change, 0 to destroy.
Take note of the summary line: Plan: 1 to add, 1 to change, 0 to destroy. This is your at-a-glance risk assessment.
Step 4: Save and apply the plan
Save the plan to a file and then apply it exactly as reviewed:
terraform plan -out=plan.tfplan
terraform apply plan.tfplan
The apply will show the same actions and ask for confirmation (unless you use -auto-approve). This ensures you are applying precisely what you planned.
Pro tip: In a CI/CD pipeline, always save the plan artifact and use it during apply. This prevents configuration drift between planning and applying.
Step 5: Practice with a destructive change
To see what destruction looks like, remove the second bucket from main.tf, then run terraform plan. You will see a - symbol and the summary will include 1 to destroy.
Plan: 0 to add, 0 to change, 1 to destroy.
This is exactly the kind of change you want to catch early!
Compare Options / When to Choose What
terraform plan is the default, but there are variations and flags that suit different contexts. Here is a comparison of common commands and their best use cases:
| Command / Flag | Description | Best Use Case |
|---|---|---|
terraform plan |
Standard plan; refreshes state and shows changes. | Daily workflow; pre-apply review. |
terraform plan -out=plan.tfplan |
Saves the plan to a binary file for later apply. | CI/CD pipelines; ensuring apply matches plan. |
terraform plan -refresh=false |
Skips refreshing state; uses cached state. | When you are certain no drift exists; saves time. |
terraform plan -target=resource |
Limits plan to a specific resource. | Testing a single resource changes; focused debugging. |
terraform plan -destroy |
Plans a full destruction of all resources. | Deprovisioning an environment. |
terraform plan -var-file=prod.tfvars |
Uses a specific variable file. | Different environments (dev/staging/prod). |
When to choose what:
- For day-to-day development, the plain
terraform planis enough. - For production changes, always use
-outand store the plan as an artifact. - For a large codebase, use
-targetsparingly — it can lead to partial state changes if misused. - For decommissioning,
-destroyis your friend, but you should review the plan very carefully.
Troubleshooting & Edge Cases
Even with terraform plan, things can go wrong. Here are common issues and how to fix them.
Issue 1: "Error: Failed to load state: terraform state file is locked"
- Cause: Another process (like an apply or plan) is holding the state lock.
- Fix: Release the lock with
terraform force-unlock <lock-id>after verifying no other operation is running. Never force-unlock during a real operation — it can corrupt state.
Issue 2: Plan shows unexpected "replace" instead of "update"
- Cause: Some attributes are immutable, like AWS AMI for EC2 instances. Any change to those forces replacement.
- Fix: Read the plan carefully to understand why. If you need to avoid replacement, consider using
lifecyclemeta-argument withcreate_before_destroy = trueto minimize downtime.
Issue 3: Troubleshooting "Resource already exists" error during apply
- Cause: The resource exists in the cloud but not in the state file (e.g., created manually). Terraform wants to create it again and fails.
- Fix: Use
terraform importto bring the resource into state, then plan to see only the differences. Do not blindly delete the resource.
Issue 4: Secret values exposed in plan output
- Cause: Sensitive values (like database passwords) are shown in plain text in the plan.
- Fix: Mark variables and outputs as
sensitive = truein the configuration, and use-no-coloror-jsonif you need to parse output programmatically.
Edge Case: Plan succeeds but apply fails
This can happen if the cloud API changes between plan and apply, or if a dependency is missing. Always re-run terraform plan right before apply, and use -out so you know exactly what you applied. Also, enable precondition and postcondition checks to validate assumptions.
What You Learned & What's Next
You now know how to plan changes with terraform plan effectively. Core takeaways from this lesson:
terraform planis a dry run that shows creates, updates, and destroys without applying.- Always review the plan summary (e.g., “Plan: 1 to add, 1 to change, 0 to destroy”) before applying.
- Use
-outto save plans for audit and consistent apply. - Use troubleshooting techniques to handle state locks, replacements, and sensitive data.
These skills set you up for the next lesson, where you will dive into state management — how Terraform stores and handles state, which is the backbone of plan accuracy. Mastering state will make your planning even more reliable.
Continue to the next lesson to explore terraform state commands.
Practice recap
Create a Terraform config with two resources (e.g., an S3 bucket and an IAM user). Apply it, then make a change that forces a replacement (e.g., change a region) and run terraform plan. Note how the plan indicates a replacement. Then save the plan to a file and run terraform show to inspect it. This will solidify your understanding of plan output.
Common mistakes
- Skipping
terraform planand going straight to apply — this defeats the entire purpose of a safe IaC workflow. - Ignoring
-outand letting a plan drift: between plan and apply, someone edits the config, and apply uses a different set of changes. - Using
-targettoo broadly, which can lead to partial state updates and unplanned resource modifications. - Forgetting to mark sensitive variables as
sensitive, causing credentials to appear in plan logs. - Assuming a plan with 0 destroys is safe — always read the diff, not just the summary.
Variations
- Use
terraform plan -jsonto get machine-readable output for custom tooling or CI integrations. - Utilize
terraform show plan.tfplanto inspect a saved plan in detail later. - Leverage
terraform plan -destroyto plan a full teardown of your environment.
Real-world use cases
- Reviewing a plan in a pull request to ensure infrastructure changes are safe before merging.
- Using
terraform plan -outin CI pipelines to produce an artifact for audited production applies. - Catching unintended resource deletions before a scheduled maintenance window.
Key takeaways
- Terraform plan is a dry-run simulation that shows exactly what will change before apply.
- Always review the plan summary and diff to catch destructive changes early.
- Save plans with
-outto ensure apply matches the reviewed plan. - Handle state locks, replacements, and sensitive data with proper troubleshooting techniques.
- Use variations like
-jsonand-destroyto adapt planning to your workflow.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.