Getting Started with Automated ARM Deployments in Azure

The audience for this post are technologists who have some exposure to provisioning resources in the Azure portal, but haven’t yet started doing scripted deployments. The purpose of this post is to get started with understanding and customizing the contents of an ARM template (as opposed to doing the one-click deployment from a template available on GitHub).

This post is accurate as of mid-June 2017. As with anything related to Azure, things change at a pretty rapid pace.

There are two ways to provision a resource in Azure: PowerShell scripts, or ARM templates. You can also use both, for instance if you use PowerShell to deploy the ARM template. Which to use is mostly personal preference. The PowerShell approach focuses more on individual resources, whereas ARM focuses more on groups of related resources. In this post we’re focusing on ARM templates (though I like to use PowerShell instead of ARM for certain types of resources, such as alerts).

Intro to Azure Resource Manager

The Azure portal is built on Azure Resource Manager (ARM), as are the newer (non-classic) Azure resources. Before we dive into details, here are a few key definitions so you know the lingo:

Azure Resource Manager (ARM): The framework which allows us to deploy and manage resources in Azure. The various service management APIs behind Visual Studio, command line, and the Azure portal all share the same underlying Azure Resource Manager layer and APIs.

Resource Group: A logical container for resources in Azure. Typically, one resource group contains resources with the same lifecycle which should be managed, secured, and deployed together as a unit (such as an Azure Virtual Machine and its related Azure Storage accounts).

Azure Resource Groups

Resource: Resources are the individual services in Azure, such as Azure SQL Database, Azure Virtual Machine, or Azure Storage account. There is a wide variety of resources available in the Azure platform. A resource must be assigned to a resource group (though a resource can usually be moved to another resource group if needed). Resources are able to interact with resources from other resource groups.


ARM Template: A declarative JSON script which describes the resource(s) to be deployed to a resource group. If you are familiar with a database project in SQL Server Data Tools (SSDT) or with Desired State Configuration (DSC), then you are familiar with declarative development: a script with the end state, or desired state, in mind that doesn’t specify every single step required to reach the end state.


Resource Provider: A resource provider is specified within the ARM template. The most common resource providers are pre-registered, but you may need to register some before deploying a resource. Resource providers registered for use with your subscription can be found in the portal (or via PowerShell, REST API, or CLI).


The resource provider allows access to resource types, and in turn allows communication between each resource and Azure Resource Manager:


Image source:

Why Use ARM Templates?

In many cases, you can easily provision resources in the web-based Azure portal. If you’re never going to repeat the deployment process, then by all means use the interface in the Azure portal. It doesn’t always make sense to invest the time in automated deployments. However, ARM templates are really helpful if you’re interested in achieving repeatability, improving accuracy, achieving consistency between environments, and reducing manual effort.

Use ARM templates if you intend to:

  • Include the configuration of Azure resources in source control (“Infrastructure as Code”), and/or
  • Repeat the deployment process numerous times, and/or
  • Automate deployments, and/or
  • Employ continuous integration techniques, and/or
  • Utilize DevOps principles and practices, and/or
  • Repeatedly utilize testing infrastructure then de-provision it when finished

For this post, I’m going to use the Azure infrastructure behind the visuals shown in the SQLSkills Waits Library as an example for how to do an ARM deployment:


For the purposes of this post, each of the resource names and parameter values have been changed somewhat from the real names. Also, the source data and data integration processes for getting the data into the Azure SQLDB are omitted for brevity so that we focus only on the ARM components. (For those interested, the source data for the charts comes from our SentryOne customers who have opted to sync data to This data is first anonymized, then extracted into an internal data warehouse, and finally summarized to refresh aggregates in Azure SQLDB when a calendar month completes.)

In our Azure environment for the Analytics team, we have separate Development and Production resource groups for this set of resources to support the SQLSkills Waits Library visuals (we’ll introduce a 3rd tier for UAT/Stage purposes later if necessary but we have not fired up a 3rd set of resources until it becomes necessary). Other than naming and scale level (i.e., cost), the Production and Dev resources are exact copies of each other -- unless of course active development is occurring which hasn’t yet been migrated to Prod.

Importance of Resource Groups

Resource groups are extremely useful as a ‘container’ for organizing and separating resources. However, resource groups are more than just a logical container though - they are extremely fundamental to ARM. Whenever you generate an ARM automation script from the Azure portal, you always get the code for the entire resource group even if you’re in the scope of a single resource. For that reason, it is important to keep resource groups more narrow than broad. From an ARM perspective, all assets in a resource group are intended to be deployed, managed, and secured as a group. (You can see the ARM script for individual resources in the Resource Explorer-it also exposes Resource Providers nicely.)

Another advantage of resource groups is you can assign permissions (RBAC – role-based access control) at the resource group level, so security will be inherited by all resources within that resource group. For instance, perhaps you want to allow a certain user, group, or service principal to be an owner, contributor, or reader of all resources within the confines of one resource group. The inheritance model is: Subscription > Resource Group > Resource (therefore, it’s often best to set roles at the subscription level only if they should inherit across all, or at least most, resource groups).

One more advantage is that you can also set policies to apply across a resource group. For instance, you can specify things such as allowed resources (or not allowed resource types), allowed locations, allowed storage SKUs, or a requirement for storage account encryption.

Ways to Create an ARM Template

There are a few ways you can get started:

  1. From the automation script available from the Azure Portal (which is imperfect – more on that in a moment), or
  2. An ARM template in Visual Studio, or
  3. QuickStart Templates from GitHub (there’s a lot to choose from – the templates that start with 101 are less complex), or
  4. Create from the ground up, or
  5. Start with a combination of 1 and 2 or 3, customizing the way you like it

Starting with the Automation Script in the Azure Portal

The pre-generated automation scripts (from existing resources) is a good place to start.


However, there are a few things I don’t like about these auto-generated scripts:

  • The value is part of the parameter name. Ex: if you have a web app named “AppSQLWaitsDev” it will generate a parameter called “sites_AppSQLWaitsDev_name.” Since my naming convention calls for having Dev, Test, and Prod as the suffix, I don’t personally care for having the initial parameter value as part of the actual parameter name.
  • Not all resources can be scripted out in this manner yet. You’ll see a message at the top of the template pane when this occurs.
  • Default values are overused – at least for the way I want to use ARM templates. Some folks will disagree and say a default should always be there. However, if I want to enforce different values between Dev, Test, and Prod then I don’t necessarily always want a default for every single value. We’ll see some of this in the example below.
  • The admin password (when required) is not parameterized.
  • There are a few inaccurate values that come out, which result in deployment failures. Ex: for a SQL Server (i.e., the container for SQLDB and SQLDW), the script auto-generates the master database with an edition of “System.” This fails deployment. In my testing, I’ve found if I leave off master in my deployment script, Azure creates it behind the scenes ok when the server is provisioned. (You'll want to test this for yourself as I couldn't find the best approach documented anywhere.)

Since I tend to manually provision resources in Dev, then deploy to Test and Prod, that means my workflow is to start with the auto-generated script and make changes from there. My list of changes includes:

  1. Put script into a Visual Studio project
  2. Split out parameter values into their own file
  3. Fine-tune the parameters section at the top of the main JSON file
    • Change parameter names to match internal naming conventions (removing the default value as part of the auto-generated name)
    • Add/remove allowed values, default values, min values when appropriate
    • Add metadata description for each parameter
    • Add parameters for anything that could vary between environments, or anything you don’t want ‘buried’ down in the Resources section of the ARM template
    • Ensure the admin password is a securestring and will prompt at runtime (i.e., this prompt will happen if there’s not a specified parameter value or default value)
  4. Add variables where they make sense
  5. Fine-tune the resources section
    • Convert locations to inherit from the parent Resource Group
    • Add tags which are appropriate
    • Add/update comments for each resource to explain the purpose
  6. Consider which items might be useful to be separated out from the ARM template, such as alerts

Note: if Visual Studio says parameters are undefined, but you know they are defined, just close and reopen Visual Studio.

Creating an ARM Project in Visual Studio

In Visual Studio, there is an “Azure Resource Group” type of project under Visual C# > Cloud:

If you don’t see this option, that means the Azure SDK hasn’t been installed yet. For more information, see this post.

Note: all screen shots shown here are from Visual Studio 2015 (because, at the time of this writing, not all extensions for BI and Azure projects are available yet in VS 2017).

Make sure you don’t use any special characters in your Visual Studio project name if you intend to deploy directly from Visual Studio (discussed later in this post). It uses the project name as the deployment name and you will get the following error:

The provided deployment name
16:57:51 – [ERROR] 'webapp+sqldbresources-0525-2057' has these invalid characters: '+'. The name
16:57:51 – [ERROR] can only be a letter, digit, '-', '.' or '_'.

Before it creates the project, you are prompted to choose an Azure Template. If you want to choose a template to poke around what it looks like, that’s a good way to learn syntax. Based on my workflow, I usually choose a blank template now.


Once the project is created, you will see 3 files:

Visual Studio Initial 3 ARM Files

The names of the files will differ, based on if you selected a template or not. If this structure works for you, that’s great. However, I have come up with a slightly different structure to use instead.

Visual Studio ARM Project Structure

The structure I’m working with looks like this:

Visual Studio Initial 3 ARM Files

ARMParameters folder: Contains one JSON parameters file for each environment that I need to deploy this solution to. For this solution, we only require 2 environments so far instead of 3 or 4.

ARMTemplates folder: Contains the main JSON file for deployment (I call it ARMResources.json – the original structure created by Visual Studio calls it azuredeploy.json).

Documentation folder: Just a simple document with any pertinent notes or instructions. Helpful for colleagues that might need do a deployment, but didn’t design it from the ground up. Or to specify if there’s a particular property that needs to be set after deployment which isn’t exposed via ARM.

PowerShell folder: These are scripts which are separate from the ARM template. Alerts could be part of the ARM template if you want, but they get verbose so I like to separate them out in PowerShell instead. I'm also separating Role-Based Access Control (RBAC) specifications so they don't get 'buried' in the main ARM template. The DeployFromFileDrop.ps1 is discussed later in this post (it picks up the JSON files from a drop zone and does the deployment to Azure).

Deploy-AzureResourceGroup.ps1 file: This is a deployment file required by Visual Studio if you choose to deploy from VS (right-click the project name and choose deploy). I’ve only gotten myself in trouble when I try to edit this file, so now I just leave it alone. Do make sure the Build Action on this file is set to Content (if it’s set to None, you’ll get an error: "PowerShell deployment script is missing in project").

Example ARM Parameters

Below is an example from my Dev parameters file. I like following these practices:

  • camelCase naming
  • Descriptive names starting with the type of resource it is
  • Value not included as part of the name

My rule of thumb:

  • If a value is the same between all environments, or isn't necessary to call out in a parameter, it's just fine to put in the resource. Ex: a comment like "Create database associated with the app."
  • If it differs between environments, use a parameter.
  • If it varies but can be worked out on its own by the system, put in a variable instead of a parameter.

Example syntax from DevValues.parameters.json:

{"$schema": "","contentVersion": "","parameters": {"environmentName": {"value": "Dev"},"sqlServerAdminUser": {"value": "BITeamAdminAcct"},"sqlServerName": {"value": "sqlserversqlwaitsdev"},"sqlServerFirewallRulesAllowAllAzureIPs": {"value": "AllowAllWindowsAzureIps"},"sqlServerFirewallRulesAllowS1Office": {"value": "Allow S1 Huntersville"},"sqlServerFirewallRulesS1OfficeStartIPAddr": {"value": ""},"sqlServerFirewallRulesS1OfficeEndIPAddr": {"value": ""},"sqlDBName": {"value": "sqldatabasesqlwaits"},"sqlDBEdition": {"value": "Standard"},"sqlDBServiceObjectiveName": {"value": "S0"},"sqlDBMaxSizeBytes": {"value": "524288000"},"sqlDBCollation": {"value": "SQL_Latin1_General_CP1_CI_AS"},"storageSQLBackupsName": {"value": "bckstrgsqlwaitsdev"},"storageSQLBackupsSkuName": {"value": "Standard_LRS"},"storageSQLDiagnosticsName": {"value": "diagstrgsqlwaitsdev"},"storageSQLDiagnosticsSkuName": {"value": "Standard_LRS"},"webAppServicePlanName": {"value": "AppServicePlanSQLWaitsDev"},"webAppServicePlanSkuName": {"value": "F1"},"webAppServicePlanCapacity": {"value": 1},"webAppName": {"value": "AppSQLWaitsDev"},"tagSupportContact": {"value": "Analytics Team"},"tagBillingCategory": {"value": "SQLSkills Waits Library"}}}

The value specified for sqlDBServiceObjectiveName differs between the Dev and Prod parameter files. Same with webAppServicePlanSkuName. This indicates Dev is scaled lower than Prod.

The names of resources differ as well between the Dev and Prod parameter files. Our naming convention uses the environment as the suffix. Our Analytics Team here at SentryOne uses one single subscription for all of our resources, separated out by resource group. Therefore, we want to be as specific as possible as to what’s what (which is why we don’t leave the prod suffix off completely).

The thing to remember is that parameters are all defined within the main JSON script (the one I call ARMResources.json which will be discussed next) including the type, allowed values, default value, and description. The only thing specified in the separate parameters file above are the values. This technique is optional.

Example ARM Deployment Template

When working with the main JSON file, there’s a couple of ways to interact with it in Visual Studio: the main window, and the JSON Outline pane.

In the main window, notice how you can collapse the main sections which are:

  1. $schema (required)
  2. contentVersion (required)
  3. parameters (optional)
  4. variables (optional)
  5. resources (required)
  6. outputs (optional)

ARM Template

You’ll want to use the +/- buttons to collapse the sections in the main window after the script gets verbose enough.

There is also the JSON Outline pane which helps you jump to a specific item in the JSON script:

JSON Outline Pane

Let’s break down each of the sections in the ARM template file:

1. $schema

This refers to the JSON schema. Note that the schema specified is different in the parameters file vs. the main ARM deployment file.

2. contentVersion

You can increment this version if you’d like to manage the changes made over time. The default is “”

3. Parameters

Next are the parameter definitions (not the values, just the definitions for each parameter).

ARM Parameters

Type: Most commonly string or int; note the sqlServerAdminPW is set to be a securestring. By setting it to securestring, it won’t be a visible value in the deployment status log.

Metadata Description: Helpful info to remember what it’s used for, or any other notes you want to leave for yourself and your team.

Allowed Values: If there are only certain options which will work successfuly (such as the sqlDB Edition shown above), this is really helpful.

Default Value: Makes sense in certain circumstances. If there’s not a default value, then the value needs to be in the separate parameters file, or typed in at deployment time.

4. Variables

Next is the variables section:

ARM Variables

The variables section can be optionally used for things that do vary but can be worked out systematically via JSON fragments. The idea here is to simplify what’s in the resources section. Note that variables do not have definitions (allowed values, default values, etc) the way parameters do.

If you prefer, you can input values directly into the variables section instead of the parameters file. A lot of examples online do this, and some ARM template authors purposely try to minimize the number of parameters. I like to centralize all of my inputs into the parameters file, rather than have some values in parameters and some values some in variables. However, it’s really up to you – both parameters and variables feed values to the resources.

5. Resources

The resources section is where everything comes together:

ARM Resources

The resources section defines each resource to be deployed, with references to parameters and variables as necessary. The elements which are defined vary based on the kind of resource which is being deployed.

Location: You might be tempted to create a parameter for Location. However, a better practice is to inherit the location from the resource group as shown in the above example.

Type: The Type element for a resource is a combination of Resource Provider (discussed above) plus the Resource Type (ex: Microsoft.sql/servers).

Comments: Helpful info to clarify what the resource is, or what it’s being used for.

DependsOn: This helps Azure understand dependencies, so it can deploy resources in parallel, or sequentially, as appropriate.

API Version: A version is specified for each resource which is associated with a version of the REST API. The version impacts which elements can be specified for the resource, so the versions are updated on occasion.

Example syntax from ARMResources.json:

{"$schema": "","contentVersion": "","parameters": {"environmentName": {"type": "string","metadata": {"description": "The environment this set of parameters applies to."},"allowedValues": ["Dev","Test","Prod"]},"sqlServerAdminUser": {"type": "string","metadata": {"description": "The administrator account (aka subscriber account) for the SQL Server."}},"sqlServerAdminPW": {"type": "securestring","metadata": {"description": "The PW for the administrator account. This value should not be saved in source control, so it is excluded from the companion parameters file."}},"sqlServerName": {"type": "string","metadata": {"description": "Name – at the server level. Must include the dev/test/prod suffix per naming conventions."}},"sqlServerFirewallRulesAllowAllAzureIPs": {"type": "string","metadata": {"description": "Specifies if firewall is open internally to Azure. See related variable for actual value passed in. Note this applies at the server level, not the database level."}},"sqlServerFirewallRulesAllowS1Office": {"type": "string","metadata": {"description": "Firewall openings for SentryOne office."}},"sqlServerFirewallRulesS1OfficeStartIPAddr": {"type": "string","metadata": {"description": "Start of IP address range for firewall openings for SentryOne office."}},"sqlServerFirewallRulesS1OfficeEndIPAddr": {"type": "string","metadata": {"description": "End of IP address range for firewall openings for SentryOne office."}},"sqlDBName": {"type": "string","metadata": {"description": "The name of the Azure SQLDB. There is no dev/test/prod suffix-this SQLDB name must remain consistent across environments. See related variable for actual value passed in."}},"sqlDBEdition": {"type": "string","defaultValue": "Basic","allowedValues": ["Basic","Standard","Premium"],"metadata": {"description": "The edition for the Azure SQLDB."}},"sqlDBServiceObjectiveName": {"type": "string","defaultValue": "Basic","allowedValues": ["Basic","S0","S1","S2","P1","P2","P3"],"metadata": {"description": "The performance level for the edition of Azure SQLDB. This is expected to be S0 in Dev, and S2 in Prod."}},"sqlDBMaxSizeBytes": {"type": "string","defaultValue": "524288000"},"sqlDBCollation": {"type": "string","defaultValue": "SQL_Latin1_General_CP1_CI_AS","metadata": {"description": "Collation which applies to all DBs in this server."}},"storageSQLBackupsName": {"type": "string","metadata": {"description": "The name of the storage account where automated SQL backups are sent."}},"storageSQLBackupsSkuName": {"type": "string","defaultValue": "Standard_LRS","allowedValues": ["Standard_LRS","Standard_GRS","Standard_ZRS","Premium_LRS"],"metadata": {"description": "The storage account type for automated SQL backups. For backups, we should use GRS."}},"storageSQLDiagnosticsName": {"type": "string","metadata": {"description": "The name of the storage account where automated SQL auditing & logging are sent."}},"storageSQLDiagnosticsSkuName": {"type": "string","defaultValue": "Standard_LRS","allowedValues": ["Standard_LRS","Standard_GRS","Standard_ZRS","Premium_LRS"],"metadata": {"description": "The storage account type for automated SQL backups. For diagnostics, we should use LRS."}},"webAppServicePlanName": {"type": "string","metadata": {"description": "Name for the app service plan (aka hosting plan name)."}},"webAppServicePlanSkuName": {"type": "string","allowedValues": ["F1","D1","B1","B2","B3","S1","S2","S3","P1","P2","P3","P4"],"metadata": {"description": "Pricing tier for the app service plan. Should be Free (F1) in Dev, and Standard (S1) in Prod."}},"webAppServicePlanCapacity": {"type": "int","defaultValue": 1,"minValue": 1,"metadata": {"description": "Instance count for the app service plan."}},"webAppName": {"type": "string","metadata": {"description": "Name for the web app within the App Service Plan."}},"tagSupportContact": {"type": "string","metadata": {"description": "An Azure tag which specifies who to contact for support of these resources."}},"tagBillingCategory": {"type": "string","metadata": {"description": "An Azure tag which specifies the billing category for these resources."}}},"variables": {"fullSQLDBName": "[concat(parameters('sqlServerName'), '/', parameters('sqlDBName'))]","fullSQLServerFirewallRulesAllowAllAzureIPs": "[concat(parameters('sqlServerName'), '/', parameters('sqlServerFirewallRulesAllowAllAzureIPs'))]","fullSQLServerFirewallRulesAllowS1Office": "[concat(parameters('sqlServerName'), '/', parameters('sqlServerFirewallRulesAllowS1Office'))]","fullWebAppURL": "[concat(parameters('webAppName'), '')]","fullWebAppSCMURL": "[concat(parameters('webAppName'), '')]","fullWebsiteName": "[concat(parameters('webAppName'), '/web')]","fullWebsitePublishingName": "[concat('$', parameters('webAppName'))]"},
"resources": [{"comments": "Create SQL Server as a container for the DBs","type": "Microsoft.Sql/servers","kind": "v12.0","name": "[parameters('sqlServerName')]","tags": {"supportContact": "[parameters('tagSupportContact')]","billingCategory": "[parameters('tagBillingCategory')]"},"apiVersion": "2014-04-01-preview","location": "[resourceGroup().location]","properties": {"administratorLogin": "[parameters('sqlServerAdminUser')]","administratorLoginPassword": "[parameters('sqlServerAdminPW')]","version": "12.0"},"dependsOn": []},{"comments": "Create the database associated with this solution.","type": "Microsoft.Sql/servers/databases","kind": "v12.0,user","name": "[variables('fullSQLDBName')]","tags": {"supportContact": "[parameters('tagSupportContact')]","billingCategory": "[parameters('tagBillingCategory')]"},"apiVersion": "2014-04-01-preview","location": "[resourceGroup().location]","properties": {"edition": "[parameters('sqlDBEdition')]","status": "Online","requestedServiceObjectiveName": "[parameters('sqlDBServiceObjectiveName')]","collation": "[parameters('sqlDBCollation')]","maxSizeBytes": "[parameters('sqlDBMaxSizeBytes')]"},"dependsOn": ["[resourceId('Microsoft.Sql/servers', parameters('sqlServerName'))]"]},{"comments": "Allow firewall access internally to Azure","type": "Microsoft.Sql/servers/firewallRules","kind": "v12.0","name": "[variables('fullSQLServerFirewallRulesAllowAllAzureIPs')]","apiVersion": "2014-04-01-preview","location": "[resourceGroup().location]","properties": {"startIpAddress": "","endIpAddress": ""},"dependsOn": ["[resourceId('Microsoft.Sql/servers', parameters('sqlServerName'))]"]},{"comments": "Allow firewall access for S1 office","type": "Microsoft.Sql/servers/firewallRules","kind": "v12.0","name": "[variables('fullSQLServerFirewallRulesAllowS1Office')]","apiVersion": "2014-04-01-preview","location": "[resourceGroup().location]","properties": {"startIpAddress": "[parameters('sqlServerFirewallRulesS1OfficeStartIPAddr')]","endIpAddress": "[parameters('sqlServerFirewallRulesS1OfficeEndIPAddr')]"},"dependsOn": ["[resourceId('Microsoft.Sql/servers', parameters('sqlServerName'))]"]},{"comments": "Create the storage account to hold automated SQLDB backups.","type": "Microsoft.Storage/storageAccounts","sku": {"name": "[parameters('storageSQLBackupsSkuName')]"},"kind": "Storage","name": "[parameters('storageSQLBackupsName')]","apiVersion": "2016-01-01","location": "[resourceGroup().location]","tags": {"supportContact": "[parameters('tagSupportContact')]","billingCategory": "[parameters('tagBillingCategory')]"},"properties": {"encryption": {"keySource": "Microsoft.Storage","services": {"blob": {"enabled": true}}}},"dependsOn": []},{"comments": "Create the storage account to hold SQL diagnostics data.","type": "Microsoft.Storage/storageAccounts","sku": {"name": "[parameters('storageSQLDiagnosticsSkuName')]"},"kind": "Storage","name": "[parameters('storageSQLDiagnosticsName')]","apiVersion": "2016-01-01","location": "[resourceGroup().location]","tags": {"supportContact": "[parameters('tagSupportContact')]","billingCategory": "[parameters('tagBillingCategory')]"},"properties": {"encryption": {"keySource": "Microsoft.Storage","services": {"blob": {"enabled": true}}}},"dependsOn": []},{"comments": "App Service Plan","apiVersion": "2015-08-01","name": "[parameters('webAppServicePlanName')]","type": "Microsoft.Web/serverfarms","kind": "app","location": "[resourceGroup().location]","tags": {"supportContact": "[parameters('tagSupportContact')]","billingCategory": "[parameters('tagBillingCategory')]"},"sku": {"name": "[parameters('webAppServicePlanSkuName')]","capacity": "[parameters('webAppServicePlanCapacity')]"},"properties": {"name": "[parameters('webAppServicePlanName')]","numberOfWorkers": 1}},{"comments": "Web App","apiVersion": "2015-08-01","name": "[parameters('webAppName')]","type": "Microsoft.Web/sites","kind": "app","location": "[resourceGroup().location]","dependsOn": ["[resourceId('Microsoft.Web/serverFarms/', parameters('webAppServicePlanName'))]"],"tags": {"supportContact": "[parameters('tagSupportContact')]","billingCategory": "[parameters('tagBillingCategory')]"},"properties": {"name": "[parameters('webAppName')]","hostNames": ["[variables('fullWebAppURL')]"],"enabledHostNames": ["[variables('fullWebAppURL')]","[variables('fullWebAppSCMURL')]"],"hostNameSslStates": [{"name": "[concat(parameters('webAppName'), variables('fullWebAppURL') )]","sslState": 0,"ipBasedSslState": 0},{"name": "[concat(parameters('webAppName'), variables('fullWebAppSCMURL') )]","sslState": 0,"ipBasedSslState": 0}],"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('webAppServicePlanName'))]"}}]}

6. Outputs

The Outputs section returns values when the deployment is completed. This can be useful if you are doing linked deployments.

More information about ARM template sections can be found here.

Starting a Deployment

Azure Portal

One way to do a deployment is within the Azure Portal. If you click the + button at the top left, then search for “Template deployment” you can then type or copy the syntax into the online editor:

Azure Portal ARM Deployment

Visual Studio

Another option is to start the deployment via right-click in the ARM Visual Studio project:

Visual Studio ARM Deployment

You will be prompted for the following choices. Note you select a resource group to deploy to, along with the deployment template file (ARMResources.json), and the parameters file (devvalues.parameters.json).

Visual Studio ARM Deployment

Here we can verify the parameter values, or change one at the last moment if necessary:

Visual Studio ARM Deployment

This is the final prompt for the administrator password since the value is not stored in a parameter:

Visual Studio ARM Deployment


The other option for initiating a deployment is via PowerShell. In this case, PowerShell refers to the JSON files. Here is an example:

    Kicks off ARM template deployment
    Prerequisite 1: ARM template created & in the deployment history drop zone
    Prerequisite 2: Parameters file created & in the deployment history drop zone
    File Name  : DeployFromFileDrop.ps1
    Author     : Melissa Coates
    Notes      : 
    Supporting information:
#Input Area
$subscriptionName = 'InsertSubscriptionName'
$resourceGroupName = 'SQLSkillsWaitsLibraryRGDev'
$deploymentName = 'SQLSkillsSQLWaitsDeployment'
$templateFilePath = 'O:\Deployments\SQLWaits\2017-06-07\ARMResources.json'
$parameterFilePath = 'O:\Deployments\SQLWaits\2017-06-07\DevValues.parameters.json'
#Manual login into Azure
#Login-AzureRmAccount -SubscriptionName $subscriptionName
#Test the ARM template deployment
#Test-AzureRmResourceGroupDeployment `
#  -ResourceGroupName $resourceGroupName `
#  -TemplateFile $templateFilePath `
#  -TemplateParameterFile $parameterFilePath
#ARM template deployment
New-AzureRmResourceGroupDeployment `
  -Name $deploymentName `
  -ResourceGroupName $resourceGroupName `
  -TemplateFile $templateFilePath `
  -TemplateParameterFile $parameterFilePath

Monitoring Deployment Progress

Azure Portal

In the Azure portal, you can check the status of a deployment (and history) from “Deployments” within the resource group:

Monitor ARM Deployment in Azure Portal

Visual Studio

If you are deploying from within Visual Studio, you can check the Output window. If there is an error, it will be shown.

Monitor ARM Deployment in Visual Studio

You can also grab copies of the deployment files from the \bin\ folder underneath where you keep your Visual Studio project. This is useful for tracking history of deployments, or to use for the next deployment (to Test or Prod, for instance).

Limitations and Rules for ARM Templates

A few final things to be aware of when getting started with ARM templates:

  • Certain resources are not Resource Manager-enabled (most are enabled).
  • Certain resources can’t yet be exported from the automation script in the Azure Portal (you’ll see a message the top if this is the case).
  • Certain regions are not supported for certain resources.
  • There are API versions which are supported for each resource.
  • Unique names (across all of Azure) are required for certain services (and rules for requiring lower case if it is a URI).

More info about what is supported can be found here: Resource Manager Supported Services.

PC Setup

Prerequisites in order to perform the deployments discussed in this post:

  1. Visual Studio 2015
    • I’m still using VS 2015 now rather than 2017 because not all Azure & BI-related extensions are available at the time of this writing.
    • Once this is installed you will see the Azure Resource Manager project type.
  2. Azure SDK for Visual Studio 2015
    • Specifically what you are looking for here is Microsoft Azure Tools, which comes as part of the SDK.
  3. Azure PowerShell
    • Needed to run the AzureRM PowerShell scripts.
  4. PowerShell Tools for Visual Studio
    • Useful if you want to use all .ps1 scripts rather than ARM templates (note that .ps1 files can be stored in an ARM project too).


Hopefully you found this getting started guide helpful. There’s a lot more that can be accomplished with respect to automating Azure deployments. For instance, you can link to other templates, define conditions, iterate through a loop, return outputs to serve as inputs to another process, define nested templates to deploy to >1 resource group, just to name a few more advanced scenarios you can do.

Here are a few links to more information:

Thwack - Symbolize TM, R, and C