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 with0
) 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>
ormodule.<NAME>
(for example,aws_instance.server
) refers to the resource block.<TYPE>.<NAME>[<INDEX>]
ormodule.<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.