Understanding the Differences Between Terraform Count and For_each

·

4 min read

Cover Image for Understanding the Differences Between Terraform Count and For_each

Terraform, as a powerful Infrastructure as Code (IaC) tool, provides two key constructs—count and for_each—to manage resource creation dynamically. While both enable you to create multiple resources efficiently, they differ in their flexibility and use cases. In this article, we’ll explore the differences between count and for_each, and when to use each.


What is Terraform Count?

The count meta-argument is a simple way to create multiple instances of a resource. By specifying a numeric value for count, Terraform will create that many instances of the resource.

Example:

resource "azurerm_virtual_machine" "example" {
  count = 3

  name                  = "vm-${count.index}"
  location              = azurerm_resource_group.example.location
  resource_group_name   = azurerm_resource_group.example.name
  network_interface_ids = [azurerm_network_interface.example[count.index].id]
  vm_size               = "Standard_DS1_v2"
}

In this example:

  • Terraform creates three virtual machines.

  • The count.index value (0, 1, 2) is used to generate unique names for each instance.

Key Features of Count:

  • Index-based: Resources are identified by their index.

  • Simple: Best suited for scenarios where all instances share similar configurations.

  • Limitations: Less flexible when dealing with heterogeneous resource configurations.


What is Terraform For_each?

The for_each meta-argument is more flexible and allows you to create resources based on a set, map, or list. Each instance is uniquely identified by a key rather than an index.

Example:

 terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=3.47.0"
    }
  }
}

#https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret
provider "azurerm" {
  features {} 
  client_id       = "00000000-0000-0000-0000-000000000000"
  client_secret   = "20000000-0000-0000-0000-000000000000"
  tenant_id       = "10000000-0000-0000-0000-000000000000"
  subscription_id = "20000000-0000-0000-0000-000000000000"
}
#variables are declared here
variable "resourcedetails" {
  type = map(object({
    name     = string
    location = string
    size     = string
    rg_name  = string
    vnet_name = string
    subnet_name = string
  }))
  default = {
    westus = {
      rg_name  = "westus-rg"  
      name     = "west-vm"
      location = "westus2"
      size     = "Standard_B2s"
      vnet_name = "west-vnet"
      subnet_name = "west-subnet"
    }
    eastus = {
      rg_name  = "eastus-rg"  
      name     = "east-vm"
      location = "eastus"
      size     = "Standard_B1s"
      vnet_name = "east-vnet"
      subnet_name = "east-subnet"
    }
  }
}


resource "azurerm_resource_group" "myrg" {
  for_each = var.resourcedetails

  name     = each.value.rg_name
  location = each.value.location
}

resource "azurerm_virtual_network" "myvnet" {
  for_each = var.resourcedetails
  name                = each.value.vnet_name
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.myrg[each.key].location
  resource_group_name = azurerm_resource_group.myrg[each.key].name
}

resource "azurerm_subnet" "mysubnet" {
  for_each = var.resourcedetails

  name                 = each.value.subnet_name
  address_prefixes     = ["10.0.0.0/24"]
  virtual_network_name = azurerm_virtual_network.myvnet[each.key].name
  resource_group_name  = azurerm_resource_group.myrg[each.key].name
}

resource "azurerm_network_interface" "mynic" {
  for_each = var.resourcedetails

  name                = "my-nic"  
  location            = azurerm_resource_group.myrg[each.key].location
  resource_group_name = azurerm_resource_group.myrg[each.key].name
  ip_configuration {
    name                          = "my-ip-config"
    subnet_id                     = azurerm_subnet.mysubnet[each.key].id
    private_ip_address_allocation = "Dynamic"
  }
}


resource "azurerm_virtual_machine" "vm" {
  for_each = var.resourcedetails

  name                  = each.value.name
  location            = azurerm_resource_group.myrg[each.key].location
  resource_group_name = azurerm_resource_group.myrg[each.key].name
  network_interface_ids = [azurerm_network_interface.mynic[each.key].id]
  vm_size               = each.value.size

  storage_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "16.04-LTS"
    version   = "latest"
  }

  storage_os_disk {
    name              = "${each.value.name}-osdisk"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Standard_LRS"
  }

  os_profile {
    computer_name  = each.value.name
    admin_username = "adminuser"
    admin_password = "Password1234!"
  }

  os_profile_linux_config {
    disable_password_authentication = false
  }


}

In this example:

  • Two virtual machines are created, each with a unique name and size

Key Features of For_each:

  • Key-based: Resources are identified by unique keys.

  • Flexible: Ideal for scenarios where instances require different configurations.

  • Dynamic: Can adapt to changes in the input set or map.


Key Differences Between Count and For_each

FeatureCountFor_each
Resource IdentificationIndex-based (count.index)Key-based (each.key)
Input TypeNumeric valueSet, map, or list
Use CaseHomogeneous resource configurationsHeterogeneous resource configurations
FlexibilityLimitedHigh
Dynamic UpdatesChallenging with changing countsAdapts easily to input changes

When to Use Count

  • All resources have similar configurations.

  • You know the exact number of resources required upfront.

  • Simpler scenarios where flexibility is not a priority.

Example Use Case:

Creating multiple identical storage accounts.

resource "azurerm_storage_account" "example" {
  count = 5

  name                     = "storage${count.index}"
  resource_group_name      = azurerm_resource_group.example.name
  location                 = azurerm_resource_group.example.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

When to Use For_each

  • Resources require unique configurations.

  • You need to manage resources based on dynamic or changing input data.

  • Resources need to be uniquely identified by a key.

Example Use Case:

Creating VMs with different configurations for a development and production environment.

resource "azurerm_virtual_machine" "example" {
  for_each = {
    dev  = "Standard_DS1_v2"
    prod = "Standard_DS2_v2"
  }

  name                  = each.key
  location              = azurerm_resource_group.example.location
  resource_group_name   = azurerm_resource_group.example.name
  vm_size               = each.value
}

Conclusion

Both count and for_each are powerful tools in Terraform for managing multiple resources, but they serve different purposes. Use count for simple, uniform resource creation and for_each for more complex, dynamic scenarios. Understanding their differences and use cases will help you write more efficient and maintainable Terraform configurations.

By leveraging the right construct for the right situation, you can optimize your Infrastructure as Code workflows and better manage your cloud resources.