Why you need plan
terraform plan is a fitting. You write HCL: "I want one bucket with tags." Terraform looks at what already exists (recorded in the state file) and shows the difference: "one new bucket needs to be created, nothing to change, nothing to destroy."
The key point: plan changes nothing. It does not call AWS with a "create" command. It only looks and compares. This is a safe command, and you can run it as often as you like.
What plan shows
Say you wrote this:
resource "aws_s3_bucket" "demo" {bucket = "my-bucket-12345"
tags = {Owner = "student"
}
}
And you ran terraform plan. The output looks roughly like this:
Terraform will perform the following actions:
# aws_s3_bucket.demo will be created
+ resource "aws_s3_bucket" "demo" {+ bucket = "my-bucket-12345"
+ id = (known after apply)
+ tags = {+ "Owner" = "student"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
Line by line:
+ resource ...means this resource will be created. The+sign means add.+ bucket = "my-bucket-12345"is the value Terraform will send to the API.+ id = (known after apply)is an attribute that appears only after creation; you do not know it yet.Plan: 1 to add, 0 to change, 0 to destroyis the summary. This is the most important line.
Signs in the diff (memorize these)
| Sign | What it means |
|---|---|
+ | Create a new resource or attribute |
- | Destroy a resource or attribute |
~ | Change a value in place (no recreation needed) |
-/+ | Destroy and recreate (force replacement) |
The most dangerous one is -/+. It means "this resource has to be recreated from scratch." If it is a database, the data is gone. If it is an EC2 instance, you lose the IP address. Always read the -/+ lines carefully.
Exit code: 0, 1, 2
By default terraform plan always returns exit code 0, even when there are changes. This is convenient for CI: the command does not fail.
But run it with the -detailed-exitcode flag and the logic changes:
0means no changes, state == HCL. "No changes".1means an error (a syntax problem, the provider is not responding, the token is invalid).2means there is a plan and changes are required.
terraform plan -detailed-exitcode
echo $? # 0, 1, or 2
This is essential in scripts: "if there is drift, send me an alert."
Saving the plan to a file
You can save a plan and later apply exactly that plan, with no recomputation:
terraform plan -out=tfplan.bin # saved
terraform apply tfplan.bin # apply this exact plan
Why people do this:
- In CI: a reviewer sees the plan in a PR, merges it, and apply runs exactly what was shown. Between the review and the apply no code can sneak in.
tfplan.binmay contain secrets (if they are in the state). Do not commit it to git.
Pitfalls
-
"No changes" is the invariant you want to reach after apply. If a second
planright afterapplystill shows changes, you have drift (something changed outside Terraform), or your HCL does not match reality. That is a bad sign. -
plan reads the state, not real AWS. If someone deleted the bucket by hand in the console, plan will not notice until you run
terraform refreshorterraform plan -refresh=true(the default, though some people turn it off). See tf-state. -
plan can take a while. On large projects (thousands of resources) plan can run for minutes. Terraform queries the cloud about each one. That is normal. If it really hurts, use
-target=resource.namefor a narrow plan. -
plan does not predict everything. Some attributes are computed on the cloud side. plan shows
(known after apply), and that is fine. It is not a "secret"; the value simply appears after creation. -
Use
-targetrarely. It is a "crutch for unexpected situations." Reaching for-targetregularly is a sign that your HCL is structured wrong.