top of page
  • Writer's pictureChristoffer Windahl Madsen

Terraform weekly tips, terraform best practices (7) - How to loop

Updated: Jun 26, 2023

Hi guys and welcome to yet another edition of the terraform weekly tips, terraform best practices spree! After spending the last week on Rhodos, I am now back with renewed enegy and ready with yet another tip. Todays topic as the title depicts regards the use of loops in Terraform, more specifically we will look at the following 2 concepts:

- count

- for-each

First lets discuss the general principal for both concepts and then I will break down each one. In Terraform we are blessed with a programmatic feature that makes our code way more scaleable and dynamic; The concept of using a loop. We can utilize this functionallity to create a dynamic resource definition, creating x-amount of resources. The loop takes in an array of resource definitions structured as either a map or list. The structure of the array will dependent entirely on the loop-type used; Lets define the specifics for each one:


The count concept is the most basic of the two. It is the more dureable of the two, as it does not require any predetermined information in order to execute. I will describe this in more detail under the for-each loop. Furthermore it takes in a tupple as its input and enumerates over each index. The index can be accessed directly by calling the built-in variable count.index which is 0-based.


The for-each concept is interesting for multiple reasons. First, its built-in as the switch for any dynamic-block definitions. It takes in a map and structures each of the objects with a key that must be unique in value. The key must be mapped to 1 specific attribute on each of the objects. Whats special is that we get to decide what the value defintion of each key shall be. With this possibility we can make code more readable, as return values of set objects will contain our decided keys. More in regards to the key values; They cannot consist of values that cannot be verified by Terraform before runtime. This basicly means that keys must be predefined in the configuration and not depent on information Terraform can only retrieve during resource creation.

Now, with all the above out of the way, lets look at some code! As always either copy code directly from below or fork the repo at ->

Lets start by defining a new file and add all the following boilerplate code:

terraform {
  required_providers {
    azurerm = {
        source = "hashicorp/azurerm"

provider "azurerm" {
  features {

Note that we will not define any provider credential data - Make sure to have an Azure CLI context first. If in any doubt visit post -> Terraform weekly tips (1) ( Example 1

Run terraform init (Make sure to point the terminal to the root folder location)

terraform init

We now want to create 3 different environments in terms of their most basic resource types. This will be for the environments: Dev, Test and Preprod and each will have:

  1. Resource groups

  2. Virtual Networks

  3. Subnets

Now, since we know that we want to create the above environment names, lets define a local map of environment names inside our locals block:

locals {
  environments = {
    "Dev" = {
        name = "Dev"
        location = "westeurope"
        ip_range_vnet = [""]
        ip_range_subnets = ["", "", "", "", "", "", "", "", ""]
    "Test" = {
        name = "Test"
        location = "westeurope"
        ip_range_vnet = [""]
        ip_range_subnets = ["", "", "", "", "", "", "", "", ""]
    "Preprod" = {
        name = "Preprod"
        location = "westeurope"
        ip_range_vnet = [""]
        ip_range_subnets = ["", "", "", "", "", "", "", "", ""]Rule = ""

Above, we have created a map with the overall name of 'environments' Which contains 3 keys which then, the underlying objects with different attributes.

With the above objects defined, lets create the resource groups first:

resource "azurerm_resource_group" "rg_objects" {
  for_each = local.environments
  name = "${}-rg" //Can also use each.key here
  location = each.value.location

run terraform apply

terraform apply --auto-approve=true


terraform apply azure resource groups

We simply created all 3 resource groups using the base environment name + the prefix of 'rg' Together with each of their locations. Notice how we simply parse the 'for-each' Argument on the left and parse it the 'local.environments' Variable defined earlier. To this, we access attributes on each object in the map by calling either 'each.key' Or its corrensponding values found in 'each.value.<attribute_name>'

Now, lets create the required Virtual Networks. Again, we will utilize our variable definition from earlier:

resource "azurerm_virtual_network" "vn_objects" {
  for_each = local.environments
  name = "${}-vnet"
  location = each.value.location
  resource_group_name = azurerm_resource_group.rg_objects[each.key].name
  address_space = each.value.ip_range

The interesting thing here is, we take the return array from the resource groups and called each key utilizing our existing key from the 'local.environments' Variable as the index. We always seek to use return values whenever possible as it helps Terraform build its dependency graph.

Run terraform apply

terraform apply --auto-approve=true


terraform apply azure vnets

Now, lets create the missing Subnets:

resource "azurerm_subnet" "subnet_objects_dev" {
  count = length(local.environments.Dev.ip_range_subnets)
  name = "${replace(azurerm_virtual_network.vn_objects["Dev"].name, "vnet", "subnet${count.index}")}"
  resource_group_name = azurerm_resource_group.rg_objects["Dev"].name
  virtual_network_name = azurerm_virtual_network.vn_objects["Dev"].name
  address_prefixes = [local.environments.Dev.ip_range_subnets[count.index]]

resource "azurerm_subnet" "subnet_objects_test" {
  count = length(local.environments.Test.ip_range_subnets)
  name = "${replace(azurerm_virtual_network.vn_objects["Test"].name, "vnet", "subnet${count.index}")}"
  resource_group_name = azurerm_resource_group.rg_objects["Test"].name
  virtual_network_name = azurerm_virtual_network.vn_objects["Test"].name
  address_prefixes = [local.environments.Test.ip_range_subnets[count.index]]

resource "azurerm_subnet" "subnet_objects_preprod" {
  count = length(local.environments.Preprod.ip_range_subnets)
  name = "${replace(azurerm_virtual_network.vn_objects["Preprod"].name, "vnet", "subnet${count.index}")}"
  resource_group_name = azurerm_resource_group.rg_objects["Preprod"].name
  virtual_network_name = azurerm_virtual_network.vn_objects["Preprod"].name
  address_prefixes = [local.environments.Preprod.ip_range_subnets[count.index]]

With the above configuration we define 3 new resource definitions, 1 for each of the environments. We use the count meta-argument to take advantage of the fact that we want exactly the length (each element) in the list for the attribute 'ip_ranges_subnet' Of each object inside the local variable 'environments'. We then simply plot the key name of each environment object to retrieve the specifc Virtual Machine name and Resource group name.

run terraform apply

terraform apply --auto-approve=true


terraform apply azure subnets

How it looks in Azure:

view of dev vnet with subnets
view of test vnet with subnets
view of preprod vnet with subnets

That was all for today folks, thank you so much for reading along. I will showcase more complex loop structures in later posts, as there are things we can do to make the above code even more clean.



Want to learn more about Terraform? Click here -> terraform (

Want to learn more about other cool stuff like Automation or Powershell -> powershell ( / automation (

45 views0 comments


Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page