Chapter 5: Iteration and Conditionals
Topic 29

count

IterationHCL

count creates N copies of a resource from a single block. Set count = 3 on an aws_instance and Terraform makes three instances, addressed aws_instance.web[0], [1], and [2]. It is the simplest multiplicity primitive in HCL, and the same thing that makes it simple — identity by numeric position — is what makes it dangerous.

Because each instance is identified by its index, removing an item from the middle of the list renumbers everything after it. Terraform sees the resource that used to be [2] sitting at [1] now, decides they are different objects, and plans to destroy and recreate things you never touched. Knowing exactly when that happens is the whole point of this page.

Basic Usage

Set count to a whole number and reference count.index inside the block to differentiate each instance — picking an availability zone, building a name, slicing a CIDR. The index is zero-based, so three instances are numbered 0, 1, and 2.

count.index differentiates each copy
resource "aws_instance" "web" {
  count         = 3
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  tags = {
    Name = "web-${count.index}"  # web-0, web-1, web-2
  }
}

Conditional Creation

The idiom count = var.enabled ? 1 : 0 turns count into an on/off switch: a truthy variable makes exactly one instance, a falsy one makes zero and the resource does not exist at all. This is the standard way to make a resource optional in Terraform, since there is no if statement to wrap a block in.

count as an on/off toggle
resource "aws_cloudwatch_log_group" "audit" {
  count = var.enable_audit_logs ? 1 : 0
  name  = "/app/audit"
}

# Reference it as a list, even when there is only one:
# aws_cloudwatch_log_group.audit[0].arn

Index-Based Addressing

Every instance created by count lives at an indexed address — aws_instance.web[0], aws_instance.web[1] — and that address is exactly how the instance is keyed in state. The bare address aws_instance.web is now a list, so referencing aws_instance.web.id without an index is an error. Use aws_instance.web[0].id for one, or aws_instance.web[*].id to collect them all.

The Renumbering Trap

This is where count bites. Suppose count = length(var.names) over ["api", "worker", "cron"], giving you [0]=api, [1]=worker, [2]=cron. Remove "worker" from the middle and the list becomes ["api", "cron"]: now [1] is cron and [2] is gone. Terraform plans to update [1] from worker into cron and destroy [2] — it churns cron, a resource you only wanted to keep, because positions shifted underneath it.

Deleting a middle element renumbers the tail
[0]api · [1]worker · [2]cron
remove "worker"
[0]api · [1]cron
[1] updated, [2] destroyed
terraform plan after deleting a middle element
# aws_instance.web[1] will be updated in-place  (was worker, now cron)
~ resource "aws_instance" "web" {
    ~ tags = { "Name" = "worker" -> "cron" }
  }

# aws_instance.web[2] will be destroyed
Plan: 0 to add, 1 to change, 1 to destroy.

Only appending to the end of the list is safe with count; any insert or delete anywhere else renumbers the tail. That single fact is the reason for_each exists and the reason the next two topics push you toward it for anything with identity.

Where count Fits

count is the right tool in two cases and almost no others: a fixed number of genuinely identical resources where position carries no meaning, and the ? 1 : 0 conditional-creation toggle for a single optional resource. Three identical NAT gateways, one per AZ, fit. A set of distinctly-named services or any collection a human will edit over time does not — reach for for_each there.

Common Mistakes
  • Using count over a list and removing a middle element, renumbering every following resource and triggering destroy/recreate on resources you never meant to touch.
  • Toggling a resource with count and editing the surrounding list in the same change, compounding the index shuffle so the plan is impossible to read.
  • Reaching for count to create a set of named, distinct resources where for_each would give each a stable key and surgical edits.
  • Referencing aws_instance.web.id with no index while count is set — that address is a list now, so the reference errors.
  • Forgetting that a count = ... ? 1 : 0 resource is still a list, then referencing it as .arn instead of [0].arn.
Best Practices
  • Use count only for a fixed number of identical resources or the count = condition ? 1 : 0 conditional-creation idiom.
  • Switch to for_each the moment resources have distinct identities or the collection can change anywhere but the end.
  • Never drive count from a list whose elements get inserted or deleted from the middle — that is the renumbering trap by design.
  • Reference every count resource with an explicit index ([0]) or a splat ([*]); the bare name is a list.
  • Use count.index only for position-derived values (an AZ slice), never to encode a name a human will later reorder.
Comparable tools CloudFormation no clean per-resource count — you template or use macros Pulumi host-language loops produce the instances Terraform count is HCL's basic multiplicity primitive

Knowledge Check

You have count = length(var.names) over ["api", "worker", "cron"] and delete "worker". What does the plan show?

  • It updates index 1 from worker to cron and destroys index 2, churning cron even though you didn't change it
  • It destroys only the worker instance sitting at index 1 and leaves both the api and cron instances completely untouched
  • It recreates all three instances from scratch, including the unchanged api at index 0
  • Nothing changes at all, because count tracks each resource by its name

What is the idiomatic way to make a single resource optional with count?

  • count = var.enabled ? 1 : 0 — one instance when true, zero when false
  • count = var.enabled — Terraform coerces the bool to a count
  • Wrap the resource definition in an if block that gates it on the flag
  • count = null whenever you want Terraform to skip it entirely

When is count the right choice over for_each?

  • A fixed number of identical resources, or a simple on/off toggle for one resource
  • Any collection of named resources you expect to edit over time
  • Whenever the underlying source data is a map of objects keyed by string rather than a plain ordered list
  • It is always preferable because it takes fewer characters to write

With count set, why does aws_instance.web.id error?

  • The address is now a list of instances, so you must index it: aws_instance.web[0].id
  • id is only known after apply, so the address can never be referenced at all
  • count hides all attributes except count.index
  • You must use each.key in place of the attribute name to reach the indexed instance you want

You got correct