Should You Learn ARM?

Short on time? Bicep (the next module) supersedes ARM for anything you’d hand-write today — it compiles straight to ARM, so you lose nothing by starting there instead. This module is here because Bicep is ARM underneath, existing ARM templates are still common in the wild, and every limitation covered here still applies to Bicep too. Treat it as reference material you can skip on a first pass and come back to.

What Are ARM Templates?

ARM (Azure Resource Manager) Templates are the native JSON-based IaC format for Azure. Every Bicep file, every Terraform azurerm resource, and every portal deployment ultimately compiles down to an ARM Template before Azure processes it.

ARM Templates are JSON files that declare the resources you want to deploy, their properties, and the dependencies between them. They are submitted directly to the Azure Resource Manager API — the same API the Azure portal uses.

Key Characteristics

  • Declarative — describe what you want, not how to create it
  • Idempotent — running the same template multiple times produces the same result
  • Azure-native — full coverage of every Azure resource type and API version on day 0
  • No state file — Azure tracks the state of your resources
  • Deployment modes — Incremental (add/update only) or Complete (remove resources not in template)

Pros

  • Full Azure API coverage — every new Azure feature is available in ARM the day it launches
  • No abstraction layer — what you write is what Azure receives; no transpilation surprises
  • Built-in expression functions — [concat()], [resourceId()], [reference()], etc.
  • Linked/nested templates — compose large deployments from smaller, reusable templates
  • What-if deployments — preview changes before applying
  • Multiple deployment scopes — resource group, subscription, management group, or tenant

Cons

  • Verbose JSON — simple resources require many lines of boilerplate
  • Steep learning curve — JSON expressions like [parameters('name')] are easy to mistype
  • No clean loops — iteration requires the copy element rather than a natural loop syntax
  • Limited modularity — linked templates require accessible URLs; no local module resolution
  • Largely superseded by Bicep — for new Azure-only projects, Bicep is the recommended path

File Structure

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

Example: Deploy a Storage Account

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": {
      "type": "string",
      "minLength": 3,
      "maxLength": 24
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    }
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2023-01-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": { "name": "Standard_LRS" },
      "kind": "StorageV2",
      "properties": {
        "accessTier": "Hot",
        "minimumTlsVersion": "TLS1_2",
        "allowBlobPublicAccess": false,
        "supportsHttpsTrafficOnly": true
      }
    }
  ]
}
# Deploy
az deployment group create \
  --resource-group my-rg \
  --template-file storage.json \
  --parameters storageAccountName=mystorageaccount123

# Preview changes first
az deployment group what-if \
  --resource-group my-rg \
  --template-file storage.json \
  --parameters storageAccountName=mystorageaccount123

Migrating to Bicep

ARM is largely superseded by Bicep for human-authored templates. You can decompile any ARM JSON file to Bicep as a starting point:

az bicep decompile --file storage.json
# Produces: storage.bicep

ARM Template Docs ARM Template Functions Azure Quickstart Templates


🧪 Lab — Complete the Storage Account

This ARM resource block is missing two of the hardening properties shown in the example above. Add a "kind" of StorageV2, and set minimumTlsVersion to TLS1_2 inside properties.

📝 Quiz — ARM Templates

Next Up

Bicep fixes ARM’s biggest usability complaints while staying on exactly the same underlying API.