Terraform/OpenTofu Fundamentals
Commands That Only Fix State, and Destroy Order
In one line
A plan does two comparisons together: code versus state, and state versus real objects. The refresh family of commands peels off only the latter comparison, and destruction walks that graph backward.
Why it had to be split
A single apply actually contains three jobs. Read the real objects to bring the state up to date, compare that state with the code to decide what to do, and execute what was decided. For the most part this bundle is convenient. But there are cases where being bundled is a problem.
First, the case where only the state is wrong. Suppose someone deleted a resource by hand in the console or renamed it. The code is fine, and the real object is also fine (if you accept that change), but only the state is speaking of the past. If you just apply in this case, the tool creates what is missing anew or overwrites what is there. All that is needed is "bring the state in line with reality."
Second, conversely, there are times when the cost of reading real objects is large. In a repository with thousands of resources, the refresh alone takes minutes. When in a hurry, you want to skip that stage and produce a plan.
So options arose that handle the two comparisons separately.
How it works
A refresh-only plan does not look at the code at all. It compares only the state and the real objects, and shows how it would fix the state.
Note: Objects have changed outside of OpenTofu
# local_file.conf has been deleted
- resource "local_file" "conf" {
If you apply here, only the state changes. It does not touch the real object at all, and the previous state is left in the backup file. The serial number of the state goes up and the vanished entry is removed from the state.
A plan that does not read real objects is the exact opposite. It trusts the values written in the state as fact, so it does not know what changed outside.
No changes. Your infrastructure matches the configuration.
If you just run a plan at the same moment, a completely different thing is said. It recreates what vanished, and even replaces what referenced its value. The same code, the same state, the same moment, but a different conclusion — the only difference is whether the real object was read. So you must not get approval with a plan made with this option for the sake of speed.
Now to the destruction side. When creating, the tool follows the dependency graph and creates from the front. The network must exist for the database to exist, and that in turn for the application to exist. When deleting, it must be exactly the reverse order. If you delete the database first while the application still points at it, what remains points at something that does not exist.
A destroy plan can also be saved to a file, like a normal plan. The more irreversible the operation, the safer it is to save it, review it, and then apply only that file. A saved plan file can also be viewed in a machine-readable format, so you can extract a list of what will be deleted and attach it to the approval.
The option that narrows the target is different. Using it means that a person takes responsibility for the consistency of the whole graph, which the tool normally guards, and so a warning is attached.
Warning: Resource targeting is in effect
Warning: Applied changes may be incomplete
Finally, even after everything is deleted, the state file itself does not disappear. The list of entries and the outputs become empty and the serial goes up, but the lineage number of that state stays the same. If you apply again in the same place, it continues as an extension of the same record. Deleting the state file is an entirely different thing from deleting resources — if you delete it, the real objects remain and only the record vanishes, and the tool comes to treat them as unknown.
What you see in the field
The most expensive accident is applying as it is when only the state has diverged. The tool recreates a resource someone fixed by hand in production, and traffic is cut in the meantime. The habit of running a refresh-only plan first to tell "is it a state problem rather than a code problem?" prevents this accident.
The second is the trap of the fast plan. If you put a plan with refresh turned off into CI in a large repository, review gets faster, but changes made outside show up only after approval. If you need speed, the compromise is to run one more plan that reads real objects right before applying.
The third is when a target-narrowed apply becomes a habit. If an option used once in a hurry becomes the team's default procedure, you end up with a repository whose whole graph has never been consistent. After using it, always run one un-narrowed plan and check that it is clean.
The fourth is the practice of finishing destruction with a single command. Deletion cannot be undone, yet if you pass only one interactive confirmation and finish, no one can later reconstruct what was deleted. If you save the plan to a file and extract a list in a machine-readable format, the grounds for approval remain and, when an accident happens, what needs to be recovered comes out at once. If you apply a plan saved as a file, there is also no room for what was reviewed and the actual work to diverge.
What you will do in the next lab
You go through eight steps. You build a baseline, fix a file without going through the tool and then make a refresh-only plan, and save side by side and compare a plan that did not read real objects and a plan that did. Then you apply only the refresh and confirm how the state's serial number and entry count change and whether the file fixed by hand stays as it is. In the last four steps, you build a three-step chain and save a destroy plan to a file, apply that file and confirm the deletion order from a log, read the warning of a target-narrowed destruction, and finally compare with the backup what remains in the state after everything is deleted.