Azure VM Image Builder - Part 1
Updated: Apr 1
Azure VM Image Builder is a managed service from Microsoft that lets you easily build, customize, and distribute your own custom VM images. All you need to do is create a configuration file which describes the image that you want to build and then you must submit that configuration to the Azure VM Image Builder service. The service will take care of the rest. Azure VM Image Builder is built on HashiCorp’s Packer software.
Now that the intro is out of the way, I’m just going to jump right into the technical details, as this blog post is going to be lengthy enough already.
The “configuration” file that I referenced earlier is officially called an Image Template. Originally, the only option available was to create your Image Templates using Infrastructure as Code (IaC) such as Bicep, ARM Templates, or Terraform. Here is a link to Microsoft’s IaC reference page for Image Templates which lists all of the possible options. However, recently Microsoft has added to the option to build your Image Template directly in the Azure Portal.
To oversimplify things, an Image Template answers three major questions:
What’s the source of your new VM image?
What customizations do you want to perform to your new VM image?
Where do you want to store / distribute your new VM image?
I’ll go over all three of these things in more detail below.
Image Template: Source
There are three options that you can use as the source of your custom image:
This is an image from the Azure Marketplace
You must specify a valid Publisher, Offer, Sku, and Version.
Version can be specified as ‘latest’ if you don’t want to give a specific version number
You can use the Az CLI command az vm image list --output table --all to find out info about marketplace images
You can add some filters to your command to list only certain images, for example az vm image list --publisher Canonical --sku gen2 --output table --all
Optionally, if needed by your Marketplace image, you can specify a PlanInfo block, which defines PlanName, PlanProduct, and PlanPublisher
This uses an existing Managed Image that you have in Azure
Managed Images are an entirely different subject which I won’t go into detail on in this blog post. If you want more information on Managed Images then check this link for some documentation from Microsoft
The specified managed image must be located in the same Subscription and Region as your Image Template
You can use the Az CLI command az image list to see available managed images
This uses an existing Image Version that you have stored in an Azure Compute Gallery (formerly known as Shared Image Gallery)
The specified Image Version must be located in the same Region as your Image Template
You can use the Az CLI command az sig image-version list to see available Image Versions.
Image Template: Customizers
Customizers are functions that can be used to, you guessed it, customize your image. You can do things like run a script, reboot, run windows update and more.
You can run multiple customizers. They execute in the order they are listed. If one customizer fails, then the entire customization step fails. In other words, all customizers must succeed for the image build to succeed.
There are five different types of customizers:
This support Linux images and lets you run shell scripts
The script could be stored in a separate file, in which case you must specify the URI to that file. Or the contents of the script can be stored inline in the customizer itself
Do not put sensitive values in your inline shell customizers, as the inline commands can be read by anyone who has access to the Image Template
If your command needs elevated rights, then just use sudo in the command.
This supports Windows images and lets you run PowerShell scripts
Same as above, the script could be stored in a separate file, or commands can be inline with the customizer
Same warning as above, do not put sensitive values in your inline commands!
If your script needs elevated rights, then make sure to set runElevated: true on your customizer
This supports both Linux and Windows images and lets you download a file, either from a GitHub repo or from Azure Storage
You can only download one file. If you need to download more than one file, or download an entire directory, then use a compressed file. After you download the compressed file, you could use a Shell/PowerShell customizer to decompress the file.
This customizer is only good for smaller files that are less than 20 MB. For larger files you should use a Shell customizer and run wget or curl, or a PowerShell customizer and run Invoke-WebRequest
You must specify a destination for the file:
The destination folder must exist already. You could use a Shell/PowerShell customizer to create the path beforehand, if needed
Linux images are only allowed to use a destination somewhere under /tmp
Windows images can use any destination, as long as it already exists
This support Windows images and it restarts a Windows VM
This waits for the VM to come back online before continuing
This allows you to install software which requires a reboot
This supports Windows images and allows you to perform Windows Updates to your image
This customizer is built on an open-source project maintained by the Packer community. It’s not officially supported by Microsoft, however they will still provide best effort support
If there are any outstanding Windows restarts waiting, or any application installs still ongoing, then this customizer will fail. You could use a PowerShell customizer that runs Start-Sleep to give application installs more time, or you could do a WindowsRestart customizer to clear out any pending restarts, before running the WindowsUpdate customizer
Generalize / Deprovision / SysPrep
When all your customizers are complete, Azure VM Image Builder will generalize your image. Generalizing is the process where your image is set up so it can be reused over and over to create multiple VMs. For Windows images, the sysprep command is used. For Linux, the waagent -deprovision command is used.
If the default generalization process does not work for your image, then you can override it and use your own. Just create a script with a specific name and location, and VM Image Builder will use that script instead.
For Windows Images, create a file named c:\DeprovisioningScript.ps1
For Linux Images, create a file named /tmp/DeprovisioningScript.sh
An example of when you might need to use your own custom Sysprep command is when you need to utilize nested virtualization. The default Sysprep command doesn't include /mode:vm, and this property may be required when your image needs the HyperV role installed. If you need to add this command argument, you must override the default Sysprep command.
Image Template - Distributing your image
Azure VM Image Builder supports three distribution targets:
Your customized image is stored in a managed image
The destination RG of the managed image must already exist
If you pick a destination Region that’s different than your Image Template, then this will increase the deployment time as the image must be distributed to that other Region
Your customized image is stored in an Azure Compute Gallery
You can choose to let Azure perform automatic versioning of your image, or you can specify your own explicit image versions
You can specify multiple Regions where the image will be replicated to. At a minimum, you must choose the region where the Image Gallery is located. Note: more regions means more build time because it takes time to replicate to other regions
Your customized image is stored as a VHD file
The VHD is put into a Storage Account inside the staging Resource Group that's automatically created by Azure VM Image Builder (more on this in Part 2). What’s important to know is that you should copy the VHD to a different location as soon as possible. Because if the Image Template is deleted, then this staging Resource Group is also deleted, which includes the storage account containing the VHD
If desired, you can specify multiple distribution targets at the same time. Each target must have a unique value for runOutputName. Azure stores the current state of each distribution target in that runOutputName field.
One last thing that I want to mention is that you must create one or more User-Assigned Managed Identity for the Azure VM Image Builder service to use.
An Identity is required to be used at the Image Template scope. This Identity is used for things like reading source Images, creating your Distribution targets, and accessing Azure Storage Accounts. You can only assign one identity for this purpose.
Here are the permissions needed in order for the Managed Identity to create Managed Images and/or Image Versions:
Here is a link to my GitHub repo where you can find an RBAC role definition that you could use to build a custom role in your tenant.
Optionally, you can also assign one or more Identities that will be attached to the temporary VM that is used to build your image. This is useful if your Customizer scripts need to access KeyVault or other Azure resources. If desired, you can use the same Managed Identity mentioned above for this purpose as well.
There are a few more settings that you can configure in your Image Template which I do not cover here. Some will also be covered in Part 2 of this series. I recommend reading the sources that are referenced below for details on every available option.
So with your IaC code now complete, you have to actually create the Image Template resource. If you wrote your code in Bicep, and you need a little help with deploying it, then have a look at my Deploying Bicep Series.
So now you know how to create an Image Template (for the most part) How do you build your image? And what happens under the hood during your build? That will all be covered in Part 2!