Monthly Archives: July 2021

Getting Started With Packer to Create vSphere Templates – Part 4 – Blocks

Welcome to Part 4 of the Packer Series! In this part we will look at putting together all the block and files we need to deploy a template!

As we have touched upon in earlier parts, we have multiple blocks and files available to us that can be used to make up a complete configuration. We will walk through a complete Source and Build Block here using user defined variables to complete the build. In the final part of this series, I will use a combination of user and environment variables to give you an idea of how you may use this outside of a lab.

Lets start by breaking down a Source Block for a Windows 2019 Core template.

source "vsphere-iso" "win-2019-std-core" {
  CPUs            = var.CPUs
  RAM             = var.RAM
  RAM_reserve_all = var.ram_reserve_all
  boot_command    = var.boot_command
  boot_order      = var.boot_order
  boot_wait       = var.boot_wait
  cluster         = var.vsphere_compute_cluster
  content_library_destination {
    destroy = var.library_vm_destroy
    library = var.content_library_destination
    name    = var.template_library_Name
    ovf     = var.ovf
  datacenter           = var.vsphere_datacenter
  datastore            = var.vsphere_datastore
  disk_controller_type = var.disk_controller_type
  firmware             = var.firmware
  floppy_files         = var.config_files
  folder               = var.vsphere_folder
  guest_os_type        = var.guest_os_type
  insecure_connection  = var.insecure_connection
  iso_paths = [var.os_iso_path,var.vmtools_iso_path]
  network_adapters {
    network      = var.vsphere_portgroup_name
    network_card = var.network_card
  notes        = var.notes
  password     = var.vsphere_password
  communicator = var.communicator
  winrm_password = var.winrm_password
  winrm_timeout  = var.winrm_timeout
  winrm_username = var.winrm_user
  storage {
    disk_size             = var.disk_size
    disk_thin_provisioned = var.disk_thin_provisioned
  username       = var.vsphere_user
  vcenter_server = var.vsphere_server
  vm_name        = var.vm_name
  vm_version     = var.vm_version

All values are passed in via variables in this example. You can see this by the ‘var.<variable_name>’ entry against every configuration line. All variables in this example are user defined variables in a pkrvar.hcl file.

We have configuration for CPU, Memory and disk sizes for instance, then we also have the WinRM username, password and timeout values used for connecting to the operating system after it’s been installed, for use with provisioners.

You can deploy your template as just a ‘normal’ VM Template in the VM and Templates Inventory by using this line:

convert_to_template        = true

Or a using a variable:

convert_to_template             = var.convert_to_template

Or you can deploy to Content Libraries by either removing the “convert_to_template” option or setting it to false, and replacing it with this:

  content_library_destination {

    library = var.content_library_destination
    name    = var.template_library_Name

If you already use Content Libraries, then you are likely going to want to continue to do so.  Or, if you have multiple vCenter’s, you may want to make use of subscribed libraries so you only have to deploy the template once.

To go further you can automatically destroy the original VM after its been uploaded to the Content Library by adding:

destroy = var.library_vm_destroy

And to take it even further, you can add the following to convert the template to an OVF.  OVF’s can be updated in the content library and therefore will be overwritten when you deploy your template again.  This can’t be done with a standard VM template.

ovf     = var.ovf

To bring that all together it looks like this:

  content_library_destination {
    destroy = var.library_vm_destroy
    library = var.content_library_destination
    name    = var.template_library_Name
    ovf     = var.ovf

A key line to point out in this windows example configuration above, is the ‘floppy_files’ option. This option is used to mount a floppy disk with any configuration files or media that you need to reference during the operating system installation. This includes your unattended.xml file, any scripts and any media or drivers such as VMware Paravirtual drivers for the SCSI controller. Checkout Part 2 for more info.

If we were looking at a Linux build, we would see the WinRM options replaced by SSH, like so:

  ssh_password = var.ssh_password
  ssh_timeout  = var.ssh_timeout
  ssh_username = var.ssh_username

A full list of the different configuration options available can be found here.

Now we have defined our source, we now want to deploy it using a build block.

build {
  name    = "win-2019-std-core"
  sources = [""]

  provisioner "powershell" {
    scripts           = var.script_files
  provisioner "windows-update" {
            search_criteria = "IsInstalled=0"
            filters = [
                      "exclude:$_.Title -like '*Preview*'",
            update_limit = 25
  post-processor "manifest" {
    output = "output/out-win-2019-std-core.json"
    strip_path = false

What’s happening in this block, is that we are referencing the source block that contains our configuration based on the name of the source block that we defined earlier, in this case ‘’.

In this example we also have two provisioners being used once the operating system has been installed. Firstly, the Windows-Update-Provisioner which installs the latest Windows updates based on any filters you include. In this example, its configured to exclude any updates with ‘Preview’ in the title and also to limit it to install up to 25 updates.

Secondly, we are making use of the Manifest post-processor. This produces an output that includes information such as build time each time it is run.

      "name": "win-2019-std-core",
      "builder_type": "vsphere-iso",
      "build_time": 1617185954,
      "files": null,
      "artifact_id": "windows-2019-std-core",
      "packer_run_uuid": "865be1fd-0dec-1688-8c89-9252e48d0818",
      "custom_data": null
  "last_run_uuid": "865be1fd-0dec-1688-8c89-9252e48d0818"

All of the above makes up a complete build file that can be deployed with any media or variables you have referenced. The full set of files for this example can be found here.

To give you an example of a non-windows Provisioner, here is a Shell Provisioner for a Linux template:

provisioner "shell" {
    execute_command = "echo '${"var.ssh_password"}' | sudo -S -E bash '{{ .Path }}'"
    scripts         = var.script_files

This executes all scripts that are referenced in the script.files variables.

Now using environment variables, nothing really changes. Your build file will look the same, the only differences will be you won’t provide a value for your declared variable in your pkrvar.hcl file, instead adding the variable to your terminal session. Check out Part 3 for more info. In the final part of this series, I will show an example of using both user defined and environment variables.

That concludes a short run through of the different files in the examples you can find on my GitHub. By no means have I covered everything in those examples or everything you can do with Packer, but this series along with the examples should help you on your way with discovering Packer! There is so much more that can be done using this product to create templates on vSphere as well as multiple other platforms so do head over to to discover more.

In the final part of this series, I am going to try a different content type, video’s! In these, we will run through two end to end template deployments using default values for variables, user defined and environment variables to show how you could use this as part of a workflow.

If you have gotten this far, thanks for sticking with me and I hope you have enjoyed it and found it useful!


Getting Started With Packer to Create vSphere Templates – Part 3 – Variables

Welcome back to part 3 of my Creating vSphere Templates using Packer series, if you missed part 1 or 2, you can find them here and here. In part 3 we will explore variables!

Why would we use variables? Variables allow you to specify customisations to your templating code without having to edit your actual build files. This can be useful when you are reusing code for multiple templates.

There are multiple types of variables that can be used, but we will talk about 2 types of input variables in this blog. They are what I will refer to as; User defined variables and Environment variables. We will talk about both during the blog post and the use cases for each.

Regardless of whether we use a user defined variable or an environment variable, we still need to declare them. This is done in a variable declaration file, so lets start with that!

Variable Declaration

Following the release of Packer version 1.7 the Hashicorp Configuration Language (HCL) format is now the preferred language over JSON. Everything you will see will be in HCL.

The variable declaration file is a pkr.hcl file used to declare any variables you will be using as part of your configuration, be it user defined or environment variables.

Lets take a look at a few of the variable types you can make use of as well as some of the options you can also set.

Variable Type

Here is a few common variable types, you don’t have to define a type at all, but you could then pass the wrong type of data into your config.

  • String – E.g. The templates name or the name of a datastore.
  • Boolean – E.g. A true or false value for whether you are using thin or thick provisioned disks.
  • List – E.g. A list of DNS server IP addresses.

We will see examples of these later on.

Default Value

You can set default values for variables. These values will be used if no other variable value is found in either your pkrvar.hcl file or as an environment variable. Using default values can help reduce the amount of repeat configuration if you use a shared variable definition file.


Another useful option is to be able to provide a description to a variable. This can be useful if you need to add any additional information about the variable or why a particular default has to be set.


You can also mark variables as sensitive for values such as keys, password or usernames etc, however you can mark any variable as sensitive if you have a need to. When a variable is marked as sensitive, it will not be displayed in any of Packers output.

User Defined Variables

Lets take a look at a few examples of declared variables in the variables.pkr.hcl file as well as any values then set for those variables in the user variables file. You will see a couple of examples of variables that have default, type and sensitive options set to give you an idea of some of the use cases.

Lets start with a basic user defined variable:

Variable Declaration – variables.pkr.hclVariable Defination – template.pkrvar.hcl
variable “vsphere_datastore” {}vsphere_datastore = “ds-vsan-01”
variable “vsphere_portgroup_name” {}vsphere_portgroup_name = “dvPG_Demo_DHCP_149”

So in this example, we are declaring that we are going to use variables called ‘vsphere_datastore’ and ‘vsphere_portgroup_name’. We then have values defined for these variables in our pkrvar.hcl file. This can be any data type for the value, as no type has been defined.

Variable Declaration – variables.pkr.hcl Variable Defination – template.pkrvar.hcl
variable “content_library_destination” {
  type    = string
  default = “Images”
Nothing defined = Default value would be used
content_library_destination = “ISOs”

In this example we have declared a variable with the type ‘String’, and also provided a default value. The configuration will use this default if no other value is defined either via a user variable or environment variable, but will be overridden should a variable value be set.

Variable Declaration – variables.pkr.hcl Variable Defination – template.pkrvar.hcl
variable “vsphere_server” {
  type    = string
  default = “vm-vcsa-01.smt-lab.local”
  description = “vCenter Server FQDN”
Nothing defined = Default value would be used
vsphere_server = “vcsa-02.smt-lab.local”

Here is an example again using a type and default values, but also providing a description to provide some additional information. Like the previous example, not providing a variable value either in the pkrvar.hcl file or in the terminal session as an environment variable, would result in the default value being used.

Variable Declaration – variables.pkr.hcl Variable Defination – template.pkrvar.hcl
variable “vsphere_user” {
  type      = string
  default   = “packer_build@smt-lab.local”
  sensitive = true
Nothing Defined

In this final example we are using the sensitive option. This will stop the value being displayed in any Packer output. Again, it’s using a default value, so you do not need to define a value in the pkrvar.hcl file unless you want to use a different value to this default.

Environmental Variables

Now let’s take a look at environment variables. These are especially useful if you want to use Packer as part of a workflow or automation pipeline, or to pass in secrets (passwords or keys) into the workflow from a secret management tool.

You still declare all your variables in your variables.pkr.hcl file as you would for user defined variables, but instead of providing a value in your pkrvar.hcl file, you create environment variables in your terminal session, in this case, PowerShell.

Packer will look for variables in the session with the prefix of PKR_VAR_. If Packer finds any variables with this prefix, it knows they are for its use.

You do not need to add this prefix anywhere in your configuration as Packer knows to ignore the prefix when matching the variable name.

For example lets set the vSphere connection password in the PowerShell session we are using. This can be done by running the following to set the variable:

$env:PKR_VAR_vsphere_password = "VMware123!"

This example will match up to the variable declaration:

variable "vsphere_password" {}

You do not need to provide a value in your pkrvar.hcl file as Packer will read the value from the ‘PKR_VAR_vsphere_password’ environment variable.

NOTE: If you also provide a user defined variable in pkrvar.hcl, this will take precedence over the environment variable.

You can find HashiCorps documentation on variables here, have a read to discover even more options.

Referencing a Variable from Build Blocks

Now we have taken a brief look at some of the ways to declare and define variables, lets now take a look at how you use them in your source block!

Here are some examples:

  username       = var.vsphere_user
  vcenter_server = var.vsphere_server
  vm_name        = var.vm_name
  vm_version     = var.vm_version

There are two components here. Firstly, ‘var.’ this defines that a variable is being referenced. Secondly, the name of the variable you wish to reference. Each variable referenced will need to exist in variables.pkr.hcl and either a default value specified or a user or environment variable set. It doesn’t matter whether you are using environment or user defined variables, this syntax is the same. Remember that you do not need to include ‘PKR_VAR_’ in the variable name in the source block when you are referencing an environment variable, it’s only needed as a prefix when actually setting the variable.

That concludes my brief overview of user defined and environment variables. Do checkout the link to HashiCorp’s official documentation above and you can also find an example of a variable declaration file here, and a pkrvar.hcl file here on my GitHub.

In Part 4 we will put all the blocks and files together to complete the configuration before moving onto the final part of the series, where we will deploy some templates!

Thanks for reading!