Azure – ARM templates

By | 11/06/2025

In this post, we’ll see what ARM templates are, why you should use them and how they are structured.
But first of all, what are ARM templates?
An ARM template is a JSON file that declaratively defines the infrastructure and configuration for our Azure resources. It enables us to automate the deployment process by specifying every detail of your resources in code, which helps manage dependencies and ensure that resources are provisioned in the correct order. By using ARM templates, we can maintain our infrastructure in source control, allowing for version tracking and consistent deployments across different environments. Ultimately, ARM templates reduce manual errors and provide a repeatable, scalable, and auditable approach to managing our cloud resources.

An ARM template is organized into several key sections that together create a clear and maintainable infrastructure code:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {},
  "variables": {},
  "functions": [],
  "resources": [],
  "outputs": {}
}

It begins with the $schema element, which points to the JSON schema defining the structure of the template, followed by the contentVersion that indicates the version of the template. Next, the parameters section lets us pass values at deployment time, adding flexibility to our template, while the variables section allows us to define reusable values throughout. Functions are User-defined functions that can be used throughout the template.
The heart of the template is the resources section, where we declare the Azure resources we want to deploy, and finally, the outputs section provides return values like resource IDs or connection strings after deployment.

Let’s start some examples of ARM templates:

ARM – create a Topic (Service Bus) called testARMtopic

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "serviceBusNamespace": {
      "type": "string",
      "defaultValue": "DamianoServiceBusNamespace",
      "metadata": {
        "description": "Name of the Service Bus namespace"
      }
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]",
      "metadata": {
        "description": "Location for all resources"
      }
    }
  },
  "resources": [
    {
      "type": "Microsoft.ServiceBus/namespaces",
      "apiVersion": "2021-11-01",
      "name": "[parameters('serviceBusNamespace')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard"
      },
      "properties": {}
    },
    {
      "type": "Microsoft.ServiceBus/namespaces/topics",
      "apiVersion": "2021-11-01",
      "name": "[concat(parameters('serviceBusNamespace'), '/testARMtopic')]",
      "dependsOn": [
        "[resourceId('Microsoft.ServiceBus/namespaces', parameters('serviceBusNamespace'))]"
      ],
      "properties": {}
    }
  ],
  "outputs": {
    "serviceBusEndpoint": {
      "type": "string",
      "value": "[concat('sb://', parameters('serviceBusNamespace'), '.servicebus.windows.net/')]"
    },
    "serviceBusNamespace": {
      "type": "string",
      "value": "[parameters('serviceBusNamespace')]"
    },
    "topicName": {
      "type": "string",
      "value": "testARMtopic"
    }
  }
}


ARM – create a CosmosDb database called TestCosmos with damianodbaccount as account name

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]",
      "metadata": {
        "description": "Location for the Cosmos DB account."
      }
    }
  },
  "variables": {
    "accountName": "damianodbaccount",
    "databaseName": "TestCosmos",
    "defaultConsistencyLevel": "Session"
  },
  "resources": [
    {
      "type": "Microsoft.DocumentDB/databaseAccounts",
      "apiVersion": "2021-10-15",
      "name": "[variables('accountName')]",
      "location": "[parameters('location')]",
      "kind": "GlobalDocumentDB",
      "properties": {
        "consistencyPolicy": {
          "defaultConsistencyLevel": "[variables('defaultConsistencyLevel')]"
        },
        "locations": [
          {
            "locationName": "[parameters('location')]",
            "failoverPriority": 0,
            "isZoneRedundant": false
          }
        ],
        "databaseAccountOfferType": "Standard",
        "enableAutomaticFailover": false,
        "enableMultipleWriteLocations": false
      }
    },
    {
      "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases",
      "apiVersion": "2021-10-15",
      "name": "[concat(variables('accountName'), '/', variables('databaseName'))]",
      "dependsOn": [
        "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('accountName'))]"
      ],
      "properties": {
        "resource": {
          "id": "[variables('databaseName')]"
        }
      }
    }
  ],
  "outputs": {
    "cosmosDbAccountName": {
      "type": "string",
      "value": "[variables('accountName')]"
    },
    "cosmosDbDatabaseName": {
      "type": "string",
      "value": "[variables('databaseName')]"
    },
    "cosmosDbEndpoint": {
      "type": "string",
      "value": "[reference(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('accountName'))).documentEndpoint]"
    }
  }
}


Now that we have the ARM templates, we can deploy them in Azure using one of these methods:

  • Azure Portal:
    Upload our ARM template through the portal by navigating to our target resource group (or at the subscription level if creating resource groups), selecting “Deploy a custom template,” and then following the guided steps. This method provides a user-friendly interface with real-time validation and progress updates.
  • Azure CLI:
    Use the az deployment group create command for resource group–scoped deployments or az deployment sub create for subscription-level deployments. We supply the path to our template (and parameters file, if necessary), and the CLI handles the deployment process while providing feedback in the terminal.
  • Azure PowerShell:
    Run the deployment using cmdlets like New-AzResourceGroupDeployment or New-AzSubscriptionDeployment. These commands accept our ARM template file and parameters, and they execute the deployment, letting us monitor progress and view detailed output.

For this post, I am going to use the Azure Portal to deploy these two ARM templates that I will save in two different files called FileARM1.yml and FileARM2.yml.
Let’s deploy the ARM templates:

[FileARM1.yml]create a Topic (Service Bus) called testARMtopic
Select “Deploy a custom template”:


Click on “Build your own template in the editor”:


Load the yml file:


Fill in all parameters and click on “Review + create”:


Wait for the deployment:


When it is finished, go to the “testARM” to check the creation of the Service bus and the creation of the topic called “testarmtopic”:


[FileARM2.yml]create a CosmosDb called TestCosmos with damianodbaccount as account name
Select “Deploy a custom template”:


Load the yml file:


Fill in all parameters and click on “Review + create”:


Wait for the deployment:


When it is finished, go to the “testARM” to check the creation of damianodbaccount as account and the the creation of the database called “TestComos”:



The last thing I would like to speak about ARM templates is Externalizing Sensitive Data.
Externalizing sensitive data is crucial because it minimizes the risk of exposing secrets such as passwords, keys, or certificates directly within our infrastructure code.
In fact, if we keep these values external to our ARM templates, we reduce the possibility of accidentally committing sensitive information to version control or having it exposed in deployment logs. This practice enhances our security posture and ensures that your deployments remain compliant with industry best practices.
There are several ways to achieve this separation.
One common method is to use secure parameter files, where sensitive values are marked as secure strings and stored separately from our main template. These secure parameters are then injected into the template during deployment, ensuring that the actual secrets remain hidden. Another effective approach is integrating Azure Key Vault directly with our ARM template. This method allows the deployment engine to fetch secrets from a centralized, highly secure vault, providing tighter control over access and improved auditing capabilities. Both approaches help maintain a cleaner and more secure deployment process by decoupling sensitive information from your infrastructure code.


Below is a small ARM template example that externalizes a sensitive value. In this case, the template deploys a Virtual Machine using an admin password that isn’t hardcoded in the template but is instead referenced from an Azure Key Vault. This approach ensures that our sensitive data remains secure and separate from our infrastructure code.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "targetScope": "resourceGroup",
  "parameters": {
    "vmName": {
      "type": "string",
      "defaultValue": "myVM",
      "metadata": {
        "description": "Name of the Virtual Machine."
      }
    },
    "adminUsername": {
      "type": "string",
      "defaultValue": "azureuser",
      "metadata": {
        "description": "Administrator username for the VM."
      }
    },
    "adminPassword": {
      "type": "securestring",
      "metadata": {
        "description": "The administrator password for the VM, retrieved from an external Key Vault."
      },
      "reference": {
        "keyVault": {
          "id": "[resourceId('Microsoft.KeyVault/vaults', 'myKeyVaultName')]"
        },
        "secretName": "vmAdminPassword"
      }
    }
  },
  "resources": [
    {
      "type": "Microsoft.Compute/virtualMachines",
      "apiVersion": "2021-03-01",
      "name": "[parameters('vmName')]",
      "location": "[resourceGroup().location]",
      "properties": {
        "hardwareProfile": {
          "vmSize": "Standard_DS1_v2"
        },
        "osProfile": {
          "computerName": "[parameters('vmName')]",
          "adminUsername": "[parameters('adminUsername')]",
          "adminPassword": "[parameters('adminPassword')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "MicrosoftWindowsServer",
            "offer": "WindowsServer",
            "sku": "2019-Datacenter",
            "version": "latest"
          },
          "osDisk": {
            "createOption": "FromImage"
          }
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(parameters('vmName'),'-nic'))]"
            }
          ]
        }
      }
    }
  ],
  "outputs": {}
}

In this template, the adminPassword parameter is defined as a secure string.
Its value is not provided directly; instead, it uses a Key Vault reference to fetch the secret named vmAdminPassword from a Key Vault called myKeyVaultName. When we deploy this template, the Azure deployment engine retrieves the actual password securely from our Key Vault, keeping your sensitive data external and safe.



Leave a Reply

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