Azure Resource Manager (ARM) templates let you define your infrastructure as code and deploy it consistently across environments. If you are still clicking through the Azure portal to provision resources, this is worth your time.
What ARM templates are
An ARM template is a JSON file that declares the Azure resources you want to deploy and their configuration. You describe the desired end state and Azure figures out how to get there. The template is idempotent: run it once, run it ten times, you get the same result.
Microsoft introduced ARM in 2014 as the replacement for the older Azure Service Management API. Every resource you create in Azure, from a VM to a storage account to an App Service, has an ARM representation. Templates are how you automate that at scale.
Template structure
Every ARM template has the same top-level structure. The $schema field tells tooling which version of the template language you are using. contentVersion is a free-form string for your own versioning. parameters are inputs that let you customise a deployment. variables compute values you reuse across the template. resources is the array of things you actually want to deploy. outputs return values after deployment completes.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": { ... },
"variables": { ... },
"resources": [ ... ],
"outputs": { ... }
}
A practical example
Say you want to deploy a storage account. The resource block looks like this:
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[parameters('storageAccountName')]",
"location": "[resourceGroup().location]",
"sku": { "name": "Standard_LRS" },
"kind": "StorageV2"
}
The [parameters()] and [resourceGroup()] syntax are ARM template functions. They evaluate at deploy time. ARM has around 50 built-in functions covering string manipulation, array operations, resource references, and conditionals.
Parameters and parameter files
Hard-coding values in templates defeats the purpose. Parameters let you pass in environment-specific values at deploy time. You define a parameter with a type, optional default, and optional allowed values list. Then you pass actual values using a separate parameters file.
This pattern means one template works across dev, staging, and production. The template defines the shape of your infrastructure. The parameters file defines the specifics for each environment. Keep templates in source control. Keep parameters files in source control. Never hard-code secrets: use Azure Key Vault references in parameter files to pull secrets at deploy time.
Deploying a template
The simplest way is the Azure CLI:
az deployment group create --resource-group myRG --template-file main.json --parameters @dev.parameters.json
This validates the template before deploying. If there is a syntax error or an invalid resource property, it tells you before any resources are created. Run with --what-if to preview the changes without actually deploying. That flag is genuinely useful before touching production.
ARM templates vs Bicep
Bicep is a domain-specific language that compiles to ARM JSON. It has cleaner syntax, better type safety, and much less verbosity. If you are starting fresh in 2024, Bicep is the way to go. ARM JSON is still the underlying format and everything here applies conceptually to Bicep. But you write a lot less to achieve the same thing. Microsoft treats Bicep as the primary authoring experience now, with ARM JSON as the compilation target.
For teams on Terraform, the same infrastructure-as-code principles apply. ARM templates and Bicep are Azure-native. Terraform is cloud-agnostic. Pick what fits your organisation's existing toolchain.