The Power of ARM Templates

As part of the project management process for a web system you will usually see code stored in a version control system such as Git. From that code we have the capability to deploy directly to Azure resources and manage multiple environments. The same way we can use the same codebase and deploy to several environments we can use Azure Resource Manager (ARM) templates to give us the same capability to deploy the shape and configurations for our Azure Resources.

When you deploy an ARM template the resource manager will convert the json template to a series of REST API operations as the example shows below.

"resources": [
  {
    "type": "Microsoft.Storage/storageAccounts",
    "apiVersion": "2019-04-01",
    "name": "mystorageaccount",
    "location": "westus",
    "sku": {
      "name": "Standard_LRS"
    },
    "kind": "StorageV2",
    "properties": {}
  }
]
PUT
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/mystorageaccount?api-version=2019-04-01
REQUEST BODY
{
  "location": "westus",
  "sku": {
    "name": "Standard_LRS"
  },
  "kind": "StorageV2",
  "properties": {}
}

Every ARM template follows a standard structure.

A list of available schemas provided by Microsoft are provided by Microsoft. A schema must be defined so that when you deploy a template it will know the shape of your json and how to handle it to create your resource. You can think of it similarly to targeting a specific an api version when making a REST call.


The contentVersion is a tag for you to be able to track this deployment.


The parameters is where you set parameters than can be configured per environment with a parameters.json file.


Variables are values to be used in the template.


Resources is where we define our resources to be deployed and managed by the template. This can be one or many but you can think of it as a resource and it's dependencies. For example an Azure Function may have a function but also have app insights, storage, and a service plan.

Outputs can be used for template chaining.

To see an example of how a full ARM template would look for an Azure Function.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "functionAppName": {
      "type": "string",
      "defaultValue": "[format('func-{0}', uniqueString(resourceGroup().id))]",
      "metadata": {
        "description": "The name of the Azure Function app."
      }
    },
    "storageAccountType": {
      "type": "string",
      "defaultValue": "Standard_LRS",
      "allowedValues": [
        "Standard_LRS",
        "Standard_GRS",
        "Standard_RAGRS"
      ],
      "metadata": {
        "description": "Storage Account type"
      }
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]",
      "metadata": {
        "description": "Location for all resources."
      }
    },
    "functionWorkerRuntime": {
      "type": "string",
      "defaultValue": "node",
      "allowedValues": [
        "dotnet",
        "node",
        "python",
        "java"
      ],
      "metadata": {
        "description": "The language worker runtime to load in the function app."
      }
    },
    "packageUri": {
      "type": "string",
      "metadata": {
        "description": "The zip content url."
      }
    }
  },
  "variables": {
    "hostingPlanName": "[parameters('functionAppName')]",
    "applicationInsightsName": "[parameters('functionAppName')]",
    "storageAccountName": "[format('{0}azfunctions', uniqueString(resourceGroup().id))]"
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2021-09-01",
      "name": "[variables('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "[parameters('storageAccountType')]"
      },
      "kind": "Storage"
    },
    {
      "type": "Microsoft.Web/serverfarms",
      "apiVersion": "2022-03-01",
      "name": "[variables('hostingPlanName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Y1",
        "tier": "Dynamic",
        "size": "Y1",
        "family": "Y"
      },
      "properties": {
        "computeMode": "Dynamic"
      }
    },
    {
      "type": "Microsoft.Insights/components",
      "apiVersion": "2020-02-02",
      "name": "[variables('applicationInsightsName')]",
      "location": "[parameters('location')]",
      "tags": {
        "[format('hidden-link:{0}', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource"
      },
      "properties": {
        "Application_Type": "web"
      },
      "kind": "web"
    },
    {
      "type": "Microsoft.Web/sites",
      "apiVersion": "2022-03-01",
      "name": "[parameters('functionAppName')]",
      "location": "[parameters('location')]",
      "kind": "functionapp",
      "properties": {
        "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
        "siteConfig": {
          "appSettings": [
            {
              "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
              "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2015-05-01').InstrumentationKey]"
            },
            {
              "name": "AzureWebJobsStorage",
              "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2021-09-01').keys[0].value)]"
            },
            {
              "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
              "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2021-09-01').keys[0].value)]"
            },
            {
              "name": "WEBSITE_CONTENTSHARE",
              "value": "[toLower(parameters('functionAppName'))]"
            },
            {
              "name": "FUNCTIONS_EXTENSION_VERSION",
              "value": "~4"
            },
            {
              "name": "FUNCTIONS_WORKER_RUNTIME",
              "value": "[parameters('functionWorkerRuntime')]"
            },
            {
              "name": "WEBSITE_NODE_DEFAULT_VERSION",
              "value": "~14"
            },
            {
              "name": "WEBSITE_RUN_FROM_PACKAGE",
              "value": "1"
            }
          ]
        }
      },
      "dependsOn": [
        "[resourceId('Microsoft.Insights/components', 			variables('applicationInsightsName'))]",
        "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
        "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
      ]
    },
    {
      "type": "Microsoft.Web/sites/extensions",
      "apiVersion": "2022-03-01",
      "name": "[format('{0}/{1}', parameters('functionAppName'), 'zipdeploy')]",
      "properties": {
        "packageUri": "[parameters('packageUri')]"
      },
      "dependsOn": [
        "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]"
      ]
    }
  ]
}

Deploying ARM templates are done the same way that you would deploy code to Azure using Visual Studio or Powershell so I won't dive in to that here but hopefully now you know the power that Azure Resource Manager templates can provide and will hopefully leverage those as I have in order to keep my infrastructure source controlled and deployments fully automated.

Drew Demechko

Drew Demechko

Dallas, TX