Skip to main content

The count Meta-Argument

-> Note: A given resource or module block cannot use both count and for_each.

By default, a resource block configures one real infrastructure object. (Similarly, a module block includes a child module's contents into the configuration one time.) However, sometimes you want to manage several similar objects (like a fixed pool of compute instances) without writing a separate block for each one. OpenTF has two ways to do this: count and for_each.

If a resource or module block includes a count argument whose value is a whole number, OpenTF will create that many instances.

Basic Syntax

count is a meta-argument defined by the OpenTF language. It can be used with modules and with every resource type.

The count meta-argument accepts a whole number, and creates that many instances of the resource or module. Each instance has a distinct infrastructure object associated with it, and each is separately created, updated, or destroyed when the configuration is applied.

resource "aws_instance" "server" {
count = 4 # create four similar EC2 instances

ami = "ami-a1b2c3d4"
instance_type = "t2.micro"

tags = {
Name = "Server ${count.index}"
}
}

The count Object

In blocks where count is set, an additional count object is available in expressions, so you can modify the configuration of each instance. This object has one attribute:

  • count.index — The distinct index number (starting with 0) corresponding to this instance.

Using Expressions in count

The count meta-argument accepts numeric expressions. However, unlike most arguments, the count value must be known before OpenTF performs any remote resource actions. This means count can't refer to any resource attributes that aren't known until after a configuration is applied (such as a unique ID generated by the remote API when an object is created).

Referring to Instances

When count is set, OpenTF distinguishes between the block itself and the multiple resource or module instances associated with it. Instances are identified by an index number, starting with 0.

  • <TYPE>.<NAME> or module.<NAME> (for example, aws_instance.server) refers to the resource block.
  • <TYPE>.<NAME>[<INDEX>] or module.<NAME>[<INDEX>] (for example, aws_instance.server[0], aws_instance.server[1], etc.) refers to individual instances.

This is different from resources and modules without count or for_each, which can be referenced without an index or key.

Similarly, resources from child modules with multiple instances are prefixed with module.<NAME>[<KEY>] when displayed in plan output and elsewhere in the UI. For a module without count or for_each, the address will not contain the module index as the module's name suffices to reference the module.

-> Note: Within nested provisioner or connection blocks, the special self object refers to the current resource instance, not the resource block as a whole.

When to Use for_each Instead of count

If your instances are almost identical, count is appropriate. If some of their arguments need distinct values that can't be directly derived from an integer, it's safer to use for_each.

Before for_each was available, it was common to derive count from the length of a list and use count.index to look up the original list value:

variable "subnet_ids" {
type = list(string)
}

resource "aws_instance" "server" {
# Create one instance for each subnet
count = length(var.subnet_ids)

ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = var.subnet_ids[count.index]

tags = {
Name = "Server ${count.index}"
}
}

This was fragile, because the resource instances were still identified by their index instead of the string values in the list. If an element was removed from the middle of the list, every instance after that element would see its subnet_id value change, resulting in more remote object changes than intended. Using for_each gives the same flexibility without the extra churn.