What happens during `terraform plan`, and how does it decide between update-in-place vs. destroy-and-recreate?
Quick Answer
`plan` refreshes in-memory state against real infrastructure (unless `-refresh=false`), walks the dependency graph, and for each resource diffs its configuration against state to classify the action as no-op, create, update-in-place, or destroy-and-recreate. The choice between update-in-place and replace comes from each provider's schema: every argument is marked either updatable in place or `ForceNew` — changing a `ForceNew` attribute (e.g., an EC2 instance's `availability_zone`) forces destroy+create, while changing a mutable attribute (e.g., `tags`) triggers an in-place API update. This is why the plan output explicitly marks each resource with `~` (update), `-/+` (replace), `+` (create), or `-` (destroy).
Detailed Answer
Understanding exactly what terraform plan does under the hood — and specifically how it chooses between an in-place update and a full destroy/recreate — is a strong signal of real Terraform experience versus surface familiarity.
Step by step
- Refresh (unless
-refresh=false): Terraform queries each managed resource's real current attributes from the provider and updates its in-memory view of state accordingly (this doesn't persist untilapply, in recent Terraform versions this happens as part of the plan itself rather than a separate step). - Graph walk: Terraform walks the dependency graph, and for each resource, diffs the refreshed prior state against the desired configuration.
- Action classification: for every resource, the diff resolves to one of: no-op (no change), create (new resource block, nothing in state yet), update-in-place, or destroy-and-recreate ("replace").
- Output: the plan renders each resource's action with a symbol —
+create,-destroy,~update in place,-/+(or+/-) destroy and recreate.
How update-in-place vs. replace is decided
This comes from the provider's schema, not from Terraform Core's own logic. Every attribute a resource type exposes is annotated by the provider as either:
- Updatable in place — the cloud API supports a "modify" operation for this attribute (e.g., changing an EC2 instance's
tags, or a security group's description). Terraform issues an update call, and the resource keeps its identity (same ID). ForceNew— the underlying API has no way to modify this attribute on an existing object (e.g., an EC2 instance'savailability_zone, or an RDS instance'sengine). Any change to aForceNewattribute forces Terraform to destroy the existing resource and create a brand-new one to satisfy the new configuration.
# aws_instance.web must be replaced
-/+ resource "aws_instance" "web" {
~ availability_zone = "us-east-1a" -> "us-east-1b" # forces replacement
instance_type = "t3.micro"
}
Why this matters practically
Recognizing which attributes are ForceNew for a given resource type (documented in each provider's resource reference) helps you predict — before ever running plan — whether a given configuration change will cause a disruptive replace or a safe in-place update, which is critical for reasoning about downtime risk before touching production infrastructure.