Terraform
for_each reference
By default, resource block configures one real infrastructure object. Similarly, a module block includes a child module's contents into the configuration one time.
Use the for_each meta-argument to manage several similar objects, such as a fixed pool of compute instances, without writing a separate block for each object. When a resource, module, or ephemeral block includes a for_each argument, Terraform creates an instance for each member of the list or map specified as the value.
Usage
The for_each meta-argument accepts a map or a set of strings and creates an instance for each item in that map or set. Each instance is associated with a distinct infrastructure object. Terraform creates, updates, or destroys each instance when applying changes to the configuration.
You can use pure functions, such as toset() and tomap(), to create a map or set for use in the for_each argument. All values that the for_each argument iterates over must be known before Terraform performs any remote resource operations. Specifying references to resource attributes that are only known after a configuration is applied, such as a unique ID generated by the remote API when an object is created, will result in an error.
The following example creates two azurerm_resource_group resources that reference a map created using the tomap() function:
resource "azurerm_resource_group" "rg" {
for_each = tomap({
a_group = "eastus"
another_group = "westus2"
})
name = each.key
location = each.value
}
The following example creates four aws_iam_user resources using a set created with the toset() function:
resource "aws_iam_user" "the-accounts" {
for_each = toset(["Todd", "James", "Alice", "Dottie"])
name = each.key
}
Limitations on values
The keys of the map or all values in a set of strings must be known values. Otherwise, Terraform returns an error message that for_each has dependencies that cannot be determined before apply and that a -target may be needed.
Keys in the for_each argument cannot be the result of or rely on the result of impure functions, including uuid, bcrypt, or timestamp, because Terraform defers evaluating impure functions during the main evaluation step.
You cannot use sensitive values, such as sensitive input variables, sensitive outputs, or sensitive resource attributes, as arguments in for_each. Sensitive values are not allowed because Terraform uses the value in for_each to identify the resource instance and always discloses it in UI output. Terraform returns an error if you attempt to use sensitive values as for_each arguments.
If you transform a value containing sensitive data into an argument for use in for_each, most functions in Terraform return a sensitive result when given an argument with any sensitive content. Refer to Using sensitive data as function arguments for more information.
In many cases, you can use a for expression to achieve similar results. For example, to call keys(local.map) where local.map is an object with sensitive values, but non-sensitive keys, you can create a value to pass to for_each with toset([for k,v in local.map : k]). Refer to Manage sensitive data for more information.
Expressions in for_each
The for_each meta-argument accepts map or set expressions, but unlike most arguments, the value must be known before Terraform performs any remote resource actions. As a result, for_each 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.
The for_each value must be a map or set with one element per desired resource
instance. To use a sequence as the for_each value, you must use an expression
that explicitly returns a set value, such as the toset function.
To prevent unexpected behavior during conversion, the for_each argument does not implicitly convert lists or tuples to sets. To declare resource instances based on a nested data structure or combinations of elements from multiple data structures, you can use Terraform expressions and functions to derive a suitable value. Refer to the following examples for more information:
- Transform a multi-level nested structure into a flat list.
- Combine collections to produce a list of element combinations.
Referring to instances
In blocks where for_each is set, Terraform creates an additional each object that you can use in expressions to modify the configuration of each instance.
This object has the following attributes:
each.key: The map key or set member corresponding to this instance.each.value: The map value corresponding to this instance. If a set is provided, this is the same aseach.key.
Terraform makes a distinction between the block containing the for_each argument and the instances associated with it. Terraform identifies instances by the map key or set member using the value provided to for_each.
<TYPE>.<NAME>ormodule.<NAME>: For example,azurerm_resource_group.rgrefers to the block.<TYPE>.<NAME>[<KEY>]ormodule.<NAME>[<KEY>]: For example,azurerm_resource_group.rg["a_group"]andazurerm_resource_group.rg["another_group"]refer 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 does not contain
the module index as the module's name suffices to reference the module.
Within nested provisioner or connection blocks, the special self object refers to the current resource instance, not the resource block as a whole.
Chain for_each between resources
Because a resource using for_each appears as a map of objects when used in
expressions elsewhere, you can directly use one resource as the for_each
of another in situations where there is a one-to-one relationship between
two sets of objects.
For example, in AWS an aws_vpc object is commonly associated with a number
of other objects that provide additional services to that VPC, such as an
internet gateway. If you are declaring multiple VPC instances using for_each
then you can chain that for_each into another resource to declare an
internet gateway for each VPC:
variable "vpcs" {
type = map(object({
cidr_block = string
}))
}
resource "aws_vpc" "example" {
# One VPC for each element of var.vpcs
for_each = var.vpcs
# each.value here is a value from var.vpcs
cidr_block = each.value.cidr_block
}
resource "aws_internet_gateway" "example" {
# One Internet Gateway per VPC
for_each = aws_vpc.example
# each.value here is a full aws_vpc object
vpc_id = each.value.id
}
output "vpc_ids" {
value = {
for k, v in aws_vpc.example : k => v.id
}
# The VPCs aren't fully functional until their
# internet gateways are running.
depends_on = [aws_internet_gateway.example]
}
This chain pattern explicitly and concisely declares the relationship between the internet gateway instances and the VPC instances, which tells Terraform to expect the instance keys for both to always change together, and typically also makes the configuration easier to understand for human maintainers.
Using sets
The Terraform language doesn't have a literal syntax for set values, but you can use the toset function to explicitly convert a list of strings to a set:
locals {
subnet_ids = toset([
"subnet-abcdef",
"subnet-012345",
])
}
resource "aws_instance" "server" {
for_each = local.subnet_ids
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = each.key # note: each.key and each.value are the same for a set
tags = {
Name = "Server ${each.key}"
}
}
Conversion from list to set discards the ordering of the items in the list and
removes any duplicate elements. For example, toset(["b", "a", "b"]) produces a set
containing only "a" and "b" in no particular order and discards the second "b".
If you are writing a module with an input variable that
will be used as a set of strings for for_each, you can set its type to
set(string) so that you don't need an explicit type conversion:
variable "subnet_ids" {
type = set(string)
}
resource "aws_instance" "server" {
for_each = var.subnet_ids
#...
}
How to choose between for_each and count
The count meta-argument performs a similar operation to for_each. Use for_each when some instance arguments must have distinct values that can't be directly derived from an
integer. Use the count argument when you want to create nearly identical instances.
You cannot use both a count and for_each argument in the same block.
Supported constucts
You can use for_each in the following Terraform configuration blocks:
Examples
The following use cases describe common patterns for the for_each argument.
Create multiple ephemeral resources
In the following example, the for_each argument creates multiple passwords for different database environments:
locals {
environments = toset(["dev", "staging", "prod"])
}
ephemeral "random_password" "db_passwords" {
for_each = local.environments
length = 16
override_special = "!#$%&*()-_=+[]{}<>:?"
}
resource "aws_db_instance" "databases" {
for_each = local.environments
identifier = "${each.key}-database"
db_name = "${each.key}db"
username = "dbadmin"
password_wo = ephemeral.random_password.db_passwords[each.key].result
# …
}
The ephemeral block creates a random_password ephemeral resource for each environment in the local.environments set. The aws_db_instance resource then uses the generated passwords for each database instance.
Terraform does not store the generated db_passwords values, but you can capture them in another resource to ensure those values are not lost. For an example of generating, storing, retrieving, and using an ephemeral password, refer to write-only arguments.
Create multiple instances of module resources
In the following example, Terraform creates one EC2 instance for each key in local.instance_configs, allowing you to customize properties per instance:
locals {
instance_configs = {
"example-instance-1" = { instance_type = "t2.micro" }
"example-instance-2" = { instance_type = "t2.small" }
"example-instance-3" = { instance_type = "t2.medium" }
}
}
module "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "6.0.2"
for_each = local.instance_configs
name = each.key
ami = data.aws_ami.latest_amazon_linux.id
instance_type = each.value.instance_type
depends_on = [aws_s3_bucket.example]
}
Create multiple instances of a resource
In the following example, the module block creates an instance of the publish_bucket module for each element in the for_each set. It uses the each.key attribute to create a unique name for each bucket.
my_buckets.tf
module "bucket" {
for_each = toset(["assets", "media"])
source = "./publish_bucket"
name = "${each.key}_bucket"
}
publish_bucket/bucket-and-cloudfront.tf
variable "name" {}
resource "aws_s3_bucket" "example" {
bucket = var.name
#...
}
resource "aws_iam_user" "deploy_user" {
#...
}
Import multiple resources
You can use for_each to import multiple resources. Refer to the import block documentation for an example.