Deploy Resources with ARM Templates

Learn to deploy Azure resources with ARM templates — hands-on exercise, troubleshooting, and what to study next.

Focus: deploy resources with arm templates

Sponsored

You've built a great app, but deploying it to Azure still involves clicking through the portal, waiting for blades to load, and hoping you didn't forget a setting. That approach doesn't scale, and it's a ticking time bomb for configuration drift. In this lesson, you'll learn how to deploy resources with ARM templates — the declarative, infrastructure-as-code backbone of Azure — so you can ship entire environments with one command, review them like code, and reuse them across subscriptions.

The problem this lesson solves

Manual deployments are slow, error-prone, and impossible to reproduce. When you create resources through the Azure portal, you rely on your memory (or a teammate's) to get every setting right. A missing tag, a wrong SKU, or a misconfigured network rule can cause outages or surprise bills. And when you need a second environment for staging or disaster recovery, you're back to clicking.

ARM templates solve this by describing your entire infrastructure as a JSON file. That file becomes a single source of truth. You can version it, review it in pull requests, and apply the same template to dev, test, and production with parameter changes only. No more drift, no more undocumented manual steps — just reproducible deployments from code.

Why now? As your Azure footprint grows, the cost of manual management compounds. Teams that adopt ARM templates early build a culture of automation and review that pays off in every subsequent deployment.

Core concept / mental model

Think of an ARM template as a recipe for Azure. Instead of telling Azure how to create each resource step by step (imperative), you declare what you want the final state to be (declarative). Azure Resource Manager reads your recipe, figures out the necessary actions, and brings your environment to that state.

A template is just a JSON document with these key sections:

  • $schema — declares the template language version.
  • contentVersion — your own versioning for the template.
  • parameters — inputs you provide at deployment time (e.g., environment name, region, SKU).
  • variables — reusable values computed from parameters.
  • resources — the actual Azure resources to create or update.
  • outputs — values returned after deployment (e.g., the web app's URL).

Here's a visual flow: you write the template → you pass parameter values → Resource Manager validates and deploys → your environment is live. If something fails, nothing is left half-created (by default, it rolls back).

How it works step by step

  1. Define your resources — For each Azure resource you need (storage account, App Service plan, web app, etc.), add an entry to the resources array with its type, apiVersion, name, location, and properties.
  2. Add parameters — Decide which values vary between environments. Common examples: environment name, location, and SKU. Define them in the parameters section with defaultValue if appropriate.
  3. Use variables for computed values — If you need to combine parameters (e.g., storage${environment}), put that logic in variables.
  4. Deploy the template — Use Azure CLI, PowerShell, or the portal to submit the template plus a parameter file to Resource Manager.
  5. Review and repeat — Once it works, you can redeploy the same template to another environment by changing the parameter file only.

Key insight: ARM templates are idempotent. Re-running the same template with the same parameters doesn't duplicate resources — it updates the existing ones to match the declared state.

Hands-on walkthrough

Let's deploy a simple storage account with ARM templates. You'll need the Azure CLI installed and logged in.

Step 1: Create the template

Create a file named template.json:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageName": {
      "type": "string",
      "minLength": 3,
      "maxLength": 24
    },
    "skuName": {
      "type": "string",
      "defaultValue": "Standard_LRS",
      "allowedValues": ["Standard_LRS", "Standard_GRS", "Premium_LRS"]
    }
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2023-05-01",
      "name": "[parameters('storageName')]",
      "location": "[resourceGroup().location]",
      "sku": {
        "name": "[parameters('skuName')]"
      },
      "kind": "StorageV2",
      "properties": {
        "minimumTlsVersion": "TLS1_2"
      }
    }
  ]
}

Step 2: Deploy to a resource group

Run the following commands in your terminal:

# Create a resource group
ez group create --name rg-arm-demo --location eastus

# Deploy the template
ez deployment group create \
  --resource-group rg-arm-demo \
  --template-file template.json \
  --parameters storageName=myuniquestorage123 skuName=Standard_LRS

Expected output (abridged):

{
  "id": "/subscriptions/.../providers/Microsoft.Resources/deployments/template",
  "name": "template",
  "properties": {
    "provisioningState": "Succeeded",
    ...
  }
}

Step 3: Verify the deployment

Check that the storage account exists and has TLS 1.2 enforced:

ez storage account show --name myuniquestorage123 --resource-group rg-arm-demo --query "minimumTlsVersion"

Expected output:

"TLS1_2"

Step 4 (bonus): Redeploy to a new environment

Run the same template with different parameters to create a staging resource group:

ez group create --name rg-arm-stage --location westus
ez deployment group create \
  --resource-group rg-arm-stage \
  --template-file template.json \
  --parameters storageName=stageuniquestorage456 skuName=Standard_GRS

You now have a storage account in a second region with redundant storage, all from the same template.

Compare options / when to choose what

ARM templates are not the only way to deploy Azure resources. Here's how they stack up against the alternatives:

Method Learning curve Reusability State tracking Best for
ARM templates Medium High (parameter files) Native (idempotent) Teams that need full control and native Azure integration
Bicep Low High (modules) Native Most new projects; ARM's modern DSL
Azure CLI/Portal Low None None Quick experiments, one-off tasks
Terraform High Very high (providers) External state file Multi-cloud environments

When to choose ARM templates:

  • You need to target az deployment commands directly.
  • You work with Azure Policy or Azure Blueprints that expect ARM syntax.
  • You're maintaining legacy templates that predate Bicep.

When to choose something else:

  • Bicep — if you're starting fresh, Bicep compiles to ARM but is far more readable and concise.
  • Terraform — if you also manage AWS or GCP.

Pro tip: Even if you adopt Bicep, understanding ARM is still valuable — every error message and Stack Overflow answer is in ARM terms.

Troubleshooting & edge cases

Here are the most common pitfalls and how to fix them:

  1. Invalid template syntax — JSON is unforgiving. A missing comma or bracket throws InvalidTemplate. Run az deployment group validate to check before deploying.
ez deployment group validate \
  --resource-group rg-arm-demo \
  --template-file template.json \
  --parameters storageName=test
  1. Storage account name already taken — You'll see StorageAccountAlreadyTaken. Storage names are globally unique; use a random suffix or a naming convention with environment and date.

  2. apiVersion not recognized — Older apiVersions can go missing. Use az provider list --query "[?namespace=='Microsoft.Storage'].resourceTypes[]" to find valid values.

  3. Cascading failures — If your template has dependencies, Resource Manager handles them via dependsOn, but cyclic dependencies cause CircularDependency errors. Break the cycle by redesigning your resources.

  4. Rollback surprises — By default, failed deployments roll back to the last good state. If you expect partial success, set --rollback-on-error (PowerShell) or accept the default.

What you learned & what's next

You now understand how to deploy resources with ARM templates: the problem they solve, the mental model of declarative infrastructure, and how to write, deploy, and troubleshoot a template. You can create a storage account, scale to multiple environments, and know when to pick ARM over Bicep or Terraform.

Next in the Azure Tutorial track, you'll move from infrastructure definition to application-level concerns. Expect to learn about [managed identities] — how to give your code secure, passwordless access to Azure resources without storing secrets in your templates. Your ARM skills will be the perfect foundation.

Final tip: Save every template in a Git repository. Deploy from CI/CD with the same commands you just practiced, and you'll have audit trails, reviews, and rollbacks for free.

Practice recap

Create a new template that deploys a Linux App Service plan and a web app with a starter Node.js app. Deploy it to a new resource group, then change the app name in a parameters file and deploy to a second resource group. Confirm both apps are running and note how the same template served both environments.

Common mistakes

  • Forgetting that storage account names must be globally unique — using the same name in two resource groups causes StorageAccountAlreadyTaken, so append a random suffix.
  • Hardcoding resource names or locations inside the template instead of using parameters — this kills reusability and forces template duplication per environment.
  • Skipping the az deployment group validate step — a syntax typo or bad apiVersion will fail mid-deployment, and debugging live is slower than catching it beforehand.
  • Ignoring that a failed ARM deployment rolls back to the previous state by default — if you expected partial progress, define rollbackOnError explicitly or check the operation logs first.

Variations

  1. Use Bicep instead of raw JSON — Bicep compiles to ARM templates and feels like a modern language, but you're still deploying through the same ARM engine.
  2. Employ Azure Pipelines or GitHub Actions to deploy ARM templates from CI/CD, passing parameters per environment and adding approval gates.
  3. Store templates in Azure Blueprints to bundle role assignments, policies, and resource definitions for governance across subscriptions.

Real-world use cases

  • Automate creation of a staging environment that mirrors production with different SKUs, using the same template and a parameter file.
  • Deploy a multi-tier web app (App Service + Storage + SQL) to a new region during disaster recovery, simply by changing the location parameter.
  • Audit and reproduce a customer's environment in a sandbox subscription for support reproductions — exactly one command away.

Key takeaways

  • ARM templates are declarative JSON files that let you describe and deploy complete Azure environments from code.
  • The core template sections — parameters, variables, resources http://outputs — give you reusability and control.
  • Deploying is as simple as az deployment group create with a template file and parameter values.
  • Templates are idempotent, so you can redeploy safely to update or recreate infrastructure.
  • Validate before you deploy to catch syntax and API issues early.
  • Choose ARM over Bicep, Terraform, or portal when you need native Azure control or legacy template compatibility.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.