Skip to main content

Terraform Plan Says Destroy and Recreate. What Now?

Updated 4 min readMarkdown

By Devang Pandya · Cloud Tech by Devang · Cloud • DevOps • AI — Made Simple

"Terraform plan shows a resource will be destroyed and recreated. What now?" This is one of my favourite Terraform interview questions. A weak answer is "run apply, it'll recreate it." That's fine for a security group rule. On a database, a load balancer or a DNS-facing resource, it can mean an outage or data loss. Here's how a strong engineer handles it.


1. Find the trigger

In the plan output, a replacement shows as -/+ (or +/- with create_before_destroy), and the attribute causing it is marked # forces replacement. That line is the whole story. Common triggers:

  • Changing a resource name or identifier that the cloud API can't update in place
  • Moving to a different subnet, VPC or availability zone
  • A new AMI or image ID
  • Changing an immutable property such as encryption settings or engine type on some resources

Some attributes can be updated in place and some can't. The provider decides, so check the provider documentation.

2. Was it intended?

Many surprise replacements aren't caused by the change you made:

  • Provider version upgrades can change defaults or how attributes are read. Pin provider versions and read the changelog before upgrading.
  • Data sources such as "latest AMI" can return a new value on any run and quietly trigger replacement.
  • Drift: someone changed the resource in the console, and Terraform now wants to put it back.

Use terraform plan -refresh-only to see drift separately from your own changes.

3. Renamed or moved? Tell Terraform

If you renamed a resource, moved it into a module or changed a count index to for_each, Terraform sees a new address and plans destroy plus create. The real resource is the same, so update the state, not the infrastructure:

  • A moved block (Terraform 1.1+) records the refactor in code and is reviewable in the PR.
  • terraform state mv does the same thing imperatively.

Afterwards, the plan should show no changes.

4. Protect what matters

Use lifecycle rules on purpose:

  • prevent_destroy = true on stateful resources such as databases, buckets with data and KMS keys. Any plan that would destroy them fails.
  • create_before_destroy = true where replacement is acceptable but downtime isn't. Watch for name collisions: unique names often need a prefix or random suffix.
  • Cloud-side protection as a second layer, such as deletion protection on databases and load balancers.

5. Use ignore_changes carefully

If another system legitimately manages an attribute (autoscaling changing desired capacity, or tags added by a policy engine), ignore_changes on that specific attribute is right. Using it to silence a plan you don't understand is how config and reality drift apart for good.

6. Make it a process

  • Save the plan with -out, review it in the PR, and apply exactly that plan.
  • Add a policy check (OPA/Conftest, Sentinel or a simple script on terraform show -json) that blocks or flags any delete action on critical resource types.
  • For replacements you can't avoid on stateful resources, plan it like a migration: backup or snapshot, a maintenance window, a DNS/endpoint change plan and a rollback path.

The senior angle

A plan is a contract, not a formality. "Destroy and recreate" on a database or load balancer can mean data loss, a new endpoint, broken connection strings or DNS churn that clients cache for hours. The seniority isn't in knowing the commands. It's in reading the plan line by line and refusing to apply anything you can't explain.

In my 14+ years in Cloud and DevOps, the most dangerous Terraform command has been an apply nobody read.


Let's connect: Free 15-min Cloud & DevOps Interview Guidance Call 👆 link in bio (topmate.io/cloudtechbydevang)

About the author

  • I help engineers learn cloud, DevOps, SRE and AI in simple English, crack interviews and follow clear roadmaps. 14+ years building and running platforms on AWS, GCP, Kubernetes and Terraform. One topic a day.

The point about a data source returning a new latest AMI quietly triggering a replacement really stood out — that is the kind of surprise that does not show up in the change you actually made. I also liked the distinction between using ignore_changes for something another system legitimately manages versus using it to silence a plan you do not understand. Do you have a rule of thumb for when a replacement on a stateful resource is worth handling with a migration plan instead of accepting the recreate and scheduling downtime?