Deploy an ARM Template from CLI
Deploy an ARM template from CLI — Azure Tutorial. Learn how to deploy Azure resources using ARM templates via the Azure CLI, with hands-on steps and best practices.
Focus: deploy an arm template from cli
You’ve written infrastructure as code, committed it to a repo, and maybe even deployed a template from the portal. But now you’re staring at a terminal, wondering how to deploy an ARM template from CLI without clicking through Azure’s web console. The pain is real: manual deployments are slow, error-prone, and impossible to reproduce in CI/CD pipelines. The Azure CLI makes this feel like a superpower — one command turns your JSON template into live, verified Azure resources, and it works equally well on your laptop and in a GitHub Actions workflow.
The problem this lesson solves
Imagine you need to spin up a resource group, a storage account, and a virtual network for a new environment — maybe staging, maybe a test sandbox. Doing that through the Azure portal takes dozens of clicks per resource, and every click is a chance to misconfigure a name, a region, or a permission. Worse, you can’t rerun the same steps for another environment without starting over.
That’s the core problem: manual cloud provisioning doesn’t scale for developers or operations teams. The solution is infrastructure as code — you define your entire environment in a declarative JSON template (an ARM template), then deploy it repeatedly with a single command. But knowing the template syntax isn’t enough; you must master the deployment workflow from the command line, including validating, creating, and troubleshooting. This lesson fills that gap, showing you exactly how to deploy an ARM template from CLI — the Azure CLI, to be precise — and how to do it safely and predictably.
Core concept / mental model
Think of an ARM template as a blueprint for your Azure resources. It describes what you want (a storage account, a VM, a database) and how those resources relate, but not how Azure builds them. The Azure CLI is the builder that reads the blueprint and orders the work.
A deployment is the act of telling Azure: “Here’s my template — make it real.” The CLI sends your template to the Azure Resource Manager (ARM) service, which then provisions or updates resources to match the template’s desired state. Crucially, ARM deployments are idempotent: if you run the same deployment twice, Azure will not create duplicates — it reconciles the current state with the template’s intended state.
Three core concepts anchor the mental model:
- Deployment scope — where the deployment takes effect: resource group, subscription, management group, or tenant. Most commonly for beginners: a resource group.
- Template and parameters — the template (JSON) defines resources; a parameters file (also JSON) supplies environment-specific values like names and SKUs.
- Deployment name — a required, unique identifier for each deployment operation, which shows up in Azure activity logs and helps you track what happened.
A helpful analogy: think of your template as a Dockerfile and the CLI as docker build. You don’t manually construct the container; you describe it and let the tool produce the result — you can rebuild it anywhere, anytime.
How it works step by step
The journey from template to live resources follows a logical sequence:
- Authenticate — your CLI must be logged into Azure and have permission to create resources in the target subscription.
- Prepare your template — write or download a valid ARM template (JSON) and, optionally, a parameters file.
- Validate — run a test deployment (what-if or dry-run) to catch errors before committing changes.
- Deploy — issue the deployment command, pointing to your template and parameters.
- Verify — confirm resources were created as expected via CLI queries or the portal.
Each step has a cause-and-effect relationship. For example, if you skip validation, a typo in your resource type will cause deployment to fail midway — but you could have caught it early. If you miss the parameters file, the CLI will prompt you interactively, which breaks automation.
The magic happens in step 4. For a resource group–scoped deployment, you use the az deployment group create command. For subscription-level deployments (e.g., creating a resource group itself), you use az deployment sub create. The key difference is what you can deploy and how you define parameters.
Hands-on walkthrough
Prerequisites
- Azure CLI installed (≥ 2.40.0) — check with
az version. - You are logged in (
az login) and have selected the correct subscription (az account set --subscription "<id>"). - A local file
template.jsonwith a simple storage account definition.
Example 1: Create a resource group and deploy a simple template
First, create a resource group (if you don’t have one):
az group create --name rg-demo-eastus --location eastus
Next, save this minimal ARM template as template.json in your working directory:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[concat('storagedemo', uniqueString(resourceGroup().id))]",
"location": "[resourceGroup().location]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2"
}
]
}
Deploy it:
az deployment group create \
--resource-group rg-demo-eastus \
--template-file template.json \
--name demo-deployment-001
Expected output (trimmed):
{
"id": "/subscriptions/.../providers/Microsoft.Resources/deployments/demo-deployment-001",
"properties": {
"provisioningState": "Succeeded",
...
}
}
The storage account now exists, and you can verify it with az storage account list --resource-group rg-demo-eastus.
Example 2: Use a parameters file for reusable deployments
The beauty of ARM templates is that the same template can deploy to different environments with different parameter values. Create a parameters.json file:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"value": "storagedemoeast"
},
"skuName": {
"value": "Standard_GRS"
}
}
}
Now modify your template to accept parameters:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"defaultValue": "[concat('stor', uniqueString(resourceGroup().id))]"
},
"skuName": {
"type": "string",
"defaultValue": "Standard_LRS"
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[parameters('storageAccountName')]",
"location": "[resourceGroup().location]",
"sku": {
"name": "[parameters('skuName')]"
},
"kind": "StorageV2"
}
]
}
Run the deployment referencing both files:
az deployment group create \
--resource-group rg-demo-eastus \
--template-file template.json \
--parameters @parameters.json \
--name demo-deployment-002
Now you can deploy to westus by swapping the parameter values — no template edits needed.
Example 3: Validate before you commit
Never deploy a broken template. Use the --what-if flag to preview changes without applying them:
az deployment group what-if \
--resource-group rg-demo-eastus \
--template-file template.json \
--parameters @parameters.json
You’ll see a color-coded list of resources to create, modify, or delete. If your template has a syntax error, this command will report it without touching your resource group.
Compare options / when to choose what
You have several ways to deploy ARM templates, and the CLI is just one of them. Here’s how they stack up:
| Method | Best for | Automation | Learning curve |
|---|---|---|---|
| Azure CLI | Local dev, quick tests, scripted ops | Great (can be called in scripts) | Low |
| Azure PowerShell | Windows-centric workflows, PowerShell skills | Great (same as CLI) | Low-Medium |
| Azure Portal (deploy custom template) | One-off manual deployments, visual review | Poor | Low |
| REST API | Custom tooling, deep integration | Excellent | High |
| CI/CD pipelines (GitHub Actions or Azure DevOps) | Production deployments, team collaboration | Excellent | Medium-High |
When to choose CLI: For day-to-day development, quick experiments, and when you want a single command that works locally and in scripts. For production, you’d likely wrap the same CLI commands inside a pipeline, but the command itself is identical.
Pro tip: The CLI is essentially a thin wrapper over the ARM REST API. Every
az deployment ...command translates to an HTTP request to Azure. This means you can use the same command in a shell script, a CI job, or an Azure Cloud Shell session — no extra dependencies.
Variation: Deployment modes. The CLI supports two deployment modes: Incremental (default) and Complete. Incremental only adds or updates resources; Complete also deletes any resources in the resource group that aren’t in the template. Use --mode Complete with extreme caution — it’s risky and rarely needed outside special cases.
Troubleshooting & edge cases
Error: “ authorization failed ”
If your CLI user doesn’t have Contributor rights on the resource group, deployments will fail. Fix: run az role assignment create to grant the user the proper role, or ask your admin.
Error: “ InvalidTemplate ” or “ Deployment failed ”
This usually means your template has a syntax error, a missing parameter, or an unsupported API version. Fix: examine the error message — it often points to the exact JSON path. Use az deployment group validate to get more detailed feedback:
az deployment group validate \
--resource-group rg-demo-eastus \
--template-file template.json \
--parameters @parameters.json
Resource name conflicts
Storage account names must be globally unique. If you try to create mystorageaccount and it’s taken, deployment fails. Fix: use uniqueString() or [concat('mystore', uniqueString(resourceGroup().id))] as shown in the example.
Deployment name already exists
Deployments are immutable — you cannot reuse a deployment name within the same scope. Fix: append a timestamp to your deployment name in scripts, e.g., --name $(date +%s).
Template exceeds size limits
The ARM template size limit is 4 MB (for linked templates, it’s different). For larger templates, break them into linked or nested templates.
Edge case: subscription-level deployments
If you need to deploy a resource group itself, use az deployment sub create and include the resource group definition in your template. This is common for management-group-level policies.
What you learned & what's next
You’ve now mastered the essential skill of deploying an ARM template from CLI. You can:
- Explain the core idea: ARM templates are declarative blueprints, and the CLI turns them into live resources via resource-manager deployments.
- Complete a practical exercise: create a resource group, validate with
--what-if, and deploy a template with parameters usingaz deployment group create. - Identify when to use the CLI versus other methods, and troubleshoot common failures like authorization errors, invalid templates, and naming collisions.
These fundamentals directly enable the next lessons in this Azure track: you’ll soon wire these deployments into CI/CD pipelines, manage state with Bicep (an easier template language), and handle multi-environment deployments with confidence. In the next lesson, you’ll learn how to parameterize and reuse templates across environments — a natural step after mastering the CLI deployment flow.
Pro tip: Save your ARM template and parameters file in a Git repo alongside your application code. Then, any teammate (or a CI job) can recreate your environment with one command — that’s the foundation of true infrastructure as code.
Practice recap
Create a new resource group, then copy the template from the hands-on section and deploy it with az deployment group create and a parameters file. Run --what-if first to see the predicted changes, then deploy and verify with az resource list. Try changing the skuName parameter and redeploying to see how ARM reconciles the state.
Common mistakes
- Running
--what-ifis optional, but skipping it can cost you minutes of failed deployments — always validate first. - Not picking a unique deployment name — reusing a name will fail; append a timestamp or unique suffix.
- Forgetting to include required parameters; the CLI will prompt interactively, which breaks automation — always pass a parameters file.
- Using
--mode Completewithout understanding it — this can delete resources you intended to keep.
Variations
- Azure PowerShell: use
New-AzResourceGroupDeploymentinstead ofaz deployment group create. - Bicep: a friendlier DSL that compiles to ARM templates; deploy with
az deployment group create --template-file main.bicep. - REST API: call
PUT /subscriptions/{subId}/resourcegroups/{rg}/providers/Microsoft.Resources/deployments/{name}directly for custom integrations.
Real-world use cases
- Deploying a storage account and network resources for a staging environment from a CI pipeline using the same CLI command.
- Recreating a development sandbox every morning by running a single
az deployment group createscript that pulls the latest template from Git. - Teaching a team to move from portal click-ops to reproducible deployments by sharing a one-command template that provisions a demo app.
Key takeaways
- ARM templates are declarative blueprints — the CLI is just the builder that makes them real.
- Always validate with
az deployment group what-ifbefore applying changes — it saves time and surprises. - Use parameters files to keep templates reusable across environments without editing JSON.
- The Azure CLI command for resource group deployments is
az deployment group create. - Deployment names must be unique within a scope — use timestamps in automation.
- Incremental is the default and safe mode; avoid
--mode Completeunless you really know what you're doing.
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.