Deploying Applications to Azure Virtual Machine Scale Sets

This blog post shows how you can deploy an application from Visual Studio Team Services to Azure Virtual Machine Scale Set.  An application running on a VM Scale Set is typically deployed in one of the two ways:

  • Install new software on a platform image at deployment time by using VM extensions.
  • Create a custom VM image that includes both the OS and the application in a single VHD

Creating a custom image approach, also known as immutable deployments has its advantages. It is predictable, as you are promoting the image which you have already tested. It is also easy to scale and makes rollback to a well-known previous state easier. Another important scenario is when you want to scale out, the VM extension based approach can result in a slow scale out because the extensions will run on a new VM each time it is created. If the scale set is based on a custom image, any new VM is a copy of the source custom image and the scale out will be faster as the prerequisites and application are already installed.

In this example, we will first build and test a NodeJs application and then use the new Build immutable machine image task to build custom Ubuntu 16.04 VHD image which has NGINX, PM2 and the NodeJs application installed and configured. This image can then be used for Azure Virtual Machine Scale Sets deployment.

VM Scale Sets - End to End Continuous Delivery workflow


Download/fork our NodeJs sample app. Upload your code to Team Services or you’re on-premises Team Foundation Server: either push your code to Git or check in your code to TFVC.


On the Tasks or Build tab, add these steps.

Package: npm install Install your npm package dependencies.

  • Command:  install
  • Set the working folder to the folder where your application code is committed in the repository

For example, in case of sample app it will be NodejsWebApp1

Build: Gulp Transpiles typescript to JavaScript.

  • Gulp File Path: Set the Gulp file path. For example, in case of sample app it will be NodejsWebApp1/gulpfile.js
  • Advanced section, set the working folder to the folder where your gulpfile.js is located
Package: npm test Test your application.

  • Command:  test
  • Set the working folder to the folder where your application code is committed in the repository

For example, in case of sample app it will be NodejsWebApp1 and a Mocha test will be run

Build: Publish Build Artifacts Publish the build outputs.

  • Path to Publish: Use variables or $(Build.ArtifactStagingDirectory) Folder or file path to publish. Either fully qualified path or relative to repo root. For example, in case of sample app it will be NodejsWebApp1
  • Artifact name: drop
  • Artifact Type: Server

Enable continuous integration (CI)

On the Triggers tab, enable Continuous integration (CI). This tells the system to queue a build whenever someone on your team commits or checks in new code.

Save, queue, and test the build

Save and queue the build. Once the build is done, click the link to the completed build (for example, Build 1634), click Artifacts, and then click Explore to see the files produced by the build.


  • Open the Releases tab of the Build & Release hub, open the + drop-down in the list of release definitions, and choose Create release definition with empty definition.
  • Select the build definition you created earlier as the source of artifact to be deployed.

Now add these steps. Bake Immutable image for VMSS and Azure PowerShell. The Bake Immutable image for VMSS uses Packer to create a VHD. The whole bake process involves:

  • Creating a new Virtual Machine with the selected base OS
  • Installing all the prerequisites and application on the VM by using a deployment script
  • Creating a VHD and storing it in the Azure storage account
  • Deleting the new Virtual Machine which was created


Bake Immutable image for VMSS
  • Packer template: You can bring your own packer configuration json or use the auto generate feature where the task generates a packer template for you. In this example let us use the auto generated packer configuration.
  • Azure subscription: Select the Azure service connection you want to use
  • Storage location: The location of storage account where the VHD will be stored. This should be the same location where the Virtual Machine Scale Set is or will be created.
  • Base Image Source: You can either choose from a curated gallery of OS images or provide url of your custom image. For this example, let us choose Ubuntu 16.04 LTS
  • Deployment Package: Specify the path for deployment package directory relative to $(System.DefaultWorkingDirectory). For example, in case of sample app it will be $(System.DefaultWorkingDirectory)/Packer-NodeJs/drop
  • Deployment Script: Specify the relative path to PowerShell script(for Windows) or shell script(for Linux) which deploys the package. This script should be contained in the deployment package path selected above. For example, in case of sample app it will be Deploy/ubuntu/  In the sample app deployment script takes care of installing curl, Node.js, NGINX, PM2, copying over the application and then configuring NGINX and PM2 to run the application.
  • Output – Image URL: Provide a name for the output variable which will store the generated machine image url. For example bakedImageUrl
Azure PowerShell This task will update the Virtual Machine Scale Set with the new VHD which was created.

  • Azure Connection Type: Select Azure Resource Manager
  • Azure RM Subscription: Select the Azure service connection you want to use
  • Script type: Select inline
  • Inline Script: Use the following script to update the Virtual machine Scale Set

# get the VMSS model

$vmss = Get-AzureRmVmss -ResourceGroupName resource_group_name -VMScaleSetName VM_scale_set_name

# set the new version in the model data


# update the virtual machine scale set model

Update-AzureRmVmss -ResourceGroupName resource_group_name -Name resource_group_name -VirtualMachineScaleSet $vmss

You can use variables to pass on the resource group and Virtual Machine Scale Set names.

  • Type a name for the new release definition and, optionally, change the name of the environment from Default Environment to Dev. Also, set the deployment condition on the environment to “Automatically start after release creation”.
  • Save the new release definition. Create a new release and verify that the application has been deployed correctly.

Packer configuration template

The Bake immutable image for VMSS task makes it easy for users who are new to immutable VHD based deployments to use Packer without learning concepts like provisioners and builders. If you are deploying to Virtual Machines by using deployment scripts then you can try out this task and use it for either creating new Virtual Machine instances or creating/updating Virtual Machine Scale Sets.

The auto generate mode of the task generates the packer configuration with:

  • Builder for Azure
  • Provisioners depends on the type of base OS selected. For Linux, it is shell script and for Windows it is PowerShell script. The deployment script provided is used by the provisioner.

A custom packer configuration json can also be used.

Setup Continuous Delivery from Azure portal

The entire build and release Continuous delivery can be setup from the Azure portal as well by using the Continuous Delivery option available in the Virtual Machine scale set blade.

Virtual machine Scale Sets - Setup Continuous Delivery from Azure Portal

There are few things you should keep in mind while setting immutable deployments. For example:

  1. The Azure storage account (which is used as VHD storage) should be in the same location as the VM Scale Set which will get updated by the generated image
  2. If the VM Scale set was created by using a platform image, then it cannot be updated by using Custom image.