VMWare Templates with terraform and cloud-init

published in: Infrastructure as a Service DevOps Date: January 21, 2021
Martin Buchleitner, Senior IT-Consultant

About the author

Martin Buchleitner is a Senior IT-Consultant for Infralovers and for Commandemy. Twitter github LinkedIn

See all articles by this author

Automate creation of VMWare virtual machines

In the previous post about VMWare templates with packer and also the revisted post about that topic we introduced the automated generation of VMWare templates. In this posting we are going the next step and also automate the generation of virtual machines on VMWare using terraform and cloud-init.

Prepare VMWare Packer images with customized cloud-init

Because VMWare does store the cloud-init data different than the official packages support, you must install a custom version of cloud-init provided on github On RedHat based systems you were able to just install a rpm provided by the project:

yum install -y https://github.com/vmware/cloud-init-vmware-guestinfo/releases/download/v1.1.0/cloud-init-vmware-guestinfo-1.1.0-1.el7.noarch.rpm

but as you can see on the release page of the project that this support ended with the metioned release above. Since then you can use a custom shell script provided for all distributions. Be aware that you might have to install additional packages to use this script!

curl -O https://raw.githubusercontent.com/vmware/cloud-init-vmware-guestinfo/master/install.sh

A nice approach is that you use ansible provisioner of packer to do this job. You can write your own role to handle the operation system dependencies to deploy this custom cloud-init package.

Terraform the VMWare Templates

To use terraform with cloud-init you must use a data template and a cloud-init template.

data "template_file" "cloud-init" {
  template = file("cloud-init.tpl")

  vars = {
    hostname = var.vm_name
    ssh_key_list = var.ssh_keys
data "template_cloudinit_config" "cloud-init" {
  gzip          = true
  base64_encode = true

  part {
    content_type = "text/cloud-config"
    content      = data.template_file.cloud-init.rendered

And you must generate a cloud-init template like this small example snippet:

hostname: ${hostname}

  - name: test
    primary_group: test
    groups: sudo, wheel
    ssh_import_id: None
    lock_passwd: true

Based on this templates you can also gonna be creative and customize these steps to your needs. Right now this template sets the hostname and creates a user test, which is able to use sudo, and gets a list of authorized keys injected. Those ssh keys can be fetched depending on the customer from a vault or repository.

Afterwards you can use this in your virtual_machine definition

resource "vsphere_virtual_machine" "example" {
  name             = var.vm_name
  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id
  folder           = vsphere_folder.terraformed.path

  # VM resources #
  num_cpus = "2"
  memory   = "4096"

  # Guest OS #
  guest_id = data.vsphere_virtual_machine.template.guest_id

  # VM storage #
  disk {
    label            = "${var.vm_name}.vmdk"
    size             = data.vsphere_virtual_machine.template.disks[0].size
    thin_provisioned = data.vsphere_virtual_machine.template.disks[0].thin_provisioned
    eagerly_scrub    = data.vsphere_virtual_machine.template.disks[0].eagerly_scrub

  # VM networking #
  network_interface {
    network_id   = data.vsphere_network.network.id
    adapter_type = "e1000"

  # Customization of the VM #
  clone {
    template_uuid = data.vsphere_virtual_machine.template.id
    linked_clone  = "false"

  vapp {
    properties = {
    "guestinfo.userdata" = base64gzip(data.template_file.cloud-init.rendered)

With this definition you can create your VMWare virtual machines using terraform and use cloud-init mechanism to customize your machines on their first boot before handing them over to the next team.