If you’ve been spending much time with Linux VMs, you may have encountered Cloud-Init. Cloud-Init is an easy way to specify the configuration which needs to be applied to a deployed virtual machine. Executed when the machine is first provisioned, it can easily configure the environment on your behalf.

The process of using Cloud-Init with ARM has been documented several times on the Microsoft site. In each case, it tends to rely on base64 encoding the template and including it into your ARM template. This ensures that it is deployed automatically as part of the virtual machine.

While this is a viable option, I prefer to make sure that my ARM templates are as easy to maintain as possible. This means that I prefer to make the content of the template easily readable and editable to support development processes. This is even more important if I have one or more complex Cloud-Init templates. I often prefer to have the Cloud-Init sitting side-by-side with my ARM templates rather than embedded within the template itself. This makes it easier to develop and test the file.

There’s a trick that makes this particularly easy.

In the ARM template, I include the following variable definition:

"variables" : {
    "customData": "[concat('#include\n',


Typically, the _artifactsLocation parameter is configured to be the same source URL that contains your deployment contents. If you’re using Visual Studio or PowerShell, this value is automatically populated for you as part of the deployment process.

I use an uri function to ensure that a valid URI is being generated using the base URL and Cloud-Init.txt. If there is an associated SAS Token, it is concatenated to the URL.

This process creates a variable called customData which looks like the following:


If you’re new to Cloud-Init, using a #include header may seem unusual. Cloud-Init supports several directives which control how the file will be processed. While the most common is #cloud-config, others can be used to alter the overall process. The #include directive is followed by a list of files which should be downloaded and evaluated by Cloud-Init. By including my own cloud-config file in that list, I ensure that Cloud-Init will download it and run it. At the same time, I’m not forced to encode and embed it into my ARM template.

To utilize this file, we need to include the base64 content in the osprofile for our virtual machine:

 "osProfile": {
     "customData": "[base64(variables('customData'))]",

You’ll notice that I took advantage of the base64 function to encode the customData variable that we created above. Azure will place this content on the VM and then execute Cloud-Init. This process will then download the referenced file (cloud-init.txt) and execute the commands contained within it.

As you can see, it’s not difficult to get started with Cloud-Init, and it’s surprisingly easy to utilize standalone files in your process.

Happy DevOp’ing!