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.

Three storage accounts deployed using count.

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"
}
Storage accounts deployed by manipulating the count index.

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.

Storage accounts deployed using for_each

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 and for_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!