How to create and deploy a development machine on Azure using Azure Resource Manager template in Visual Studio

Azure Resource manager have been around for a while now and it made provisioning quite easy and convenient. A couple of persons have asked me how to create and manage development machines when I speak about dev/tests environment in Azure, I'll explain you one way of doing this.

If you follow this step by step guide, at the end, you'll get a development box running Visual Studio 2015 community edition. Your VM will be running on Azure Premium Storage.

We'll use a premium storage account in our VM template. It is not that expensive for a 127GB disk and we'll have greater performance compared with standard storage.

Pre-requisites

  • An Azure subscription
  • Visual Studio 2015 and Azure SDK 2.6 or later

This article is not an introduction to Azure Resource Manager, if you need to catch up or learn on ARM, please see the References section at the end of the article.

Steps to create and customize your ARM template

Step 1

Launch Visual Studio 2015 and create a new Azure Resource Group Project.
Create Azure Resource Group project

Step 2

Choose the Windows Virtual Machine template and click OK
ARM Windows virtual machine template

This will create you a project in Visual Studio with a couple of files. The most important files are the one under the Templates folder, your deployment template and deployment parameters files.

Files created by Azure Resource Group project

Step 3

Open the template file named: WindowsVirtualMachine.json

Step 4

Locate the windowsOSVersion parameter and replace the whole parameter with the following:

"imageSku": {
    "type": "string",
    "defaultValue": "VS-2015-Community-AzureSDK-2.7-WS2012R2",
    "allowedValues": [
        "VS-2015-Community-AzureSDK-2.7-WS2012R2",
        "VS-2015-Enterprise-AzureSDK-2.7-WS2012R2"
    ],
    "metadata": {
        "description": "The Visual Studio version for the VM. This will pick a fully patched image of this Visual Studio version. Allowed values: VS-2015-Community-AzureSDK-2.7-WS2012R2, VS-2015-Enterprise-AzureSDK-2.7-WS2012R2."
    }
}

Add the following parameter at the end of the parameters section:

"resourcesLocation": {
  "type": "string",
  "defaultValue": "East US 2",
  "allowedValues": [
    "West US",
    "East US 2",
    "West Europe",
    "East China",
    "Southeast Asia",
    "West Japan",
    "Australia East"
  ],
  "metadata": {
    "description": "The location of resources created in this template. Default value is East US 2."
  }
}

Step 5

Change the values of the following variables:

"vmName": "MyDevBoxVM"
"location": "[parameters('resourcesLocation')]"
"OSDiskName": "osdisk-devbox"
"imageOffer": "VisualStudio"
"imagePublisher": "MicrosoftVisualStudio"
"nicName": "devBoxNic"
"publicIPAddressName": "devBoxPublicIP"
"storageAccountType": "Premium_LRS"
"vmName": "MyVS2015DevBoxVM"
"vmSize": "Standard_DS2"

The important changes to understand in the variables above are:

  • imagePublisher: We changed this because we want to use the Visual Studio images instead of Windows images
  • ImageOffer: Again, we want to use a Visual Studio image offering
  • storageAccountType: We use a premium storage account instead of a regular storage account
  • vmSize: We need to use a DS serie VM, only them at the moment support premium storage.
  • location: At the time of writing this article (July 2015), Premium Storage is not available in all Azure locations, see Important Things to Know About Premium Storage in References section for more information.

Step 6

Replace the imageReference section of the virtual machine resource with the following:

"imageReference": {
  "publisher": "[variables('imagePublisher')]",
  "offer": "[variables('imageOffer')]",
  "sku": "[parameters('imageSku')]",
  "version": "latest"
}

Resulting template

You should end up with the following result for your template:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "adminPassword": {
      "metadata": {
        "description": "Password for the Virtual Machine."
      },
      "type": "securestring"
    },
    "adminUsername": {
      "metadata": {
        "description": "Username for the Virtual Machine."
      },
      "type": "string"
    },
    "dnsNameForPublicIP": {
      "metadata": {
        "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
      },
      "type": "string"
    },
    "imageSku": {
      "allowedValues": [
        "VS-2015-Community-AzureSDK-2.7-WS2012R2",
        "VS-2015-Enterprise-AzureSDK-2.7-WS2012R2"
      ],
      "defaultValue": "VS-2015-Community-AzureSDK-2.7-WS2012R2",
      "metadata": {
        "description": "The Visual Studio version for the VM. This will pick a fully patched image of this Visual Studio version. Allowed values: VS-2015-Community-AzureSDK-2.7-WS2012R2, VS-2015-Enterprise-AzureSDK-2.7-WS2012R2."
      },
      "type": "string"
    },
    "newStorageAccountName": {
      "metadata": {
        "description": "Unique DNS Name for the Storage Account where the Virtual Machine's disks will be placed."
      },
      "type": "string"
    },
    "resourcesLocation": {
      "allowedValues": [
        "West US",
        "East US 2",
        "West Europe",
        "East China",
        "Southeast Asia",
        "West Japan",
        "Australia East"
      ],
      "defaultValue": "East US 2",
      "metadata": {
        "description": "The location of resources created in this template. Default value is East US 2."
      },
      "type": "string"
    }
  },
  "resources": [
    {
      "apiVersion": "2015-05-01-preview",
      "location": "[variables('location')]",
      "name": "[parameters('newStorageAccountName')]",
      "properties": {
        "accountType": "[variables('storageAccountType')]"
      },
      "tags": {
        "displayName": "StorageAccount"
      },
      "type": "Microsoft.Storage/storageAccounts"
    },
    {
      "apiVersion": "2015-05-01-preview",
      "location": "[variables('location')]",
      "name": "[variables('publicIPAddressName')]",
      "properties": {
        "dnsSettings": {
          "domainNameLabel": "[parameters('dnsNameForPublicIP')]"
        },
        "publicIPAllocationMethod": "[variables('publicIPAddressType')]"
      },
      "tags": {
        "displayName": "PublicIPAddress"
      },
      "type": "Microsoft.Network/publicIPAddresses"
    },
    {
      "apiVersion": "2015-05-01-preview",
      "location": "[variables('location')]",
      "name": "[variables('virtualNetworkName')]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[variables('addressPrefix')]"
          ]
        },
        "subnets": [
          {
            "name": "[variables('subnetName')]",
            "properties": {
              "addressPrefix": "[variables('subnetPrefix')]"
            }
          }
        ]
      },
      "tags": {
        "displayName": "VirtualNetwork"
      },
      "type": "Microsoft.Network/virtualNetworks"
    },
    {
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [
        "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
        "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
      ],
      "location": "[variables('location')]",
      "name": "[variables('nicName')]",
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
              },
              "subnet": {
                "id": "[variables('subnetRef')]"
              }
            }
          }
        ]
      },
      "tags": {
        "displayName": "NetworkInterface"
      },
      "type": "Microsoft.Network/networkInterfaces"
    },
    {
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [
        "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]",
        "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
      ],
      "location": "[variables('location')]",
      "name": "[variables('vmName')]",
      "properties": {
        "hardwareProfile": {
          "vmSize": "[variables('vmSize')]"
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
            }
          ]
        },
        "osProfile": {
          "adminPassword": "[parameters('adminPassword')]",
          "adminUsername": "[parameters('adminUsername')]",
          "computername": "[variables('vmName')]"
        },
        "storageProfile": {
          "imageReference": {
            "offer": "[variables('imageOffer')]",
            "publisher": "[variables('imagePublisher')]",
            "sku": "[parameters('imageSku')]",
            "version": "latest"
          },
          "osDisk": {
            "caching": "ReadWrite",
            "createOption": "FromImage",
            "name": "osdisk",
            "vhd": {
              "uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]"
            }
          }
        }
      },
      "tags": {
        "displayName": "VirtualMachine"
      },
      "type": "Microsoft.Compute/virtualMachines"
    }
  ],
  "variables": {
    "OSDiskName": "osdisk-devbox",
    "addressPrefix": "10.0.0.0/16",
    "imageOffer": "VisualStudio",
    "imagePublisher": "MicrosoftVisualStudio",
    "location": "[parameters('resourcesLocation')]",
    "nicName": "devBoxNic",
    "publicIPAddressName": "devBoxPublicIP",
    "publicIPAddressType": "Dynamic",
    "storageAccountType": "Premium_LRS",
    "subnetName": "Subnet",
    "subnetPrefix": "10.0.0.0/24",
    "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
    "virtualNetworkName": "MyVNET",
    "vmName": "MyDevBoxVM",
    "vmSize": "Standard_DS2",
    "vmStorageAccountContainerName": "vhds",
    "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]"
  }
}

Step 8

Right-click your project node in Visual studio and choose Deploy / New Deployment...
New ARM template deployment from Visual Studio

  • Choose your Azure subscription
  • Select and existing or a create a new resource group. (Ensure your resource group location support Premium storage) Create resource group

Deploy to Resource group dialog

  • Click Deploy

You'll get prompted for parameters values, specially the adminPassword if you don't click the "save passwords" checkbox.

  • Enter the information and click save.

Pay special attention to the format you use for the dnsNameForPublicIP and newStorageAccountName parameters. They need to be alphanumeric only and lowercase

Deployment parameters

In 4-8 minutes, you'll get a development machine on Azure!

Have fun developing in the cloud!
Download the Visual Studio 2015 project

References

I am the proud father of two little gems. A beer & wine enthusiasm. For everything else, I work and play with Azure at day, and I am an Azure MVP & Advisor at night.
Montreal, Canada