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
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
- Define your resources — For each Azure resource you need (storage account, App Service plan, web app, etc.), add an entry to the
resourcesarray with itstype,apiVersion,name,location, andproperties. - Add parameters — Decide which values vary between environments. Common examples: environment name, location, and SKU. Define them in the
parameterssection withdefaultValueif appropriate. - Use variables for computed values — If you need to combine parameters (e.g.,
storage${environment}), put that logic invariables. - Deploy the template — Use Azure CLI, PowerShell, or the portal to submit the template plus a parameter file to Resource Manager.
- 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 deploymentcommands 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:
- Invalid template syntax — JSON is unforgiving. A missing comma or bracket throws
InvalidTemplate. Runaz deployment group validateto check before deploying.
ez deployment group validate \
--resource-group rg-arm-demo \
--template-file template.json \
--parameters storageName=test
-
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. -
apiVersionnot recognized — OlderapiVersions can go missing. Useaz provider list --query "[?namespace=='Microsoft.Storage'].resourceTypes[]"to find valid values. -
Cascading failures — If your template has dependencies, Resource Manager handles them via
dependsOn, but cyclic dependencies causeCircularDependencyerrors. Break the cycle by redesigning your resources. -
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 validatestep — 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
rollbackOnErrorexplicitly or check the operation logs first.
Variations
- 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.
- Employ Azure Pipelines or GitHub Actions to deploy ARM templates from CI/CD, passing parameters per environment and adding approval gates.
- 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 createwith 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.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.