This is invented, not a client plan
The module, names and values below are made up to show the shape of a plan review. The configuration is a stand-in built from Terraform's built-in terraform_data resource and the random provider, so that the plan text has the exact layout Terraform 1.16.5 prints; a cloud module would show provider-specific attributes instead. No real account, client or customer is involved and nothing here describes delivered work. The case: a module for a small web stack of four resources, a network, a server, a database setting and a generated administrator password. The first plan runs against an empty test account. A second plan runs after a code edit, against the state left by the first apply.
- Module: web-stack, four resources.
- First plan: an empty synthetic test account, so only creates are possible.
- Second plan: after a code edit, against existing state, which is the only way a change or a replacement can appear.
The first plan: an empty account, creates only
Every line begins with a plus sign because nothing exists yet. The reviewer ticks each create against the inventory and notes the two values Terraform hides in the output. Attributes of the password resource that do not matter here are left out.
- Four creates, matching the four inventory items; the summary says 4 to add, 0 to change, 0 to destroy.
- The administrator password and its hash are shown as (sensitive value) here, but they will be stored in state; see the sensitive-values list below.
If the matrix is wider than the box, scroll horizontally to read every column. Keyboard: focus the matrix and use Left/Right.
Terraform will perform the following actions:
# module.web_stack.random_password.db_admin will be created
+ resource "random_password" "db_admin" {
+ bcrypt_hash = (sensitive value)
+ id = (known after apply)
+ length = 20
+ result = (sensitive value)
(other attributes omitted here)
}
# module.web_stack.terraform_data.database will be created
+ resource "terraform_data" "database" {
+ id = (known after apply)
+ input = "default"
+ output = (known after apply)
}
# module.web_stack.terraform_data.network will be created
+ resource "terraform_data" "network" {
+ id = (known after apply)
+ input = "10.0.0.0/16"
+ output = (known after apply)
}
# module.web_stack.terraform_data.server will be created
+ resource "terraform_data" "server" {
+ id = (known after apply)
+ input = "web"
+ output = (known after apply)
+ triggers_replace = [
+ "img-2026-09",
]
}
Plan: 4 to add, 0 to change, 0 to destroy.The second plan: after a code edit
The inputs then changed: a new image name and a different database setting. Against the existing state the plan now shows one in-place change and one replacement. Terraform counts a replacement as one add and one destroy, so the summary reads 1 to add, 1 to change, 1 to destroy: one resource changed in place, and one resource destroyed and created again. The network and the password are not listed because nothing about them changed. The refresh lines Terraform prints before this text are left out.
If the matrix is wider than the box, scroll horizontally to read every column. Keyboard: focus the matrix and use Left/Right.
Terraform will perform the following actions:
# module.web_stack.terraform_data.database will be updated in-place
~ resource "terraform_data" "database" {
id = "b20c4e0c-1676-e3c7-828c-abd2f41f0437"
~ input = "default" -> "custom"
~ output = "default" -> (known after apply)
}
# module.web_stack.terraform_data.server must be replaced
-/+ resource "terraform_data" "server" {
~ id = "b3a6c154-8c31-3475-c69d-8ed4f3733112" -> (known after apply)
~ output = "web" -> (known after apply)
~ triggers_replace = [
~ "img-2026-09" -> "img-2026-10",
]
# (1 unchanged attribute hidden)
}
Plan: 1 to add, 1 to change, 1 to destroy.Reading the unexpected lines
The in-place change on the database means the code now sets a value that the inventory did not list. Either the inventory is incomplete or the code has an extra setting; the reviewer asks which, and corrects the code or the inventory before any apply. The replacement of the server is more serious. A replacement destroys the old server and creates a new one, which is harmless in a throwaway test account and harmful in production if the server holds state. The note records the cause, the changed image input in the list that forces replacement, and the decision: pin the image input so a later change is deliberate.
- Ask whether each unexpected line is a mistake in code or in the inventory.
- Pin inputs that cause replacement.
- Check any replacement against what the resource holds, and mark data-bearing resources with prevent_destroy so a plan that would delete them fails.
The sensitive-values list
The review ends with a list of values that state and plan files would hold. Here the administrator password is generated by the random provider and is stored in the state file in plain text, although the plan output hides it. The note recommends remote encrypted state with access control and says that marking the output sensitive hides it only in the terminal. The saved plan file is treated as sensitive and is not committed. This list is part of the handover so the buyer knows what to protect.
- Generated administrator password: present in state; protect the backend.
- Saved plan files: sensitive, and not committed.
- No secret appears in an input default or an output.
After the apply
In the test account the buyer reads the exact plan, approves it in writing and applies the corrected module themselves; the reviewer then runs a plan again. A clean plan, with the detailed exit code of zero and the message that the infrastructure matches the configuration, is the acceptance evidence that the code describes what now exists. Before the test resources are removed, the buyer reads a destroy plan that lists only what the module created, approves it and runs the destroy. For any production apply, the plan is re-run and re-read immediately before it. This outline is a checklist to use with any provider, not a delivered project.
Sources and limits
- Terraform: plan command Checked 2026-10-11.
- The plan lists actions without applying them and ends with a Plan: N to add, N to change, N to destroy summary; -detailed-exitcode returns 0 for no changes and 2 when changes are present; a saved plan holds sensitive data.
- Terraform: lifecycle Checked 2026-10-11.
- prevent_destroy fails a plan that would delete the resource.
- Terraform: sensitive data in state Checked 2026-10-11.
- Terraform stores values marked sensitive in state and plan files, and anyone who can access those files can read them; remote, encrypted, access-controlled state is recommended.