Modularisation and Re-Use with Nested ARM Templates
Most example ARM templates use a single JSON file to contain all of the deployment details in a single file. This works fine for smaller deployments, but once you start doing larger deployments, working in teams, or wanting to re-use parts of your deployment templates then you really need to start looking at nested templates.
Nested templates describes the process of calling an ARM template from inside another. In this way you can separate your logic into multiple files, call these files as required and pass parameters and results between templates. Nesting has been supported in ARM since the beginning and these nested templates are just treated like another resource.
Modularisation
Splitting your template over multiple files can help with readability or organisation, but one of the big benefits is modularisation and re-usability. If there are resources you regularly deploy in a specific configuration you can wrap them up in their own template and re-use them as required through out your different solutions.
If you take a look at the Microsoft Best Practices for ARM Templates it contains the diagram below with a recommended approach to organising your templates.
This technique allows for both re-usable templates shared between deployments, but also providing a shared resource template, which contains resources that are shared between multiple deployments, for example a virtual network that is deployed and managed by a central function and referenced by this shared resource template.
Nesting Templates
There are two ways to include a nested template:
- Inline in your main template
- As separate JSON files called from your main template.
Inline Nesting
Inline nesting is really not something that I would recommend doing except where you have a very simple set of resources in your inline template. Inline testing essentially involves having a second template included inside your first like below:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"StorageAccountName1": {
"type": "string"
},
"StorageAccountName2": {
"type": "string"
}
},
"variables": {},
"resources": [
{
"apiVersion": "2017-05-10",
"name": "nestedTemplate",
"type": "Microsoft.Resources/deployments",
"resourceGroup": "crossSubscriptionDeployment",
"subscriptionId": "623d50f1-4fa8-4e46-a967-a9214aed43ab",
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('StorageAccountName2')]",
"apiVersion": "2015-06-15",
"location": "West US",
"properties": {
"accountType": "Standard_LRS"
}
}
]
},
"parameters": {}
}
},
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('StorageAccountName1')]",
"apiVersion": "2015-06-15",
"location": "West US",
"properties": {
"accountType": "Standard_LRS"
}
}
]
}
In this example we have a resource of type Microsoft.Resources/deployments, this is the resource we use to call a nested template. This resource has a property of “template” and inside this we are putting the whole second template. As you can see this has no benefit for the complexity of the file or for modularising our work. The only reason you would do this is if something you are doing requires a nested template and you only have a few resources in that template. In this example, we are deploying some resources into a second resource group and subscription. Cross RG and subscription deployments require the use of a nested template.
Nested Files
Nested files are the way I recommend you follow. Here we will reference a second JSON file from our main template. The nested template, the one you are calling from the main template, is no different from a normal template, you just define your resources as normal. Any information you require from the main template should be setup as parameters.
In your main template you will again use a Microsoft.Resources/deployments resource, but instead of the template property we will use templateLink:
"variables": {
"templatelink": "https://raw.githubusercontent.com/sam-cogan/Demos/master/DemoExamples/newStorageAccount.json"
},
"resources": [
{
"apiVersion": "2015-01-01",
"name": "nestedTemplate",
"type": "Microsoft.Resources/deployments",
"properties": {
"mode": "incremental",
"templateLink": {
"uri": "[variables('templatelink')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"StorageAccountName": {
"value": "[parameters('StorageAccountName')]"
}
}
}
}
]
As you can see, we are using the templateLink property to pass in a link to my second template file. One thing to be aware of here, the second template file needs to be reachable at deployment time, so this is running inside Azure. As such it can’t just exist on my local machine, it needs to be hosted somewhere accessible. In this example I am using GitHub, but you can use any http service. If you want to keep the template secure the best option is to use Azure Storage with a SAS token.
You can also see here that I am passing in a parameter (StorageAccountName), this will be received by my nested template as a parameter and used in the standard way. Obviously as you are passing the parameters through from our main template, we need to make sure that ll parameter data we need exists in our main template or is in turn passed into it as a parameter,
That’s all there is to it. When I want to deploy the template I just run a new-azurermresourcegroupdeployment from PowerShell, or whatever method you usually run the main template and provide parameters, and it will handle running the nested templates.
Outputs
All ARM templates have an outputs section. In single file templates this is mainly used to pass values back to the caller (PowerShell etc.). With nested templates we can use this to pass values back to the calling top level template, which can then be used in this template or even passed to other nested templates.
Creating an output is the same as in a normal template, here we are passing the full details of the storage account created in the nested template.
"outputs": {
"storageAccountInfo": {
"value": "[reference(concat('Microsoft.Storage/storageAccounts/', parameters('StorageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0])]",
"type": "object"
}
in the main template we would then use this by referencing the nested template step and variable name:
"storageAccountInfo": { "value": "[reference('nestedTemplate').outputs.storageAccountInfo.value]" },
Next Steps
Using nested templates you can become much more organised with your templates and start re-using your common templates through your different projects, even providing a library of building blocks that you can share with colleagues
If you are building complex sets of templates I would very much recommend reading Microsoft’s best practice guidance on designing complex templates.
The examples used in this article plus more can be found in my GitHub Repo.