Chapter 38: Terraform CLI Commands fmt validate plan apply state and more
38.7 refresh-only and graph
Story. An Infosys client support engineer changed a tag in the console during an incident, and the team wanted state to record reality without changing AWS. apply -refresh-only updates state from the real resources and makes no infrastructure changes. terraform graph prints the dependency graph so you can see why things build in a certain order.
terraform plan -refresh-only
terraform apply -refresh-only
terraform graph | dot -Tsvg > graph.svg
What each line does
terraform plan -refresh-only— Shows what changed in AWS since the last apply, without proposing fixes.terraform apply -refresh-only— Writes those real values into state; AWS is not changed.terraform graph | dot -Tsvg > graph.svg— Turns the dependency graph into a picture with Graphviz.
When it is green.
Note: Objects have changed outside of Terraform
# aws_instance.web has changed
~ tags = { + "Incident" = "INC-1042" }
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
When it is red.
dot: command not found
Fix. Install Graphviz with sudo apt install graphviz or sudo dnf install graphviz. Remember: after refresh-only, the next normal plan will propose removing the console tag unless you also add it to code.
What: every term in plain English
- refresh — Terraform reads real AWS values into state.
- -refresh-only — A plan or apply that only updates state from AWS. It never changes AWS.
- drift — AWS changed outside Terraform, for example a console click.
- terraform graph — Prints the dependency graph in DOT format.
- dot — A Graphviz tool that turns DOT into a picture.
Example 1: See drift with plan -refresh-only
Someone added a tag in the console at night. Find out what changed.
- Add a tag Owner=ravi to the instance in the console.
- Run plan -refresh-only.
terraform plan -refresh-only
Output
Note: Objects have changed outside of Terraform
# aws_instance.web has changed
~ resource "aws_instance" "web" {
~ tags = {
+ "Owner" = "ravi"
# (1 unchanged element hidden)
}
}
This is a refresh-only plan, so Terraform will not take any actions to undo these.
What it shows: It only reports. Nothing is changed in AWS or state.
Example 2: Accept the drift into state, then decide
First record reality, then fix the code or revert.
terraform apply -refresh-only -auto-approve
terraform plan
Output
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
# aws_instance.web will be updated in-place
~ tags = {
- "Owner" = "ravi" -> null
}
Plan: 0 to add, 1 to change, 0 to destroy.
What it shows: Now a normal plan wants to remove the tag. Either add Owner to the code, or apply to revert.
Example 3: Draw the dependency graph
A new teammate asks why the instance waits for the security group. Show a picture.
sudo apt-get install -y graphviz
terraform graph | dot -Tsvg > graph.svg
terraform graph | grep -- '->' | head -3
Output
"[root] aws_instance.web (expand)" -> "[root] aws_security_group.web (expand)"
"[root] aws_instance.web (expand)" -> "[root] data.aws_ami.al2023 (expand)"
"[root] aws_security_group.web (expand)" -> "[root] data.aws_vpc.default (expand)"
What it shows: Each arrow means depends on. Open graph.svg in a browser to see the boxes.
Example 4: Nightly drift job
Catch console changes the next morning, not during an outage.
terraform plan -refresh-only -detailed-exitcode -no-color > drift.txt
if [ $? -eq 2 ]; then echo "DRIFT in prod"; grep 'has changed' drift.txt; fi
Output
DRIFT in prod
# aws_instance.web has changed
What it shows: Run it from Jenkins or GitHub Actions on a schedule and send the result to Slack.
Example 5: Graph only one change, from a saved plan
A full graph is crowded. A plan graph shows only what will change.
terraform plan -out=tfplan
terraform graph -type=plan tfplan | dot -Tpng > plan.png
terraform graph -type=plan tfplan | grep -c -- '->'
Output
Plan: 2 to add, 0 to change, 0 to destroy.
6
What it shows: Open plan.png. Fewer boxes, easier to explain to a reviewer.
Command cheat sheet
| Command | What it does | When to use it |
|---|---|---|
terraform plan -refresh-only |
Shows drift only | Morning checks |
terraform apply -refresh-only |
Saves real values into state | Accept drift |
terraform apply |
Reverts drift to code | Undo console clicks |
terraform plan -refresh=false |
Skips the AWS read | Very large stacks, carefully |
terraform graph |
Dependencies as DOT | Explaining order |
dot -Tsvg |
DOT to picture | After graph |
-detailed-exitcode |
Exit 2 on drift | Scheduled jobs |
Verify: did it work?
-
Drift is gone
terraform plan -refresh-only -detailed-exitcode; echo $?Output
No changes. Your infrastructure still matches the configuration. 0 -
The picture was made
ls -l graph.svgOutput
-rw-r--r-- 1 ravi ravi 18342 Oct 25 11:05 graph.svg
When it is red 1
Error: Unsupported argument: refresh-only cannot be combined with -replace
Cause. refresh-only never changes resources, so -replace makes no sense with it.
Fix, step by step
- Run them as two separate commands.
When it is red 2
bash: dot: command not found
Cause. Graphviz is not installed.
Fix, step by step
- Install graphviz with your package manager.
- Run the graph command again.
When it is red 3
Warning: The terraform refresh command is deprecated
Cause. Old command. It updates state with no review.
Fix, step by step
- Use terraform apply -refresh-only, which shows changes first.
When it is red 4
Note: Objects have changed outside of Terraform
# aws_autoscaling_group.web has changed
~ desired_capacity = 2 -> 4
Cause. Not a real problem. Auto Scaling changes desired_capacity on its own, so it shows as drift every day.
Fix, step by step
- Add lifecycle { ignore_changes = [desired_capacity] } to the group.
- Run plan -refresh-only again and the noise is gone.
Cleanup
terraform destroy -auto-approve
rm -f graph.svg drift.txt
Practice tasks
Easy Make drift
Why: See it yourself.
What to do: Change a tag in the console and run plan -refresh-only.
- Add a tag.
- Plan refresh-only.
Check: You see has changed.
Medium Revert drift
Why: Code is the source of truth.
What to do: Run a normal apply to remove the console tag.
- Apply.
- Check the console.
Check: The tag is gone.
Medium Picture a VPC lab
Why: Graphs teach order.
What to do: Draw the graph for a VPC lab with subnets.
- terraform graph | dot -Tpng > vpc.png
Check: You can name the first resource created.
Hard Schedule a drift check
Why: Automation beats memory.
What to do: Add the nightly job to a CI schedule.
- Use cron in GitHub Actions or Jenkins.
Check: The job turns red on drift.
Easy Say it in two lines
Why: If you can say it simply, you understand it.
What to do: Write two short lines about this lesson for a fresher.
- Write the problem it solves.
- Write the one command or file you use.
- Read it aloud.
Check: A fresher understands it without a question.
Easy Start a safe lab folder
Why: Practice needs a throwaway place.
What to do: Make a new folder with versions.tf and a random_pet resource, so no AWS cost.
- mkdir tf-lab and cd into it.
- Add the hashicorp/random provider and one random_pet.
- Run terraform init and terraform apply.
Check: Apply complete! with 1 added.
Medium Break it on purpose
Why: You must know the red output by face.
What to do: Cause one error from the red part of this lesson and fix it.
- Follow one red section.
- Copy the exact message to your notes.
- Write the fix in one line.
Check: You fixed it without searching the web.
Medium Run it twice
Why: A second run must be quiet.
What to do: Run the same plan two times with no edits.
- Run terraform plan.
- Run it again.
- Explain any change it still shows.
Check: The second plan says No changes, or you can explain each line.
Medium Write a runbook page
Why: At 2 a.m. nobody remembers flags.
What to do: Write a one-page runbook for this lesson.
- Title, when to use, exact commands, how to check, how to undo.
Check: A teammate can follow it alone.
Medium Run it from a pipeline
Why: Real teams run Terraform from CI, not from laptops.
What to do: Run the main command of this lesson inside a GitHub Actions or Jenkins job.
- Add a job with hashicorp/setup-terraform or a Terraform Docker image.
- Give it AWS access through an IAM role, never a key in the file.
- Run init, then the command from this lesson.
Check: The job log shows the same output you saw on your laptop.
Medium Check the cost
Why: Labs left running cost real money.
What to do: Find what this lesson would cost per day if you forgot to destroy it.
- Open the AWS Pricing Calculator.
- Add each resource from your plan.
- Write the daily cost in your notes.
Check: You know the daily cost and you ran terraform destroy.
Hard Draw the flow top to bottom
Why: A drawing shows the order of work.
What to do: Draw this lesson as boxes from top to bottom.
- Start at your code, go through plan and state, end at AWS.
- Mark where each command reads or writes.
Check: Each box matches a real step you ran.
Hard Explain it like an interview
Why: Interviewers want real stories.
What to do: Record a 60 second answer about this lesson.
- Say the problem, the command, and one error you hit.
Check: Your answer has a real command and a real error message.
Homework: try at home
Try at home
Create one EC2 lab, change its tags in the console, and practise both paths: accept the drift into code, and revert it with apply.
Recap
- Every plan refreshes state first.
- plan -refresh-only shows drift and changes nothing.
- apply -refresh-only accepts AWS values into state.
- A normal apply reverts drift to match code.
- terraform graph plus dot draws dependencies.
- Use ignore_changes for values AWS changes on purpose, such as desired_capacity.
FAQ
Does refresh-only change AWS?
Never. Only state.
Should I always accept drift?
No. Decide: add it to code, or revert it.
Can I skip refresh?
plan -refresh=false is faster but can be wrong. Use rarely.
Is graph useful for big stacks?
It gets crowded. Graph one module folder at a time.
What causes drift most?
Console clicks, scripts and auto-scaling.
Does plan -refresh-only need AWS access?
Yes. It reads AWS.
Why does Auto Scaling always show drift?
AWS changes desired_capacity when load changes. Use ignore_changes for that one argument.
How often should a drift job run?
Once a night for most teams. Every hour for strict prod accounts.
Interview questions
-
How do you detect drift?
plan -refresh-only with -detailed-exitcode on a schedule.
-
Why was terraform refresh deprecated?
It changed state without review. apply -refresh-only shows a plan first.
-
How do you fix drift?
Either update code to match, or apply to restore the code values.
-
What does terraform graph show?
Resource dependencies in DOT format.
-
How do you stop drift?
Limit console write access and change only through pipelines.
-
Your drift job fires every night for the same resource. What do you do?
Find who or what changes it. If it is expected, such as Auto Scaling, add ignore_changes for that argument. If it is a person, remove their console write access and route changes through the pipeline.
Quick quiz
Q1. Does -refresh-only change AWS?
No.
Q2. Which command accepts drift into state?
terraform apply -refresh-only.
Q3. What tool draws DOT files?
Graphviz dot.
Q4. What exit code means drift with -detailed-exitcode?
2.
Q5. Which old command is deprecated?
terraform refresh.
Lab
- Run terraform fmt -check -diff -recursive on your Chapter 33 code.
- Run terraform plan -out=tfplan and terraform show -json tfplan.
- Apply the plan, then run terraform state list and terraform output.
- Run terraform plan -replace on one instance and read the -/+ line.
- Change a tag in the console, then run terraform plan -refresh-only.
- Run terraform destroy to avoid cost.
Practice task
For each of state mv, state rm and taint, name the block or flag that replaces it in reviewed code.
Ravindra Bagale's Tip
Back up state with terraform state pull before any state mv or state rm.
In short
- fmt -check and validate are the cheapest gate.
- Save plans with -out and apply the file.
- -target is for emergencies only.
- state mv and rm change state at once; prefer moved and removed.
- Use -replace, not taint; use refresh-only to record outside changes.
Samajla ka?
Revision card
Interview card