Planreader

What "forces replacement" means in a Terraform plan

It means the provider can't make this change to the existing object, so the object has to be destroyed and created again. The attribute with the comment next to it is the one responsible. Many other lines in that resource's diff, such as a new ID, are consequences of the rebuild.

This is the line to read most carefully. A replaced database, disk or bucket is a new, empty object unless something else restores the data.

Read your own plan

Which goes first

In plan textIn plan JSONWhat happens
-/+["delete", "create"]The old object is destroyed, then the new one is created. There's a gap in between.
+/-["create", "delete"]The new object is created first, then the old one is destroyed. You get this from create_before_destroy.

Finding the attribute that caused it

In plan text, look for # forces replacement at the end of an attribute or block. In JSON, look at replace_paths in the resource's change. It lists the attribute paths that caused the replacement.

Not every provider marks this in the text. When we planned a built-in terraform_data resource with Terraform 1.9.8 and OpenTofu 1.13.2, the text had no comment, while the JSON still named the attribute in replace_paths. If the text doesn't explain a replacement, use the JSON.

terraform plan -out=tfplan
terraform show -json tfplan > plan.json

Other reasons a resource is replaced or destroyed

The JSON format gives a reason in action_reason. These are the values in the Terraform and OpenTofu documentation:

ValueIn plain English
replace_because_cannot_updateThe provider can't apply the change in place.
replace_because_taintedThe object is marked tainted, so it's rebuilt.
replace_by_requestSomeone asked for this with -replace.
replace_by_triggersA replace_triggered_by rule fired. Listed in the OpenTofu documentation.
delete_because_no_resource_configThe resource block is gone from the configuration.
delete_because_no_moduleThe module containing it is gone from the configuration.
delete_because_wrong_repetitionThis instance's address doesn't fit the count or for_each setting any more.
delete_because_count_indexIts index is past the end of the new count.
delete_because_each_keyIts key is no longer in the for_each map.
delete_because_no_move_targetA moved block points away from it, with no target. Listed in the OpenTofu documentation.

What to do about one you didn't expect