Terraform is great for defining your environment using code. However, copying and pasting the same resource definition can be tedious if you create multiple resources. Luckily, Terraform provides two ways to manage several similar resources using for each and count.
In this tutorial, you’ll learn about each method for creating multiple resources. You’ll then use each one when deploying Azure storage accounts.
Deploying Multiple Resources with Count
The count
meta-argument creates multiple instances of a module or resource block. You add the count
argument inside the block definition and assign it a whole number, and Terraform creates that number of instances. Each instance is its own distinct object and is created, updated, or destroyed separately when applying the configuration. The count
argument is great for resources that share the same properties.
While count has always worked with resources, Hashicorp didn’t add support for modules until Terraform v0.13.
Let’s take a look at deploying multiple storage accounts. In the resource definition below, each storage account has the same base name (jbtterraformdemo), account_kind (StorageV2), account_replication_type (LRS), and account_tier (Standard).
At the beginning of the resource definition, there is an additional property: count. This argument tells Terraform to create 3 of this kind of resource.
resource "azurerm_storage_account" "sa" {
count = 3
name = "jbtterraformdemo"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_kind = "StorageV2"
account_replication_type = "LRS"
account_tier = "Standard"
}
However, in this current configuration, Terraform attempts to create three storage accounts with the same name as jbtterraformdemo. This action causes an error as storage accounts must have a globally unique name across Azure.
To fix this, use the variable count.index, which represents the index number of the current count loop. The count index starts at 0 and increments by 1 for each resource. You include the count index variable in strings using interpolation. Check out name property in the updated example below showing the count index included as part of the storage account name.
resource "azurerm_storage_account" "sa" {
count = 3
name = "jbtterraformdemo${count.index}"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_kind = "StorageV2"
account_replication_type = "LRS"
account_tier = "Standard"
}
When deployed, Terraform names the storage accounts jbtterraformdemo0, jbtterraformdemo1, and jbtterraformdemo2.

If you don’t want the index to start at 0, you can increment the count index inside the curly brackets { }. Here’s an example of incrementing the count index by 5, which results in the storage account names jbtterraformdemo5 (index 0 + 5), jbtterraformdemo6 (index 1 + 5), and jbtterraformdemo7 (index 2 + 5).
resource "azurerm_storage_account" "sa" {
count = 3
name = "jbtterraformdemo${count.index + 5}"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_kind = "StorageV2"
account_replication_type = "LRS"
account_tier = "Standard"
}

Deploying Multiple Resources with For Each
Like the count argument, the for_each
meta-argument creates multiple instances of a module or resource block. However, instead of specifying the number of resources, the for_each argument takes in a map or set of strings. The number of resources Terraform creates depends on the number of input items.
Terraform 0.12.6 added for_each capabilities for resources, while Terraform 0.13 added support for modules.
For example, say you want to create multiple storage accounts as you did in the previous section. However, each storage account requires different property values. Start by creating a variable of map type that contains objects. Each object represents the different storage accounts, including the name, location, kind, replication type, and tier. For simplicity’s sake, the variable definition here includes a default value so you can see it in one place.
variable "stgaccts" {
type = map(object({
name = string
location = string
kind = string
repl = string
tier = string
}))
default = {
"sa1" = {
name = "jbttfdemowestus3"
location = "WestUS3"
kind = "StorageV2"
repl = "LRS"
tier = "Standard"
},
"sa2" = {
name = "jbttfdemoeastus"
location = "EastUS"
kind = "StorageV2"
repl = "GRS"
tier = "Standard"
}
}
}
In the storage account resource definition, use the for_each
argument and specify the stgaccts
variable. This definition directs Terraform to iterate over how many items are listed in the stgaccts
variable (in this example, there are 2).
Next, you refer to the current item in the variable using the each keyword, and you use value.<property name> to access information in the current item. Take a look at the new storage account resource definition below to see the values for name, location, account_kind, account_replication_type, and account_tier.
resource "azurerm_storage_account" "sa" {
for_each = var.stgaccts
name = each.value.name
resource_group_name = azurerm_resource_group.rg.name
location = each.value.location
account_kind = each.value.kind
account_replication_type = each.value.repl
account_tier = each.value.tier
}
Terraform iterates through the for_each
variable and creates the storage accounts with the properties you defined in each object.

Notes on Count and For Each
Here are a couple of things to keep in mind when working with Terraform count and for each:
- You cannot use
count
andfor_each
at the same time in the same module or resource block. - When using
for_each
, the keys of the map must be known values, and sensitive values are not allowed as arguments. - When instances are almost identical, use
count
. - If some arguments required distinct values that cannot be derived from the count integer, use
for_each
instead.
Ned Bellavance has a great video on Choosing Between Count and For-Each that goes into great detail on the when to use each one.
Closing
Using the count
and for_each
methods make your Terraform configurations more dynamic. It can take some getting used to using both and getting the syntax right!
Enjoyed this article? Check out more of my Terraform content here!