Operations 9 min read

Why Switching from count to for_each Makes Terraform Resource Management Safer

This article explains how Terraform's count creates indexed resource lists that are hard to delete individually, why for_each with named keys offers more flexible and safe lifecycle management, and provides step‑by‑step migration and deletion examples.

Ops Development & AI Practice
Ops Development & AI Practice
Ops Development & AI Practice
Why Switching from count to for_each Makes Terraform Resource Management Safer

In the previous article we introduced the core concept of IaC and used the count argument to quickly create two servers. A practical problem arises when we want to delete only WebServer-1 while keeping WebServer-2. Terraform can only delete resources from the end of the list, making selective removal difficult and risky.

How count works

When count is used, Terraform treats the resources as an array. In the example, aws_instance.web_server becomes a list where aws_instance.web_server[0] maps to WebServer-1 and aws_instance.web_server[1] maps to WebServer-2. Reducing count from 2 to 1 forces Terraform to delete the highest index ( [1]), not the specific element you want.

Directly deleting an element in the middle would require shifting indices, which Terraform implements as a destroy‑and‑recreate operation. This changes the resource address in the state file, causing service interruption.

Problem with count

The identity of a resource is tightly bound to its list index. When you need to operate on a specific member of a collection, this coupling becomes inflexible and dangerous.

Solution: Embrace for_each

Terraform introduced the for_each meta‑argument to identify each resource with a meaningful string key instead of an arbitrary numeric index. for_each accepts a map or a set of strings. Rewriting the previous example with for_each looks like this:

# Create two EC2 instances using for_each
resource "aws_instance" "web_server" {
  # for_each accepts a set of strings
  for_each = toset(["alpha", "beta"]) # give the servers names "alpha" and "beta"

  ami           = "ami-0c55b159cbfafe1f0" # example AMI
  instance_type = "t2.micro"
  subnet_id     = aws_subnet.app_subnet.id
  security_groups = [aws_security_group.web_sg.name]

  tags = {
    # each.key resolves to "alpha" and "beta"
    Name = "WebServer-${each.key}"
  }
}

Now the resources are addressed as aws_instance.web_server["alpha"] and aws_instance.web_server["beta"], decoupling identity from order.

How to delete a specific resource

To delete the alpha server while keeping beta, simply remove "alpha" from the for_each set:

# ...
  # keep only "beta", remove "alpha"
  for_each = toset(["beta"]) 
# ...

Running terraform apply will generate a plan that destroys aws_instance.web_server["alpha"] and leaves aws_instance.web_server["beta"] untouched.

When to use count vs. for_each

count : suitable for creating a set of identical, stateless resources where you only care about quantity, e.g., multiple identical IAM policy attachments.

for_each : preferred in most cases, especially for stateful resources (servers, databases, disks) that need independent lifecycle management.

Migration pitfalls

Changing existing count resources directly to for_each will make Terraform think the old indexed resources must be destroyed and new keyed resources created, causing downtime. To avoid this, use terraform state mv to move the state addresses before applying the new configuration.

# Move old address 'aws_instance.web_server[0]' to new address 'aws_instance.web_server["alpha"]'
terraform state mv 'aws_instance.web_server[0]' 'aws_instance.web_server["alpha"]'

# Move old address 'aws_instance.web_server[1]' to new address 'aws_instance.web_server["beta"]'
terraform state mv 'aws_instance.web_server[1]' 'aws_instance.web_server["beta"]'

After updating the code and moving the state, run terraform plan to verify that no changes are required, confirming a successful migration.

Conclusion

While count is simple, it ties resource identity to an unstable order, making selective updates hazardous. for_each is the modern Terraform best practice for managing collections, giving each resource a stable name and enabling precise, safe operations.

Adopt for_each whenever possible to avoid unnecessary operational headaches.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Resource Management_countiaccloud operationsfor_each
Ops Development & AI Practice
Written by

Ops Development & AI Practice

DevSecOps engineer sharing experiences and insights on AI, Web3, and Claude code development. Aims to help solve technical challenges, improve development efficiency, and grow through community interaction. Feel free to comment and discuss.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.