Menu Close

Use For loops in Bicep

In this post, I want to focus on a specific function in Bicep templates: for loops. Loops can help with flexibility, reusability and minimizing duplicate code. How do you make the most of them? As I got some questions on this subject, this blog post will walk through different kinds of loops. Read on to find out  how to use for loops in Bicep.

Note: If you use the AMP version of this post, the code blocks don’t work. Until I can fix that, please open this link to go to the correct version of the post

Loops in ARM templates

A for loop in an ARM template is… Let’s call it a challenge.
You would use a copyloop. The syntax can take a bit of getting used to, as it is not always intuitive. The possibilities are all there and are very extensive. But Bicep has made them all a little bit accesable, which shortens the way to more advanced features. Let’s see how we can handle for loops in Bicep.

A numbered loop

The first use of a loop we will look at is a numbered one. You define the number of resources you want to create.

First, let’s see how one storage account would be created. The bicep code for that is this:

This code deploys a single storage account:

So let’s say we want to create multiple storage accounts. We change the resource into a numbered loop.
The loop would look like this:

So as you can see, the first line has changed to include a for-syntax and a range. The loop is created with the syntax [for i in range(0,2):. The value i represents the time the loop has run. So in this case, the first time the loop runs i will be 0, the second time if will be 1.

When we look at range, there are two numbers defined. The first number is the starting number, the second number is the amount of times the loop will run. So for this loop, it would run twice, starting with the number 0.
In a numbered loop you always need to use the i value in the name of the resource, as resource names need to be unique. The i behaves as a variable, so you can call it with the syntax {$i}, as you can see in the example.

The extra storage accounts that are deployed look like this:

increase the index number

While starting a loop at zero makes sense from a programming perspective, naming conventions often start with one. You often also want to append one or multiple leading zeros, so the resources will be in the correct order if you have more than one.

We can make the loop start at any number by changing the first number in the range. As we already have 0 and 1, lets start this loop at 2. We will create three storage accounts this time.
To create a better naming convention, we can use the ARM functionality padleft.

This would result in this:

This code creates these 3 new storage accounts:

A loop based on an array of strings

Let’s look at a different kind of loop: one that is based on an array. Often that array will be a parameter or a variable, as that gives flexibility. Let’s look at an example for our storage account:

So as you see, we use the syntax [for storageAccountName in StorageAccountNames:. With this syntax, we create a new variable called storageAccountName. As the loop processes, it will use all the values in the array. So in this case, it will create two storage accounts named 4besarraystorage and 4besrandomstorageaccountname.

A loop based on an array of objects

A loop with strings is useful, but what if we want to change more? For example, you might need some more flexibility on the SKU, as not every storage account might need the same one.
To reach this goal, we can use objects with different properties in the array and use those properties. This sounds more complicated than it is. Let’s look at a new example:

So as you see, in the storageAccounts array we now define objects with different values in them. The loop runs in a very similar way, but you refer to the values in the object by putting them after a dot. This loop deploys the two storage accounts with their own names and their own sku.

You can take this logic as far as you want, with the most extreme option to more or less copy all properties to the array with the objects. I have added some properties to show how it works in the following example:

As you can see, this gives you more flexibility, but it makes the parameters more complicated. The good thing is that the object in the template itself becomes more simple, as the layered properties can be handled in the parameter. There is no right or wrong way to go, it is really up to you to think about what kind of flexibility you need and what fits your needs best.

A loop with numbers and an array

There might be cases where you want to combine an array-loop and a numbered loop. For example if you want to set the properties in the array, but use the index number for the naming convention. You are able to use both a number variable and the arrays variable in a loop like this:

As you see, you define both storageAccountlocation and i to and then can use them both in the resource properties.
This template creates two storage accounts in two locations.

Change the index value

As with just the numbered range, you might want to change the index number to a higher value. To do that, you can add the logic directly in the property, or in a variable above the loop if you need to use the value more than once. The name in the above storage account loop would look like this

name: '4besarrayandnr${i+1}'

Use a loop as a reference

When you create resources in a loop, they might refer to each other. A good example is how a nic can be linked to a public IP address. If you have a loop, how do you create the reference?
To refer to a resource in a loop, you can use the syntax resourceobject[i].
That sounds vague, so let’s see an example. Here I create 2 public IPs and then add their ID’s to 2 Nics

Note: This example needs a subnet to refer to. I will share a complete overview of all the code including the vnet in the end.

So this is how you can reference by using the i variable. It will go in the same order as the first loop.

Using a loop for properties

In a lot of resources it can be useful to use a loop for properties. All the different ways to use loops we just worked through can be used to go through properties of a resource.
Let’s look at one of the very usable examples of this option: the security rules for a network security group.

In this context, you have multiple options. One of them is to is to have an array with all the properties in a parameter and refer that directly.  This is very transparent, but the parameter file can become complicated. By losing some flexibility, you are able to make your template parameters a little bit less complicated. Look at how that looks:

In this case, the only flexibility you get is on the name and priority (as they need to be unique for every rule) and the destination port range. You can take this as far as you want, up to defining every property in the parameter.

Using loops in variables

We can use loops in variables as well. So if we look at the example with the NSG rules, we could move the logic of creating the security rules to a variable. This can be very useful if you need to use the value more than once or if you want to combine it with other bicep functions.

Using loops with modules

If you use modules, you can still use loops the same way as you would do with a resource. Let’s get an example again. I am using this file as my module. The loop would look like this:

So as you see, it works very similar to a resource loop

Output loops

The last thing that we can loop through, is the output. If we want for example the id’s of the storage accounts we created in the last example to be available after deploying the template, we need to create an output. We need to create another loop to make all value’s available.

The normal output syntax to get the id for a single storage account would be like this

output staid string = sta.id

But we want to have the id of more than one value. You can’t refer to the resource loop directly, but have to create a separate loop. For the storage accounts we just deployed trough a module, that could look like this:

output stas array = [for i in range(0,3): {
  Id: storageAccountsFromModule[i].outputs.staid
}]

The output will be an array of the values that you asked for. In this case the ID’s of the 3 storage accounts:

Conclusion

So these are some examples on how you can use for loops in Bicep. I think the syntax of for loops are one of the great improvements Bicep has brought us over ARM templates. If you want to see all the code that I have used in this post, you can find it HERE.
If you have any questions, leave them in the comments!

10 Comments

  1. Dave Meijer

    I dont see the code …

    For example : First, let’s see how one storage account would be created. The bicep code for that is this: ??

      • Barbara

        Hi Dave,
        Thank you for letting me know. It is a problem with the mobile AMP version of the site that doesn’t show the code blocks. I have added a link to the working version to the post.

  2. Nick

    Hi Barbara.
    Thanks a lot for very useful article. But I have a problem. Looks like I need nested loops. Is it possible?
    For example, I have two arrays:
    var aNum = [
    ’01’
    ’02’
    ]
    var aName = [
    ‘one’
    ‘two’
    ]

    I need the following array aResult
    var aResult = [
    ‘num01-one’
    ‘num01-two’
    ‘num02-one’
    ‘num02-two’
    ]

    Could you suggest me the solution.

  3. Sam

    Hi Barbara, firstly thanks for all the great content you’ve done over the years, it’s really helped. Also I’m currently trying to combine loops with conditions, for example only applying a certificate to particular front door endpoints. I can’t see a way to do this like we could in arm.

    • AlexN

      Conditional loops can be achieved simply by putting the conditional expression in after the loop statement like this:
      resource ‘abc’ = [for x in xs: if(contains(x, ‘property’)) {
      name: …
      etc…
      }]

      Lets say I want to deploy a vnet with subnets but not every subnet will have a network security group for example (bad idea I know, but it’s just an illustration). For this example I could do this:

      var subnets = [
      {
      ‘subnetName’: ‘subnet1’
      ‘subnetPrefix’: ‘10.10.1.0/24’
      ‘nsgName’: ‘nsgsubnet1’
      }
      {
      ‘subnetName’: ‘subnet2’
      ‘subnetPrefix’: ‘10.10.2.0/24’
      }
      {
      ‘subnetName’: ‘subnet3’
      ‘subnetPrefix’: ‘10.10.3.0/24’
      ‘nsgName’: ‘nsgsubnet3’
      }
      ]

      module nsgs ‘networkSecurityGroup.bicep’ = [for (subnet, i) in subnets: if(contains(subnet, ‘nsgName’)) {
      name: ‘deploy-nsg-${i}’
      params: {
      nsgName: subnet.nsgName
      }
      }]

      This template will only deploy a network security group if the property nsgName exists in the object it’s currently on in the loop.

  4. Anna

    Hello! I can’t seem to find an answer to this in documentation. working with your naming convention and tweaking it to have an array of functions instead of string. in the vars, it expects something other than a substring now that the param changed to array. what’s the array equivalent of substring? or should I do a convert from array to string per array value?

    (the rationale behind this idea is if a team want to deploy a full landing zone then a subnet should be created for every “function” – front-end, back-end, etc). Perhaps I am not using the “function” of the naming convention module in the intended way in this case, I may have to revisit this, but the question is mostly what is an article in an array called that can be used in the naming variables for functionShort instead of substring?

    Thank you and thanks for the amazing resources

    • Barbara

      Hi Anna,
      I don’t fully understand what you are trying to do, is it maybe possible to show me the code of the bicep template you are creating?

Leave a Reply

Your email address will not be published. Required fields are marked *