Table of Contents
HashiCorp Vault 101 - Certified Vault Associate
Get started with HashiCorp Vault and prepare for your Vault Associate Exam
Terraform 101 - Certified Terraform Associate
Learn all you need to know to ace the Terraform Associate Exam and go beyond the certification

Terraform to Create a Ubuntu 22.04 VM in VMware vSphere ESXi

Learn to use Terraform to create a Ubuntu 22.04 VM in VMware vSphere ESXi.

Created: November 22, 2022 | Updated: January 27, 2023


Below is a video explanation and demo.

Terraform Creates a Ubuntu 22.04 VM in VMware vSphere

Video Chapters

You can skip to the relevant chapters below:

  • 00:00 - Introduction
  • 01:24 - Run Terraform
  • 01:47 - Terraform 101 Course Announcement
  • 02:28 - Terraform Configuration Explained
  • 11:42 - See the Results
  • 14:22 - Wrap-Up


In this blog post, we will use Terraform to provision a VM in VMware vSphere by cloning a VMware template. If you recall, we created this template in a previous blog post using Packer. The post is called HashiCorp Packer to Build a Ubuntu 22.04 Image Template in VMware vSphere.


Get FREE access to the source code by subscribing to my newsletter
You only need to subscribe once. Already subscribed? Enter your email to get instant access to the code.


The following is required to follow along:

  • Terraform (tested with Terraform v1.3.4)
  • Access to a vSphere instance (tested on vSphere v6.7)


Below is our setup diagram.

Setup Diagram


Let's take a look at the most important configuration pieces needed.

Folder Structure

Below is the structure of the repo folder.

Folder Structure

main.tf File

Let's explore the main.tf file.

  1. The first section of the file defines the required vsphere provider along with the credentials needed to access vsphere. There is also a locals variable definition that gets used in the metadata.yaml and userdata.yaml templates.
terraform {
  required_providers {
    vsphere = {
      source = "hashicorp/vsphere"
      version = "2.2.0"

provider "vsphere" {
  user                 = var.vsphere_user
  password             = var.vsphere_password
  vsphere_server       = var.vsphere_vcenter
  allow_unverified_ssl = true

locals {
  templatevars = {
    name         = var.name,
    ipv4_address = var.ipv4_address,
    ipv4_gateway = var.ipv4_gateway,
    dns_server_1 = var.dns_server_list[0],
    dns_server_2 = var.dns_server_list[1],
    public_key = var.public_key,
    ssh_username = var.ssh_username

  1. The second section of the main.tf file has a bunch of data blocks to retrieve existing data in vsphere. You'll see that we capture the following to use in generating the VM:
  • datacenter
  • datastore
  • cluster
  • network
  • template
data "vsphere_datacenter" "dc" {
  name = var.vsphere-datacenter

data "vsphere_datastore" "datastore" {
  name          = var.vm-datastore
  datacenter_id = data.vsphere_datacenter.dc.id

data "vsphere_compute_cluster" "cluster" {
  name          = var.vsphere-cluster
  datacenter_id = data.vsphere_datacenter.dc.id

data "vsphere_network" "network" {
  name          = var.vm-network
  datacenter_id = data.vsphere_datacenter.dc.id

data "vsphere_virtual_machine" "template" {
  name          = "/${var.vsphere-datacenter}/vm/${var.vsphere-template-folder}/${var.vm-template-name}"
  datacenter_id = data.vsphere_datacenter.dc.id

  1. In this third and last section, we use a vsphere_virtual_machine resource to build our VM. Notice how we use the info from the data blocks retrieved earlier. We also feed the local.templatevars variables into the metadata.yaml and the userdata.yaml templates. We will take a look at these templates next.
resource "vsphere_virtual_machine" "vm" {
  name             = var.name
  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id

  num_cpus             = var.cpu
  num_cores_per_socket = var.cores-per-socket
  memory               = var.ram
  guest_id             = var.vm-guest-id

  network_interface {
    network_id   = data.vsphere_network.network.id
    adapter_type = data.vsphere_virtual_machine.template.network_interface_types[0]

  disk {
    label            = "${var.name}-disk"
    thin_provisioned = data.vsphere_virtual_machine.template.disks.0.thin_provisioned
    eagerly_scrub    = data.vsphere_virtual_machine.template.disks.0.eagerly_scrub
    size             = var.disksize == "" ? data.vsphere_virtual_machine.template.disks.0.size : var.disksize 

  clone {
    template_uuid = data.vsphere_virtual_machine.template.id
  extra_config = {
    "guestinfo.metadata"          = base64encode(templatefile("${path.module}/templates/metadata.yaml", local.templatevars))
    "guestinfo.metadata.encoding" = "base64"
    "guestinfo.userdata"          = base64encode(templatefile("${path.module}/templates/userdata.yaml", local.templatevars))
    "guestinfo.userdata.encoding" = "base64"
  lifecycle {
    ignore_changes = [

metadata.yaml Template File

Cloud-init uses this file to define the instance we're creating. We can configure the network interface, the hostname, the instance-id, the disks, and so on. Notice the ${variable_name} syntax. This is used to receive variables from the local.templatevars variable we fed into the template.

local-hostname: ${name}
instance-id: ubuntu-${name}
  version: 2
      dhcp4: false
        - ${ipv4_address}/24
      gateway4: ${ipv4_gateway}
        search: [home]
        addresses: [${dns_server_1}, ${dns_server_2}]
  mode: auto
  devices: ['/dev/sda2']
  ignore_growroot_disabled: true
  ipv4: true

userdata.yaml Template File

This file is used by cloud-init to configure users' ssh names, keys, and so on. Furthermore, you can install packages here. We install the tree package to show this functionality.

  - name: ${ssh_username}
      - ssh-rsa ${public_key}
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    groups: sudo
    shell: /bin/bash

  - tree

output.tf File

In this file we display the output IP for our VM.

output "ip" {
  value = vsphere_virtual_machine.vm.guest_ip_addresses[0]

Variables Files

We split the variable assignment files into two files:

  1. terraform.tfvars (holds sensitive variables - not checked into git)
  2. vars.auto.tfvars (holds non-sensitive variables - checked into git)

terraform.tfvars File

You won't find this file in the git repo because it contains sensitive information about my vSphere instance. I added it to .gitignore. I created an example file called terraform-example.tfvars. Please rename this file to terraform.tfvars and populate it with your values. Here it is below:

# vSphere Specific
vsphere_user     = "<your_vsphere_user>"
vsphere_password = "<your_vsphere_password>"
vsphere_vcenter  = "<your_vcenter_ip>"

vars.auto.tfvars File

This file assigns values to the different variables to build our VM. The variables are self-explanatory.

cpu                    = 4
cores-per-socket       = 1
ram                    = 4096
disksize               = 100 # in GB
vm-guest-id            = "ubuntu64Guest"
vsphere-unverified-ssl = "true"
vsphere-datacenter     = "Datacenter"
vsphere-cluster        = "Cluster01"
vm-datastore           = "Datastore1_SSD"
vm-network             = "VM Network"
vm-domain              = "home"
dns_server_list        = ["", ""]
name                   = "ubuntu22-04-test"
ipv4_address           = ""
ipv4_gateway           = ""
ipv4_netmask           = "24"
vm-template-name       = "Ubuntu-2204-Template-100GB-Thin"

variables.tf File

This is where you declare/define all the variables. You can find it in the GitLab repo.


In this blog post, we demonstrated how to use an existing vSphere template built by Packer to create a VM with Terraform. The key takeaway is that Infrastructure as Code works just as well in an on-premises environment as it does in the cloud. Keep building!


Other Posts
Terraform for Beginners - A Beginner's Guide to Automating Cloud Infrastructure
Terraform vs Ansible - Demo the Differences - Part 2
Terraform vs Ansible - Learn the Differences - Part 1
HashiCorp Vault Backup and Restore Raft Snapshots from Kubernetes to AWS S3
AWS Lambda - Terraform Configuration Example with API Gateway
Securing the Future - DevSecOps Trends for 2023
36 Top DevOps Questions to Get You Started in 2023
Terraform to Create a Ubuntu 22.04 VM in VMware vSphere ESXi
HashiCorp Packer to Build a Ubuntu 22.04 Image Template in VMware vSphere
Migrate Secrets from AWS Secrets Manager to HashiCorp Vault with Python, Docker, and GitLab
Migrate Secrets from AWS Secrets Manager to HashiCorp Vault with Terraform
env0 - A Terraform Cloud Alternative
Terraform Import Example - AWS EC2 Instance
DevOps Engineer NOT on Linux? You're MISSING OUT!
HashiCorp Vault API Tutorial and Pro Tips
HashiCorp Vault Tutorial for Beginners
Create a Pihole Docker Ad Blocker with Ansible and Terraform
Terraform vSphere Windows Example to Join an AD Domain
Build a Kubernetes k3s Cluster in vSphere with Terraform and Packer
HashiCorp Packer to Build a Ubuntu 20.04 Image Template in VMware
Consul-Template to Automate Certificate Management for HashiCorp Vault PKI
HashiCorp Vault PKI Secrets Engine Demo for Certificate Management
Jenkins, Vault, Terraform, Ansible, and Consul Delivering an End-to-End CI/CD Pipeline
Secret Zero Problem Solved for HashiCorp Vault
Hashicorp Packer, Terraform, and Ansible to Set Up Jenkins
Hashicorp Vault Azure Secrets Engine - Secure Your Azure Resources
HashiCorp Waypoint - Will it Replace Your CI/CD?
HashiCorp Boundary - Make Sure Your Human To Machine Access Is Secure
HashiCorp Packer for VMware Ubuntu Templates and Terraform for building VMs
HashiCorp Packer VMware Windows Templates and Terraform for VMs
Webblog App Part 4 – HashiStack – Nomad Consul Vault Terraform
Webblog App Part 3 - Consul Connect Service Mesh
Webblog App Part 2 - Secrets Development Phases with Vault
Webblog App Part 1 - Infrastructure as Code with Terraform
Microservices Applications'​ Life Cycle
HashiCorp Vault 201 - Vault for Apps in Kubernetes
Learn how to use HashiCorp Vault for your applications in Kubernetes