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.
Which goes first
| In plan text | In plan JSON | What 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:
| Value | In plain English |
|---|---|
replace_because_cannot_update | The provider can't apply the change in place. |
replace_because_tainted | The object is marked tainted, so it's rebuilt. |
replace_by_request | Someone asked for this with -replace. |
replace_by_triggers | A replace_triggered_by rule fired. Listed in the OpenTofu documentation. |
delete_because_no_resource_config | The resource block is gone from the configuration. |
delete_because_no_module | The module containing it is gone from the configuration. |
delete_because_wrong_repetition | This instance's address doesn't fit the count or for_each setting any more. |
delete_because_count_index | Its index is past the end of the new count. |
delete_because_each_key | Its key is no longer in the for_each map. |
delete_because_no_move_target | A moved block points away from it, with no target. Listed in the OpenTofu documentation. |
What to do about one you didn't expect
- Check whether the attribute really needed to change. A renamed identifier or a formatting difference in a value is a common cause, and reverting it removes the replacement.
- If it's stateful, take a backup and plan how the data moves before you apply.
- If it's only an address that changed, a
movedblock keeps the object where it is. - If you need no gap in service, look at
create_before_destroy, and check the names don't collide while both exist.