The Terraform for each meta argument allows you to use a map or a set of strings to deploy multiple similar objects (such as virtual machines) without having to define a separate resource block for each one. This is great for making our Terraform plans more efficient!
Note: for_each
was added in Terraform 0.12.6, and support for using it with Terraform modules was added in 0.13. Let’s go straight into looking at some examples of how to use Terraform for each loops.
Terraform for_each Examples
All the examples here will be using the Azure Terraform provider. To start with, lets take a look at a resource block used to deploy a Azure virtual machine:
resource "azurerm_windows_virtual_machine" "vm" {
name = var.vm_name
resource_group_name = azurerm_resource_group.demo-rg.name
location = var.location
size = "Standard_F2"
admin_username = var.vm_admin_login
admin_password = var.vm_admin_password
tags = var.tags
network_interface_ids = [
azurerm_network_interface.nic.id,
]
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2016-Datacenter"
version = "latest"
}
}
This resource block will deploy a single Azure virtual machine (as part of a wider Terraform plan which also includes supporting resources such as the virtual machine’s network interface, the resource group, vnet and so on). The virtual machine resource takes it’s name from the var.vm_name
variable, which would be defined like this:
variable "vm_name" {
description = "Azure Virtual Machine Name"
default = "VM1"
type = string
}
The resultant output from the Terraform plan will be an Azure virtual machine, taking its name from the variable. But what if we wanted to deploy more than one similar virtual machine? We could just copy the whole resource block and change some of the values. Or, we could use a for_each loop.
Terraform For Each Loop using a Set of String Values
Instead of repeating the resource block multiple times we can use a for_each loop that goes through a set of multiple values. For example, we can change the vm_name
variable so that it contains a list of values (in this case, virtual machine names):
variable "vm_names" {
description = "VM Names"
default = ["vm1","dbserver1"]
type = set(string)
}
We then need to update the virtual machine resource block to contain a for_each statement, and to refer to the vm_names variable (only the relevant part of the resource block shown for brevity):
resource "azurerm_windows_virtual_machine" "vm" {
for_each = toset(var.vm_names)
name = each.value
...
}
The result of this configuration is that Terraform would now loop through the vm_names list variable, and deploy two virtual machines, named vm1
and dbserver1
, with an otherwise identical configuration.
Now, an important thing to be aware of when using Terraform for_each loops is that when there resources which are closely dependent on another resource (for example a virtual machine and a virtual machine NIC), you have to apply the for_each loop to both resources. To successfully deploy the terraform plan we would need the following configuration under the virtual machine network interface resource:
resource "azurerm_network_interface" "nic" {
for_each = toset(var.vm_names)
name = "${each.value}-nic"
...
}
And in the virtual machine resource we would need to link to the respective network interface by referring to it like this:
network_interface_ids = [
azurerm_network_interface.nic[each.key].id,
]
We are using the key in the for_each loop to align each VM to the correct network interface each time both resources are looped through.
Great, we now have a quick and efficient way to deploy multiple virtual machines or other resources with the same configuration by using a for_each loop and a set.
Terraform For Each Loop using a Map
Using a set with a for_each loop is great if you have a bunch of near identical resources to deploy. But what if you want to change a few of the resource properties – for example, along with setting the virtual machine name, we might want to have some of the virtual machines be a different size, or maybe have different tags and so on, whilst otherwise being similar.
Again, we could simply copy the whole virtual machine resource block and change the values. Or we could use a map. A map allows us to provide a bunch of keys and values which we can use with a for_each loop. An example explains it well:
variable "vm" {
description = "Virtual Machines"
type = map
default = {
vm1 = {
name = "vm1"
size = "standard_b2ms"
}
vm2 = {
name = "dbserver1"
size = "Standard_F2"
}
}
}
Here I have created a variable which contains a number of properties – in this case, in two groups representing the virtual machines I wish to deploy. Using a map rather than a set means we have to change some properties in the virtual machine resource block.
We still have a for_each property, which this time just points to the ‘vm’ variable shown above. But, now for the VMs name and size properties we refer to the specific key/value in the variable. E.g. for the name property we use:
name = each.value.name
Let’s see how this looks for the whole virtual machine resource block:
resource "azurerm_windows_virtual_machine" "vm" {
for_each = var.vm
name = each.value.name
resource_group_name = azurerm_resource_group.demo-rg.name
location = var.location
size = each.value.size
admin_username = var.vm_admin_login
admin_password = var.vm_admin_password
tags = var.tags
network_interface_ids = [
azurerm_network_interface.nic[each.key].id,
]
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2016-Datacenter"
version = "latest"
}
}
The outcome of this configuration is that Terraform would now loop through the vm map variable, and deploy two virtual machines, named vm1
and dbserver1
, which use the standard_b2ms and Standard_F2 sizes respectively. It’s easy to see how using this for_each loop technique you can define multiple similar resources, such as virtual machines, using a relatively small amount of Terraform code.
Summary
In this article you have learned how to use the terraform for each loop effectively. This included how to use a for_each loop with a map and with a set. For more information check out the official documentation here.