azure vm with packer

create vm with packer 



Create a custom VHD (virtual hard disk) image that contains the image that you like. Store this image as a blob in Azure.
The stored vhd image has an associated json file, this json file can act as a template for creating actual Azure VM’s.
This seems very similar like the FROM tag in a Dockerfile, create your vm based on a template in your Azure Blob storage.


Sometimes the default templates in the Azure marketplace are not according to specific corporate or customer standards.
A company likes to have the freedom to have an alternate image available.
With packer you can create your own image by manipulating the image with post-processors and provisioners. This image will be stored on Azure so you don’t need to download or upload huge vhd images to the Azure blob.

Another cool feature is that you can create an image for Azure or AWS and have this image converted with the post-processor to a VirtualBox or Vagrant Image.This way you can run your image locally as well.


This assumes the following:

  • you have an azure subscription
  • you are logged in with an account (linked to the azure subscription) that has the role “Account admin”
  • you have a shell command line 🙂
For packer you need at least have the following information:




 application id 


 1 time generated key in base64 format


 main subscription id


 tenant id of app


resource group where the storage account can be found


storage account for storing the VHD


login with azure login
Packer uses the azure-arm builder for creating the VHD on azure, this asumes you are working in the new portal mode ARM.
If not sure, set this mode with the following command:  azure config mode arm
For creating a VHD in Azure, Packer needs to authenticate via a SERVICE PRINCIPAL.
A Service principal in Azure is generated by an Active Directory application, this app can be generated via this command:
azure ad app create –name [NAME OF APP] –home-page [AN IMAGINARY URL PROVIDED BY YOU] –identifier-uris [AN IMAGINARY URL PROVIDED BY YOU] –password
for e.g.
azure ad app create —name chris1-packer-auth-app —home-page http://chris1-packer-auth-app —identifier-uris http://chris1-packer-auth-app/id —password
the example will output something like this:

+ Creating application chris1-packer-auth-app                                  

data:    AppId:                   7a0cdb14-28f0-448c-aafb-39d5bb41f45c

data:    ObjectId:                c735a209-ff11-445e-ba16-9a5dca9c0d0a

data:    DisplayName:             chris1-packer-auth-app

data:    IdentifierUris:          0=http://chris1-packer-auth-app/id

data:    ReplyUrls:              

data:    AvailableToOtherTenants:  False

info:    ad app create command OK

Now that you have the Azure AD Application, you can generate a Service principal via this command:
azure ad sp create [AppID]
for e.g.
azure ad sp create 7a0cdb14-28f0-448c-aafb-39d5bb41f45c
the example will output something like this:

info:    Executing command ad sp create

+ Creating service principal for application 7a0cdb14-28f0-448c-aafb-39d5bb41f45c

data:    Object Id:               62cddb13-ae71-4513-a270-6f130c3624b4

data:    Display Name:            chris1-packer-auth-app

data:    Service Principal Names:

data:                             7a0cdb14-28f0-448c-aafb-39d5bb41f45c

data:                             http://chris1-packer-auth-app/id

info:    ad sp create command OK

You are done with the AUTHENTICATION part, this principal now needs to be AUTHORIZED, meaning it needs to have the proper rights to create the packer VHD.
azure role assignment create –objectId [SERVICE PRINCIPAL ID] -o Contributor -c /subscriptions/[SUBSCRIPTION ID]/
for e.g.
azure role assignment create –objectId 62cddb13-ae71-4513-a270-6f130c3624b4 -o Contributor -c /subscriptions/4adba00c-de3d-4526-a290-xxxxxxxxxxxx/
After the azure role assignment create … command you will see something like this:


info:    Executing command role assignment create

+ Finding role with specified name                                             

|data:    RoleAssignmentId     : /subscriptions/4adba00c-de3d-4526-a290-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleAssignments/6eee457a-eef3-4ca6-9d73-0b2c7f10831c

data:    RoleDefinitionName   : Contributor

data:    RoleDefinitionId     : b24988ac-6180-42a0-ab88-20f7382dd24c

data:    Scope                : /subscriptions/4adba00c-de3d-4526-a290-xxxxxxxxxxxx

data:    Display Name         : chris1-packer-auth-app

data:    SignInName           :

data:    ObjectId             : 62cddb13-ae71-4513-a270-6f130c3624b4

data:    ObjectType           : ServicePrincipal



info:    role assignment create command OK


For this you need to access the old portal,this will be ported in the new portal soon. Go to, navigate to the Active Directory icon and then choose the Application tab. You will see something like this:


You can see the application that we created in the example: chris1-packer-auth-app, select it and navigate to they KEYS section:


Select the duration of the key and press the save button, they Key will then be temporary visible. Please save this key in a proper place as this will be visible only ONCE!!!


If you do not know your subscription ID, type the following: azure account list you will see something like this:

info:    Executing command account list

data:    Name                                  Id                                    Current  State  

data:    ————————————  ————————————  ——-  ——-

data:    Microsoft Azure Internal Consumption  xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  false    n/a    

data:    Visual Studio Enterprise              4adba00c-de3d-4526-a290-xxxxxxxxxxxx  true     Enabled

info:    account list command OK


pick the ID from the one that is active!


If you are logged in and you type: azure account show —json you will see somehing like this:



    “id”: “4adba00c-de3d-4526-a290-0c4fa2421f84”,

    “name”: “Visual Studio Enterprise”,

    “user”: {

      “name”: “”,

      “type”: “user”


    “tenantId”: “35ef1b94-b4b2-413e-bb3d-xxxxxxxxxxxx“,

    “state”: “Enabled”,

    “isDefault”: true,

    “registeredProviders”: [],

    “environmentName”: “AzureCloud”




you can either choose an existing resource group or create one with the following command:
azure group create –location [NAME OF LOCATION] [NAME OF RESOURCE GROUP]
for e.g.
azure group create –location “West Europe” chris1-packer-resources
you will see something like this:

info:    Executing command group create

+ Getting resource group chris1-packer-resources                               

+ Creating resource group chris1-packer-resources                              

info:    Created resource group chris1-packer-resources

data:    Id:                  /subscriptions/4adba00c-de3d-4526-a290-xxxxxxxxxxxx/resourceGroups/chris1-packer-resources

data:    Name:                chris1-packer-resources

data:    Location:            westeurope

data:    Provisioning State:  Succeeded

data:    Tags: null


info:    group create command OK


you can either choose an existing storage account or create one with the following command:
azure storage account create [UNIQUE NAME OF YOUR STORAGEACCOUNT]
for e.g.
azure storage account create chris1packerstorage
this will trigger some dialogues, you will see something like this:

info:    Executing command storage account create

+ Getting type                                                                 

help:    Account Type: 

  1) LRS

  2) ZRS

  3) GRS

  4) RAGRS

  5) PLRS

  : 4

+ Getting locations                                                            

help:    Location: 

  1) East US

  2) South Central US

  3) Central US

  4) North Europe

  5) West Europe

  6) Southeast Asia

  : 5

Resource group name: chris1-packer-resources

+ Creating storage account                                                     

info:    storage account create command OK


Now we have all the information to fill the fields for the packer json build file, here is an example of a packer file which builds a centos machine.


  “builders”: [{

    “type”: “azure-arm”,

    “client_id”: “XXX_YOUR_CLIENTID_XXX”,

    “client_secret”: “XXX_YOUR_GENERATED_KEY_XXX”,

    “subscription_id”: “XXX_YOUR_SUBSCRIPTIONID_XXX”,

    “tenant_id”: “XXX_YOUR_TENANTID_XXX”,

    “resource_group_name”: “XXX_DESIRED_RESOURCEGROUP_XXX”,

    “storage_account”: “XXX_DESIRED_STORAGEACCOUNT_XXX”,

    “capture_container_name”: “packer-images”,

    “capture_name_prefix”: “packer”,

    “ssh_username”: “chris”,

    “ssh_password”: “MaartApril2016”,

    “image_publisher”: “OpenLogic”,

    “image_offer”: “CentOS”,

    “image_sku”: “7.1”,

    “ssh_pty”: “true”,

    “location”: “West Europe”,

    “vm_size”: “Standard_A2”


  “provisioners”: [{

      “type”: “shell-local”,

      “command”: “echo hello_world>/tmp/hello.txt 2>/dev/null”



build the VHD with the following command: packer build [ your filename.json ]
for e.g.
packer build packer-cantos-example.json
The Interesting part about this is the provisioning part, here you can call scripts that alter your environment to your liking. For example you can remount your folders to your own likings by using a chroot solution.
The reference to your packer created VHD can be found by navigating to: and then go to
resource groups —> [ name of your resource group ]—> [ name of your storage account ]—> blob service —> system —> Microsoft.Compute —> Images —>[ name of your defined packer name ( capture_container_name ) ]
in my example this will be
resource groups —> chris1-packer-resources —> chris1packerstorage —> blob service —> system —> Microsoft.Compute —> Images —> packer-images
click on the packer vm template (the json file) and then select download:
the json file that is associated with the created VHD file will appear now, just copy the storageProfile part for now, for eg.
        "storageProfile": {
          "osDisk": {
            "osType": "Linux",
            "name": "packer-osDisk.63914f53-ea33-4f25-a540-a273e46784f9.vhd",
            "createOption": "FromImage",
            "image": {
              "uri": ""
            "vhd": {
              "uri": ""
            "caching": "ReadWrite"

Create VM

create a json file with the following content:


    “$schema”: “”,

    “contentVersion”: “”,

    “parameters” : {

        “dnsNameForPublicIP” : {

            “type” : “string”,

            “defaultValue”: “[  SOME VALUE  ]”


        “vmSize”: {

            “type”: “string”,

            “defaultValue”: “Standard_A2”


        “publicIPAddressName”: {

            “type”: “string”,

            “defaultValue” : “[  SOME VALUE  ]”


        “vmName”: {

            “type”: “string”,

            “defaultValue” : “[  SOME VALUE  ]”



            “type” : “string”,

            “defaultValue” : “[  SOME VALUE  ]”



            “type” : “string”,

            “defaultValue”:”[  SOME VALUE  ]”


    “variables”: {

“subnet1Name”: “Subnet-1”,

        “subnet2Name”: “Subnet-2”,

        “subnet1Prefix” : “”,

        “subnet2Prefix” : “”,

        “publicIPAddressType” : “Dynamic”,


        “subnet1Ref” : “[concat(variables(‘vnetID’),’/subnets/’,variables(‘subnet1Name’))]”

“resources”: [{
“apiVersion”: “2016-03-30”,
“type”: “Microsoft.Network/publicIPAddresses”,
“name”: “[parameters(‘publicIPAddressName’)]”,

         “location”: “[resourceGroup().location]”,

         “properties”: {

              “publicIPAllocationMethod”: “[variables(‘publicIPAddressType’)]”,

              “dnsSettings”: {

                   “domainNameLabel”: “[parameters(‘dnsNameForPublicIP’)]”





      “apiVersion”: “2016-03-30”,

      “type”: “Microsoft.Network/virtualNetworks”,

      “name”: “[parameters(‘virtualNetworkName’)]”,

      “location”: “[resourceGroup().location]”,

      “properties”: {

       “addressSpace”: {

          “addressPrefixes”: [




        “subnets”: [


           “name”: “[variables(‘subnet1Name’)]”,

            “properties” : {

                “addressPrefix”: “[variables(‘subnet1Prefix’)]”




            “name”: “[variables(‘subnet2Name’)]”,

            “properties” : {

                “addressPrefix”: “[variables(‘subnet2Prefix’)]”







        “apiVersion”: “2016-03-30”,

        “type”: “Microsoft.Network/networkInterfaces”,

        “name”: “[parameters(‘nicName’)]”,

        “location”: “[resourceGroup().location]”,

        “dependsOn”: [

            “[concat(‘Microsoft.Network/publicIPAddresses/’, parameters(‘publicIPAddressName’))]”,

            “[concat(‘Microsoft.Network/virtualNetworks/’, parameters(‘virtualNetworkName’))]”


        “properties”: {

            “ipConfigurations”: [


                “name”: “ipconfig1”,

                “properties”: {

                    “privateIPAllocationMethod”: “Dynamic”,

                    “publicIPAddress”: {

                        “id”: “[resourceId(‘Microsoft.Network/publicIPAddresses’,parameters(‘publicIPAddressName’))]”


                    “subnet”: {

                        “id”: “[variables(‘subnet1Ref’)]”








        “apiVersion”: “2016-03-30”,

        “type”: “Microsoft.Compute/virtualMachines”,

        “name”: “[parameters(‘vmName’)]”,

        “location”: “[resourceGroup().location]”,

        “dependsOn”: [

            “[concat(‘Microsoft.Network/networkInterfaces/’, parameters(‘nicName’))]”


        “properties”: {

            “hardwareProfile”: {

                “vmSize”: “[parameters(‘vmSize’)]”





            “osProfile”: {

                “computername”: “[parameters(‘vmName’)]”,

                “adminUsername”: “[  SOME VALUE  ]”,

                “adminPassword”: “[  SOME VALUE  ]”


            “networkProfile”: {

                “networkInterfaces” : [


                    “id”: “[resourceId(‘Microsoft.Network/networkInterfaces’,parameters(‘nicName’))]”









Replace the SOME VALUE  parts with values you like or remove the defaultValue…then you will be prompted.
Replace the  YOUR COPIED STORAGE PROFILE GOES HERE with the storageProfile part from the previous step.

To create your VM with the json deployment type the following:

azure group deployment create –template-file [ Name of your json template ] [ Name of your resource group]
for eg.
azure group deployment create –template-file azure-centos-example.json chris1-packer-resources

After Succesful deployment you will see:

info:    Executing command group deployment create

info:    Supply values for the following parameters

+ Initializing template configurations and parameters                          

+ Creating a deployment                                                        

info:    Created template deployment “azure-centos-example”

+ Waiting for deployment to complete                                           

data:    DeploymentName     : azure-centos-example

data:    ResourceGroupName  : chris1-packer-resources

data:    ProvisioningState  : Succeeded

data:    Timestamp          :

data:    Mode               : Incremental

data:    CorrelationId      : 5c3d80c1-8493-448e-975e-1b9b921fa2cd

data:    DeploymentParameters :

data:    Name                 Type    Value             

data:    ——————-  ——  ——————

data:    dnsNameForPublicIP   String  chris1-coe-dnspip 

data:    vmSize               String  Standard_A2       

data:    publicIPAddressName  String  chris1-coe-pipname

data:    vmName               String  chris1-coe        

data:    virtualNetworkName   String  chris1-vnname     

data:    nicName              String  chris1-nicname    

info:    group deployment create command OK

with the values used in my example I will see the following stuff in my created resource group:
if you click on the virtual machine icon (chris1-coe), you will see something like this:
of course you can now login with the DNS hostname, and username password which was provided by your template:

macbook-air-chris:packer chris$ ssh

The authenticity of host ‘ (’ can’t be established.

ECDSA key fingerprint is SHA256:jTX7XqHtukNXLO5iOcvNCGXs+7/hc30qM4gALP4X2Go.

Are you sure you want to continue connecting (yes/no)? yes

Warning: Permanently added ‘,’ (ECDSA) to the list of known hosts.’s password: 

[chris@pkrvmlth9k2w172 ~]$ hostname -f

Leave a Reply

Your email address will not be published. Required fields are marked *