Automate Your Azure Infrastructure with ARM Templates
Learn how to define Azure resources as reusable JSON infrastructure code, validate your template, preview deployment changes, and deploy a storage account using Azure CLI.
Make Azure Deployments Repeatable and Consistent
ARM templates let you describe the Azure infrastructure you want in a declarative JSON file. Keep the definition in source control, review changes before deployment, and use the same pattern to build consistent development, test, and production environments.
JSON
what-if, deploy it to a resource group, and verify the result. Commands are written for Bash, including Azure Cloud Shell’s Bash environment.Infrastructure as Code
Define resources and their configuration in a version-controlled JSON file.
Preview Before Deploying
Use validation and What-If to identify issues and review expected changes.
Repeatable Deployments
Deploy consistent resource configurations through a documented workflow.
Introduction
Cloud infrastructure often needs to be recreated across multiple environments or updated in a controlled way. Creating each resource manually in the Azure portal can be slow, difficult to audit, and prone to configuration differences. Infrastructure as Code (IaC) addresses this problem by storing infrastructure definitions as code that can be reviewed, reused, and deployed consistently.
Azure Resource Manager (ARM) templates are JSON files that describe the resources and configuration for an Azure solution. Instead of writing a sequence of commands for every resource, you declare the intended state and let Azure Resource Manager coordinate the deployment. Microsoft also recommends considering Bicep for new authoring because it offers the same Azure deployment capabilities with a more concise syntax; ARM JSON remains useful for existing templates and scenarios that require the JSON format. Read Microsoft’s ARM template overview.
What Are Azure Resource Manager (ARM) Templates?
An ARM template is a declarative JSON document describing Azure resources, their properties, dependencies, parameters, variables, and optional outputs. Azure Resource Manager interprets the template and deploys resources at the selected scope.
| Template element | Purpose | Example in this tutorial |
|---|---|---|
$schema | Identifies the template schema. | Resource-group deployment schema URL. |
contentVersion | Version label for the template content. | 1.0.0.0 |
parameters | Accepts values supplied when deploying. | Storage account name and location. |
variables | Holds reusable expressions or values. | Not required for this small example. |
resources | Defines the Azure resources to deploy. | One StorageV2 account. |
outputs | Returns useful deployment results. | The deployed storage account name. |
ARM templates support repeatable deployments and can be deployed using the Azure portal, Azure CLI, Azure PowerShell, REST APIs, or Azure Cloud Shell. For a resource-group deployment, Azure CLI uses az deployment group commands.
Why Automate Azure Infrastructure?
Consistency
Store the desired configuration in one reviewed template instead of relying on a sequence of manual portal steps.
Reusability
Use parameters to customize names, regions, and supported configuration values for different environments.
Change visibility
Review source-control changes and run What-If before deploying infrastructure updates.
Automation pipelines
Integrate validation and deployment into a controlled CI/CD workflow when the team is ready.
Templates do not remove the need for review or governance. A template can still contain incorrect configuration or request changes that are inappropriate for production, so validation, access control, What-If review, and testing remain important.
Prerequisites
- An Azure subscription where you are permitted to create a resource group and storage account.
- Azure CLI installed locally, or use Azure Cloud Shell.
- Permissions to create the target resources and perform ARM deployment operations.
- A text editor such as Visual Studio Code.
- A globally unique Azure Storage account name containing only lowercase letters and numbers, between 3 and 24 characters.
How the Deployment Workflow Works
| Stage | Action | Purpose |
|---|---|---|
| 1 | Write the template | Declare the desired resource configuration. |
| 2 | Sign in and select subscription | Target the correct Azure context. |
| 3 | Validate | Check the template and deployment request. |
| 4 | Run What-If | Preview expected resource changes without applying them. |
| 5 | Deploy | Ask Azure Resource Manager to create or update the resource. |
| 6 | Verify | Confirm deployment state and resource properties. |
Step 1: Create the Project Files
Create a working folder for the template. These examples use Bash syntax and are suitable for Azure Cloud Shell Bash, Linux, or macOS terminals.
mkdir azure-arm-storage-demo
cd azure-arm-storage-demo
touch main.jsonYour project will contain the following file:
For a larger project, keep templates, parameter files, documentation, and pipeline definitions organized in source control. Do not commit credentials, secrets, or private deployment values to the repository.
Step 2: Create the ARM Template
Open main.json and paste the following complete example. It defines a StorageV2 account with locally redundant storage (LRS), HTTPS-only traffic, TLS 1.2 as the minimum TLS version, and public blob access disabled.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"metadata": {
"description": "Globally unique storage account name using lowercase letters and numbers."
},
"minLength": 3,
"maxLength": 24
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Azure region for the storage account."
}
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-05-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"kind": "StorageV2",
"sku": {
"name": "Standard_LRS"
},
"properties": {
"supportsHttpsTrafficOnly": true,
"minimumTlsVersion": "TLS1_2",
"allowBlobPublicAccess": false,
"allowSharedKeyAccess": false,
"publicNetworkAccess": "Enabled"
}
}
],
"outputs": {
"storageAccountName": {
"type": "string",
"value": "[parameters('storageAccountName')]"
}
}
}Understand the important settings
- Parameters: The account name and location can be provided at deployment time, so the template can be reused.
- StorageV2: Specifies a general-purpose v2 storage account.
- Standard_LRS: Uses locally redundant storage. Choose a redundancy option appropriate to your resilience requirements.
- HTTPS and TLS: Requires HTTPS traffic and sets the minimum TLS version to 1.2.
- allowBlobPublicAccess: false: Disables anonymous public access to blobs at the account level.
- allowSharedKeyAccess: false: Disables Shared Key authorization. This can break applications that depend on account keys or connection strings; confirm your application supports Microsoft Entra ID authorization before using this setting.
- publicNetworkAccess: Enabled: The account’s public network endpoint remains enabled. Public endpoint availability is not the same as anonymous blob access. For production workloads, assess network firewall rules, private endpoints, private DNS, and approved access paths.
Step 3: Sign In to Azure
Authenticate with Azure CLI:
az loginIf you have access to multiple subscriptions, list them and select the subscription where you intend to create the demo resources.
az account list --output table
az account set --subscription "YOUR-SUBSCRIPTION-ID-OR-NAME"
az account show --output tableReplace the placeholder with your own subscription ID or exact subscription name. Confirm the displayed subscription before continuing.
Create a resource group
A resource group provides a management boundary for this tutorial’s storage account. Choose an Azure region available to your subscription.
az group create \
--name rg-arm-automation-demo \
--location eastusThe commands in this article use eastus as an example. Change the region if needed. The resource group must exist before you run the resource-group deployment commands below.
Step 4: Validate the Template
Validation checks the deployment request before you create the resource. Run this from the folder containing main.json:
az deployment group validate \
--resource-group rg-arm-automation-demo \
--template-file main.json \
--parameters storageAccountName=REPLACEWITHUNIQUELOWERCASEReplace REPLACEWITHUNIQUELOWERCASE with a unique storage account name made only of lowercase letters and numbers. For example, do not use the placeholder literally. Storage account names are globally unique across Azure, so a name may already be taken.
If validation returns an error, read the full message. Common causes include invalid JSON, an unavailable or invalid parameter value, an unsupported property, insufficient permissions, or a resource name that does not meet Azure’s requirements.
Step 5: Preview Changes with What-If
Before creating resources, use the What-If operation to preview the changes Azure Resource Manager expects to make. What-If does not apply those changes; it helps you review the deployment plan. Microsoft Learn: ARM template What-If.
az deployment group what-if \
--resource-group rg-arm-automation-demo \
--template-file main.json \
--parameters storageAccountName=REPLACEWITHUNIQUELOWERCASEReview the result for unexpected updates, replacements, or deletions. A new storage account should appear as a resource to create if the name is available and the deployment request is valid.
Step 6: Deploy the Template
After reviewing the validation and What-If results, deploy the template:
az deployment group create \
--name deploy-arm-storage-demo \
--resource-group rg-arm-automation-demo \
--template-file main.json \
--parameters storageAccountName=REPLACEWITHUNIQUELOWERCASEUse the same unique name you used during validation and What-If. Azure CLI submits the template to Azure Resource Manager, which coordinates the resource deployment. The command returns deployment information, including a provisioning state when it completes.
For unattended automation, avoid hard-coding secrets in templates or shell history. Use managed identity or an appropriately secured workload identity for pipelines, and apply least-privilege role assignments to the deployment identity.
Step 7: Verify the Deployment
Check the deployment result:
az deployment group show \
--name deploy-arm-storage-demo \
--resource-group rg-arm-automation-demo \
--query properties.provisioningState \
--output tsvFor a successful deployment, the expected provisioning state is Succeeded. Next, confirm that the storage account exists and review its properties. Replace the placeholder with your deployed name.
az storage account show \
--name REPLACEWITHUNIQUELOWERCASE \
--resource-group rg-arm-automation-demo \
--query "{name:name,location:location,kind:kind,sku:sku.name,httpsOnly:enableHttpsTrafficOnly,publicNetworkAccess:publicNetworkAccess}" \
--output jsonYou can also list storage accounts in the resource group:
az storage account list \
--resource-group rg-arm-automation-demo \
--output tableVerify the properties that matter to your requirements rather than relying on the existence of the resource alone.
Step 8: Update and Redeploy
One advantage of declarative infrastructure is that you can update the template and redeploy it to move the resource toward the configuration described by the template. For example, if you add a supported tag property or adjust a configuration setting, validate and run What-If again before deploying.
Edit the template
Make a small, intentional change in main.json. Keep changes in source control and explain why they are needed.
Validate and preview again
Rerun az deployment group validate and az deployment group what-if using the same resource group, template, and parameters.
Deploy the reviewed update
Run az deployment group create only after you understand the proposed changes and have the required approvals.
Security and Best Practices for ARM Templates
- Use source control. Version templates, review pull requests, and keep a record of approved infrastructure changes.
- Parameterize environment-specific values. Keep the template reusable and store non-secret environment configuration in parameter files where appropriate.
- Never embed secrets in template files. Use secure parameters and approved secret-management patterns when a deployment genuinely requires a secret. Prefer managed identities or Microsoft Entra ID-based access when supported.
- Use least privilege. Give deployment identities only the permissions they need at the narrowest practical scope.
- Validate and use What-If. Review planned changes before applying them, especially in shared or production subscriptions.
- Test in a non-production environment. Check resource behavior and application compatibility before broad rollout.
- Modularize complex templates. Use linked or nested templates and shared components when that improves maintainability.
- Apply governance. Use Azure Policy, resource locks where appropriate, and approved naming/tagging conventions to support compliance and cost management.
- Keep API versions and resource settings reviewed. Check current Microsoft resource documentation before adopting a new resource type or API version.
- Plan networking and data protection. Public network access, private endpoints, logging, backup, retention, and recovery requirements depend on the workload and should be explicitly designed.
Microsoft’s guidance also recommends designing templates around deployment boundaries, using parameters thoughtfully, and testing templates before relying on them in production. See ARM template best practices.
ARM JSON vs. Bicep: Which Should You Use?
| Consideration | ARM JSON | Bicep |
|---|---|---|
| Syntax | JSON structure can become verbose for large deployments. | More concise domain-specific syntax. |
| Azure deployment engine | Deployed through Azure Resource Manager. | Compiled to ARM JSON and deployed through Azure Resource Manager. |
| Existing estates | Useful for existing JSON templates and integrations. | Can be introduced for new modules or gradual migration. |
| Learning and maintenance | Requires understanding JSON structure and ARM expressions. | Often easier to read and maintain for new IaC projects. |
For this article, the hands-on example uses ARM JSON because the topic is ARM templates. For a new Azure infrastructure project, evaluate Bicep as well; Microsoft recommends it for a more straightforward authoring experience. Learn about Bicep.
Troubleshooting Common ARM Template Problems
Invalid JSON or template syntax
Check braces, commas, quotation marks, parameter names, expressions, and the file path. Use an editor with JSON validation, then rerun Azure validation.
Storage account name is unavailable
Choose another globally unique name. Use only lowercase letters and numbers and keep the name within the required length.
Authorization failure
Confirm the signed-in identity, selected subscription, resource-group scope, and permissions to create the requested resource and ARM deployment.
Region or SKU not available
Check subscription restrictions, region availability, policy assignments, and the current resource provider documentation.
Validation passes but deployment fails
Inspect the deployment error details. Validation cannot guarantee every runtime or provider operation will succeed.
Application breaks after hardening
Review whether the application relies on Shared Key authorization or public network access. Test security changes against application requirements.
Inspect deployment operations
For additional details about failed operations, list the deployment operations for the resource group deployment:
az deployment operation group list \
--name deploy-arm-storage-demo \
--resource-group rg-arm-automation-demo \
--output tableCheck the detailed error message and correct the underlying template, permissions, policy, or resource configuration rather than repeatedly rerunning an unchanged failed deployment.
Optional: Clean Up the Demo Resources
If this was only a learning exercise and you no longer need the storage account, you can delete the dedicated demo resource group. This deletes the resources contained in that group, so verify the name carefully before running the command.
az group delete \
--name rg-arm-automation-demo \
--yes \
--no-waitFrequently Asked Questions
What is an Azure ARM template?
An ARM template is a declarative JSON file that defines Azure resources and their configuration for deployment through Azure Resource Manager.
Can ARM templates automate Azure infrastructure?
Yes. Templates describe the desired resources and can be validated, previewed, and deployed through Azure CLI, Azure PowerShell, the Azure portal, REST APIs, or Azure Cloud Shell.
Does What-If create or change Azure resources?
No. What-If previews expected changes without applying them. It should be used alongside validation, review, and appropriate change controls.
Should I use ARM JSON or Bicep for a new project?
ARM JSON remains supported and is useful for existing templates. Microsoft recommends considering Bicep for new authoring because its syntax is more concise while deploying through Azure Resource Manager.
Can I use ARM templates in CI/CD pipelines?
Yes. Teams can integrate template validation, What-If review, approval gates, and deployment into Azure DevOps or other CI/CD systems. Secure the pipeline identity and use least privilege.
Do ARM templates automatically make my resources secure?
No. Templates automate the configuration you define, but the design must still address identity, networking, encryption, monitoring, backup, compliance, and application compatibility.
Why does my storage account deployment fail?
Common reasons include a globally unavailable name, invalid template syntax, unsupported properties, insufficient permissions, Azure Policy restrictions, or region/SKU limitations. Inspect the deployment error for the exact cause.
Will deploying the same template always create a new resource?
Not necessarily. ARM templates are declarative; repeated deployments generally seek to bring resources to the state described by the template. The result depends on the resource configuration, current state, and deployment behavior.
Conclusion
Azure Resource Manager templates provide a repeatable way to define and deploy Azure infrastructure as code. In this walkthrough, you created a parameterized JSON template for a storage account, signed in to Azure, validated the deployment, previewed changes with What-If, deployed the resource, and verified its state.
For real workloads, treat the template as maintainable infrastructure code: keep it in source control, review changes, use least-privilege identities, validate and preview deployments, and design security and recovery controls for the workload. If you are starting a new Azure IaC project, also evaluate Bicep as a concise authoring option.
Continue Learning Microsoft Azure
Explore more practical cloud computing, Azure networking, hybrid cloud, Windows Server, backup, and virtualization tutorials on VMoreCloud.
Microsoft Azure • AWS • VMware • Windows Server • Linux • Backup & Disaster Recovery
Sources & Further Reading
- Microsoft Learn — ARM template deployment overview
- Microsoft Learn — Deploy ARM templates with Azure CLI
- Microsoft Learn — Preview deployment changes with What-If
- Microsoft Learn — ARM template best practices
- Microsoft Learn — Bicep overview
- Google Search Central — Creating helpful, reliable, people-first content
Technical note: Azure resource properties, supported API versions, permissions, regional availability, and CLI behavior can change. Review current Microsoft Learn documentation and test deployments in a non-production subscription before using a template for critical workloads. This tutorial is educational and is not a claim that the commands were executed in your Azure tenant.
