Home / Insights / `count` Is a Production Outage Waiting for a Deletion
Cloud Architecture 5 min read

`count` Is a Production Outage Waiting for a Deletion

Terraform's `count` assigns resources by index. Delete the middle item and the indices shift — Terraform destroys and recreates everything after it. On subnets, node pools, or persistent volumes, that's not a warning. It's an incident.

terraforminfrastructure-as-codeplatform-engineeringcountfor-eachkubernetes

If you’ve written Terraform for long enough, you’ve probably seen a plan that showed destroy on something you didn’t expect. You added a resource, removed a resource, changed a count, and suddenly Terraform wants to recreate something that wasn’t supposed to change.

The culprit, most of the time, is count.

How count assigns resources

When you use count in Terraform, resources are identified by their index in the list: resource.name[0], resource.name[1], resource.name[2]. The index is the identity.

The problem: if you delete the item at position 1 from a list of three, position 2 becomes the new position 1. Terraform sees that the resource at index 1 has new configuration and the resource at index 2 no longer exists. Its plan: destroy resource.name[1] with the old config, create resource.name[1] with the new config, destroy resource.name[2].

You deleted one thing. Terraform is going to touch two. And “touch” means destroy and recreate — not modify in place.

Where this actually hurts

This failure mode is annoying for stateless resources. For stateful ones, it’s an incident.

Subnets and VPC ranges: a subnet that gets recreated has its IP allocations disrupted. Anything that had a static IP assignment in that range needs to be updated. Load balancers, NAT gateways, and anything else tied to that subnet goes with it.

GKE or EKS node pools: recreating a node pool drains and replaces the nodes. Every pod on those nodes is evicted. If your cluster is sized to fit, you may not have headroom to reschedule everything while the new pool comes up.

Persistent volumes and storage: this is the worst case. Some volume configurations don’t survive a resource recreation. The data may be safe — if the deletion doesn’t cascade to the underlying storage — but the path from “we deleted a node pool” to “we lost data” is shorter than most people expect.

The scenario that makes this a production incident: you have three environment configurations managed with count. You deprecate the middle one. The plan looks reasonable — one delete, one recreate. You apply it. Two environments’ worth of infrastructure gets touched.

The fix: for_each with a stable key

The fix is for_each with a key that identifies the resource meaningfully — the environment name, the subnet CIDR, the region. That key is the identity, not the index. If you delete the middle item, Terraform destroys only that item. The others don’t move.

Here’s the difference in practical terms:

With count, the question Terraform asks is: “what’s at position N?” Change the size of the list and the answer changes.

With for_each, the question Terraform asks is: “what’s the resource with this key?” Remove an item and the answer for every other key is unchanged.

For any resource where order is meaningful and deletions are possible, for_each is the right choice. That includes subnets, security groups, node pools, storage configurations — anything you’ll ever need to remove from the middle of a list.

The plan review habit that catches this

Terraform plan output makes this visible if you read it carefully. The signal is a destroy followed by a create on the same resource type, where you only expected a single delete.

The habit worth building: before applying any plan that involves a resource removal, check whether anything adjacent to the deletion is also being destroyed. If subnets, node pools, or persistent volumes appear in the destroy section and you didn’t explicitly remove them, look at why before applying.

The most common root cause for that unexpected destroy: count with an index that shifted. The second most common: a for_each key that changed because it was derived from something that changed.

In both cases, the plan is telling you something real. The question is whether you’re reading it.

One more consideration

Even with for_each, recreates can happen if you change the key. A node pool keyed by environment name that gets renamed will be destroyed and recreated. Key stability matters — the key should be something that won’t change for the lifetime of the resource.

For most infrastructure, that means keying by the business identity of the resource: the environment it serves, the purpose it fills, the region it lives in. Not by a generated ID, not by a computed value, and not by position in a list.

count is useful for simple cases — spinning up N identical resources where nothing will ever be removed from the middle. For everything else, for_each with a stable key is the safer foundation to build on.

Discussion