5 ways to up your ARM template game

Posted by Matthew Bradley

Arm Templating

So, we’ve all been at a party where you’ve only been able to keep up with the conversation for so long because the discussion starts to go deeper and deeper into JSON and advanced ARM templates.

Yep, we’ve all been there!

OK, so that might say more about the type of parties that I go to than anything else.  But you never know when you’ll be in a situation just like that, so this is probably worth a read anyway.

If you’ve ever dreamt of being elbow deep in ARM then this will be a good start…

1. Set Custom Names For All Resources

I’m starting with an easy one so don’t be put off with this sounding quite simple, the rest does get more interesting!  Although this isn’t an enthralling topic to talk about, it will help you so much when it comes to managing your deployments. If you don’t specify a name for certain objects then Azure will name them for you, which means a random string at the end of the name. Setting these as variables allows you to add the VM (or whatever other resource) name to the start of the object and then add a sensible suffix to describe what the object is. Below is an example for NIC, public IP and OS disk:

"NicName": "[concat(parameters('vmName'), '-nic')]",
"pipName": "[concat(parameters('vmName'), '-pip')]",
"osDisk": "[concat(parameters('vmName'), '-osDisk')]"

Doesn’t that just look prettier?  Trust me, if you don’t currently do this, start doing it immediately. You will thank me later…

Adding comments and tags when describing resources will also make everyone happy! Comments make the template easier to read and tags give far more control over resources once they are built.

2. Make Use Of Conditional Resources

Conditional resources in ARM templates give you a way to add conditions and logic within your deployment.  An example I saw of this earlier in the week was to only create a public IP address for a VM if there was a DNS label assigned to it.  If there is no DNS label then it didn’t need a public IP address, so it didn’t add one.  This allowed for a far more generic template that could be easily configured for deployments to multiple solutions.

Conditional resources use functions (if ‘x’ then ‘y’ else ‘z’, greater than, less than etc) so familiarise yourself with them. There are too many options to even begin describing them all here so have a read through the reference link and have a play around with their capabilities.

3. Use ‘Copy’ Rather Than Duplicating Code

The copy command is used to duplicate resources or objects and will loop through the code to make an array at deployment.

Copy can be used either at the resource level (VM, Database, vNET etc) or at the object level (disks, NIC, subnet…) within a resource in an ARM template.  If you are using Terraform then you can only currently use the copy command at the resource level and not for objects! So please keep that in mind.

The format used for copy differs depending on whether it is being performed at the resource level or object level; at the resource level is simpler to use so we’ll start there.  Add an object block to the description of the resource to say you want to copy it and then how many times you want it to copy:

"resources": [
        "type": "Microsoft.Network/virtualNetworks",
        "apiVersion": "2017-06-01",
        "name": "[concat('myResourcePrefix-', copyIndex(1))]",
        "location": "[resourceGroup().location]",
        "properties": {},
        "copy": {
            "name": "resourcecopy",
            "count": 4

Using copy on an object is slightly different in its layout. The below will create a number of data disks equal to the value of the parameter called ‘numberofDataDisks’.  This example will even add a number to the end of the disk name, to match the iteration that the loop is on.

"copy": [
        "name": "dataDisks",
        "count": "[parameters('numberofDataDisks')]",
        "input": {
            "name": "[concat(parameters('vmName'), '-dataDisk', padLeft(copyIndex('dataDisks'), 2, '0'))]",
            "lun": "[copyIndex('dataDisks')]",
            "createOption": "Empty",
            "diskSizeGB": 1023

Just imagine creating a VM with 32 data disks without being able to create a loop for them! That would be a lot of lines of JSON and if you were to copy and paste then you’d probably get dizzy just counting them to make sure that you had the correct number.  No thank you – give me copy over that any day!

4. Learn All Parameter Types

The most common parameter types that are used are ‘string’, ‘securestring’ and ‘int’, however there are some other gems that can be used to improve the functionality of your templates.

There are several other parameter types that can be used, however here we’ll be talking about arrays.

As Availability Zones are still only in preview and in a small number of locations. You want a way to deploy resources across multiple regions – you know, in case a data centre has issues due to it being a bit warm outside.

Array parameter types can be used with copy to loop through and create resources in multiple locations:

"parameters": {
    "location": {
        "type": "array",
        "defaultValue": [

"resources": [
        "name": "[concat('vmname', parameters('location')[copyIndex()])]",
        "copy": {
            "name": "VM copy",
            "count": "[length(parameters('location'))]"

5. Understand Nested Templates

If you’re a fan of normal ARM templates then hold on to your seats as this is about to get wild!

Nested templates are templates files referenced from within templates and add an extra dimension to your deployment.

The amazing thing about nested templates is that they are so simple to use! You just create a deployment resource type and link it to the file that you want to nest.

Which brings us to ‘Xzibit A‘ (see what I did there…):

"apiVersion": "2017-05-10",
"name": "linkedTemplate",
"type": "Microsoft.Resources/deployments",
"properties": {
    "mode": "incremental",
    "templateLink": {
        "uri": "[uri(deployment().properties.templateLink.uri, 'nestedtemplate.json')]",
        "contentVersion": ""

There are different variations for how you reference the nested file, however the above will use the relative path of the file that you are in. This makes it more portable without having to hard-code URLs into the template.

And yes you can nest a template within a nested template but let’s not be silly just for the sake of it.  I know it’s Friday, but still…

For huge deployments then you may have a legitimate reason for this but you should never over-complicate your template files just to use a feature that may not be needed!

That’s it. If you have any questions about this then drop me a message or leave a comment.

Back to Blog