Skip to main content

Terraform - VMware vSphere: Install vSphere CA Certificate on Linux; Deploy basic VM with manual Installation; Create VM Template with Packer, Deploy VMs with Custom IP and Hostname from Template and place them in vSphere Folder

2756 words·
Terraform Packer vSphere VMware Cloud-init PowerCLI Root CA Certificate
Table of Contents
Terraform - This article is part of a series.
Part 4: This Article

vSphere Prerequisites
#

PowerShell PowerCLI
#

Use PowerShell PowerCLI to list the vSphere details that are necessary to connect with Terraform.

Connect to vSphere
#


# Connect to vCenter node: Define PW
Connect-VIServer -Server 192.168.70.70 -User Administrator@vsphere.local -Password my-secure-password

# Shell output:
Name                           Port  User
----                           ----  ----
192.168.70.70                  443   VSPHERE.LOCAL\Administrator

List vSphere Datacenters
#

# List vSphere Datacenters
Get-Datacenter

# Shell output:
Name
----
Datacenter
# List vSphere Datacenters: More details
Get-Datacenter | Select-Object Name, Id, Status

# Shell output:
Name       Id                         Status
----       --                         ------
Datacenter Datacenter-datacenter-1001

List vSphere Clusters
#

# List vSphere Clusters: All available clusters
Get-Cluster

# Shell output:
Name                           HAEnabled  HAFailover DrsEnabled DrsAutomationLevel
                                          Level
----                           ---------  ---------- ---------- ------------------
jkw-cluster-1                  False      1          True       FullyAutomated
jkw-cluster-2                  False      1          True       FullyAutomated
# List vSphere Clusters: In specific Datacenter
Get-Cluster -Location "Datacenter"

# Shell output:
Name                           HAEnabled  HAFailover DrsEnabled DrsAutomationLevel
                                          Level
----                           ---------  ---------- ---------- ------------------
jkw-cluster-1                  False      1          True       FullyAutomated
jkw-cluster-2                  False      1          True       FullyAutomated
# List vSphere Clusters: In specific Datacenter / specify Id output
Get-Cluster -Location "Datacenter" | Select-Object Name, Id | Format-List

# Shell output:
Name : jkw-cluster-1
Id   : ClusterComputeResource-domain-c1012

Name : jkw-cluster-2
Id   : ClusterComputeResource-domain-c1017

List vSphere Datastores
#

# List vSphere Datastores: All available Datastores
Get-Datastore

# Shell output:
Name                               FreeSpaceGB      CapacityGB
----                               -----------      ----------
Datastore1                             273,754         349,750
# List vSphere Datastores: In a specific Datacenter
Get-Datastore -Datacenter "Datacenter" | Select-Object Name, Id, CapacityGB, FreeSpaceGB

# Shell output:
Name       Id                       CapacityGB  FreeSpaceGB
----       --                       ----------  -----------
Datastore1 Datastore-datastore-1009     349,75 273,75390625

List vSphere Networks
#

# List vSphere Networks
Get-VirtualNetwork

# Shell output:
Name                                               NetworkType
----                                               -----------
VM Network                                         Network
# List vSphere Networks: More details
Get-VirtualNetwork | Select-Object Name, Id | Format-List

# Shell output:
Name : VM Network
Id   : Network-network-1010

Write Down vSphere Resource Names
#

Write down the vSphere resource names and define them for the Terraform provider:

# vSphere Datacenter
Datacenter

# vSphere Cluster
jkw-cluster-1

# vSphere Datastore
Datastore1

# vSphere VM Network
VM Network



Install vSphere root CA Certificate
#

Make sure to Install the vSphere root CA certificate on the host where Terraform is run.

Test TLS Encryption
#

# Test the TLS encryption with curl
curl https://vcsa.vsphere.local/

# Shell output: Without vSphere root CA certificate
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

Download vSphere Certificate
#

# Download the vSphere server certificate
wget https://vcsa.vsphere.local/certs/download.zip --no-check-certificate

# Unzip archive
unzip download.zip
# The unpacked archive looks as follows:
├── certs
│   ├── lin
│   │   ├── 94dfc8ac.0
│   │   └── 94dfc8ac.r0
│   ├── mac
│   │   ├── 94dfc8ac.0
│   │   └── 94dfc8ac.r0
│   └── win
│       ├── 94dfc8ac.0.crt
│       └── 94dfc8ac.r0.crl
└── download.zip
# List certificate details
openssl x509 -in certs/lin/94dfc8ac.0 -text -noout

Install vSphere Certificate
#

# Create folder for certificate
sudo mkdir /usr/share/ca-certificates/vsphere

# Copy certificate in ca-certificates directory
sudo cp certs/lin/94dfc8ac.0 /usr/share/ca-certificates/vsphere

# Rename certificate file
sudo mv /usr/share/ca-certificates/vsphere/94dfc8ac.0 /usr/share/ca-certificates/vsphere/94dfc8ac.crt

# Install / uninstall certificate: Start wizard
sudo dpkg-reconfigure ca-certificates
# Verify the certificate is installed
ls -la /etc/ssl/certs | grep vsphere

# Shell output:
lrwxrwxrwx 1 root root     47 Jul 11 20:23 94dfc8ac.pem -> /usr/share/ca-certificates/vsphere/94dfc8ac.crt

Test TLS Encryption
#

# Test the TLS encryption with curl
curl https://vcsa.vsphere.local/

# Shell output: With vSphere root CA certificate
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
 <head> ...



Terraform & Packer Prerequisites
#

vSphere Hosts Entry
#

# Hosts entry
192.168.70.70 vcsa.vsphere.local

Installation
#

Install Terraform
#

Use the following script to install Terraform ob Debian based Linux distributions:

# Install the HashiCorp GPG key
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null

# Verify the GPG key fingerprint
gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint

# Add the official HashiCorp repository 
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

# Install Terraform
sudo apt update && sudo apt-get install terraform

Verify the installation:

# Verify the installation / check version
terraform version

# Shell output:
Terraform v1.9.1
on linux_amd64

Install Packer
#

# Install Packer dependencies
sudo apt update && 
sudo apt install xorriso genisoimage -y
# Install packer (From official HashiCorp repository)
sudo apt update && 
sudo apt install packer

Verify the installation:

# Verify the installation / check version
packer --version

# Shell output:
Packer v1.11.1



Example: Create Simple VM
#

The following example creates a new virtual machine that boots from an ISO file. The installation is done manually, this setup is just for testing and troubleshooting.

  • In this example, the vSphere resources like “Datacenter”, “Datastore”, “Cluster” and “VM Network” are defined directly in the “provider.tf” configuration.

  • The vSphere credentials are defined as variables in “vars.tf” configuration and the credential values are define in “terraform.tfvars”.

  • The actual virtual machine is defined in the “main.tf” configuration.

The file structure looks as follows:

├── main.tf
├── provider.tf
├── terraform.tfstate
├── terraform.tfstate.backup
├── terraform.tfvars
└── vars.tf

provider.tf
#

# vSphere Provider
provider "vsphere" {
  user                 = var.vsphere_user
  password             = var.vsphere_password
  vsphere_server       = var.vsphere_server
  allow_unverified_ssl = true # Disable if vSphere root CA certificate is installed
  api_timeout          = 10
}

# vSphere Datacenter
data "vsphere_datacenter" "datacenter" {
  name = "Datacenter"
}

# vSphere Datastore
data "vsphere_datastore" "datastore" {
  name          = "Datastore1"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

# vSphere Cluster
data "vsphere_compute_cluster" "cluster" {
  name          = "jkw-cluster-1"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

# vSphere VM Network
data "vsphere_network" "network" {
  name          = "VM Network"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

vars.tf
#

# vars.tf
variable "vsphere_user" {
  type        = string
  description = "User name for vSphere API operations."
}

variable "vsphere_password" {
  type        = string
  description = "Password for vSphere API operations."
  sensitive   = true  // This will prevent the password from being displayed in logs or CLI output
}

variable "vsphere_server" {
  type        = string
  description = "vSphere server for API operations."
}

terraform.tfvars
#

# terraform.tfvars
vsphere_user     = "Administrator@vsphere.local"
vsphere_password = "my-secure-pw"
vsphere_server   = "vcsa.vsphere.local"

vSphere VM
#

# main.tf
resource "vsphere_virtual_machine" "vm" {
  name             = "vm1"
  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id
  num_cpus         = 1
  memory           = 2048
  guest_id         = "otherLinux64Guest"
  network_interface {
    network_id = data.vsphere_network.network.id
  }
  disk {
    label = "disk0"
    size  = 20
  }
  cdrom {
    datastore_id = data.vsphere_datastore.datastore.id
    path         = "ISO/ubuntu-24.04-live-server-amd64.iso"
  }
}

Terraform Apply
#

# Check for any syntax errors or inconsistencies in the Terraform configuration files
terraform validate

# List actions Terraform will take when run "terraform apply"
terraform plan
# Deploy the resources
terraform apply

# Deploy the resources: Skip approval
terraform apply -auto-approve

Example: Deploy VM from Template
#

Packer Template
#

The following Packer configuration creates an Ubuntu-based VMware vSphere template tf-ubuntu22.04 designed for cloning with Terraform.

I’m using an Ubuntu 22.04 image located in vSphere: Datestore1/ISO/ubuntu-22.04.3-live-server-amd64.iso

GitHub repository: https://github.com/jueklu/packer-vsphere


Packer File Structure
#

# Packer file structure
├── meta-data
├── user-data
├── variables.pkr.hcl
├── vars.auto.pkrvars.hcl
└── vsphere-iso_basic_ubuntu.pkr.hcl

Cloud-Init: “user-data”
#

#cloud-config
autoinstall:
  version: 1
  identity:
    hostname: ubuntu
    password: "$6$exDY1mhS4KUYCE/2$zmn9ToZwTKLhCw.b4/b.ZRTIZM30JZ4QrOQ2aOXJ8yk96xpcCof0kxKwuX1kqLG/ygbJ1f8wxED22bTL4F46P0"
    username: ubuntu
  locale: "de_DE.UTF-8"
  keyboard:
    layout: "de"
  ssh:
    install-server: true
    allow-pw: true
  packages:
    - open-vm-tools

Variables “variables.pkr.hcl”
#

# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

variable "vsphere_server" {
  type    = string
  default = ""
}

variable "vsphere_user" {
  type    = string
  default = ""
}

variable "vsphere_password" {
  type    = string
  default = ""
}

variable "datacenter" {
  type    = string
  default = ""
}

variable "cluster" {
  type    = string
  default = ""
}

variable "datastore" {
  type    = string
  default = ""
}

variable "network_name" {
  type    = string
  default = ""
}

Variables Values: “vars.auto.pkrvars.hcl”
#

# vars.auto.pkrvars.hcl
vsphere_server = "vcsa.vsphere.local"
vsphere_user = "Administrator@vsphere.local"
vsphere_password = "my-secure-pw"
datacenter = "Datacenter"
cluster = "jkw-cluster-1"
datastore = "Datastore1"
network_name = "VM Network"

vars.auto.pkrvars.hcl
#

# vsphere-iso_basic_ubuntu.pkr.hcl
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

packer {
  required_plugins {
    vsphere = {
      version = ">= 1.2.3"
      source  = "github.com/hashicorp/vsphere"
    }
  }
}

source "vsphere-iso" "this" {
  vcenter_server      = var.vsphere_server
  username            = var.vsphere_user
  password            = var.vsphere_password
  datacenter          = var.datacenter
  cluster             = var.cluster
  insecure_connection = true

  vm_name       = "tf-ubuntu22.04" # Define VM template name
  guest_os_type = "ubuntu64Guest"

  CPUs            = 2
  RAM             = 2048
  RAM_reserve_all = true

  ssh_username = "ubuntu"
  ssh_password = "ubuntu"
  ssh_timeout  = "30m"

  /* Uncomment when running on vcsim
  ssh_host     = "127.0.0.1"
  ssh_port     = 2222

  configuration_parameters = {
    "RUN.container" : "lscr.io/linuxserver/openssh-server:latest"
    "RUN.mountdmi" : "false"
    "RUN.port.2222" : "2222"
    "RUN.env.USER_NAME" : "ubuntu"
    "RUN.env.USER_PASSWORD" : "ubuntu"
    "RUN.env.PASSWORD_ACCESS" : "true"
  }
  */

  disk_controller_type = ["pvscsi"]
  datastore            = var.datastore
  storage {
    disk_size             = 16384
    disk_thin_provisioned = true
  }
  # Define ISO path
  iso_paths = ["[Datastore1] ISO/ubuntu-22.04-live-server-amd64.iso"]

  network_adapters {
    network = var.network_name
  }

  cd_files = ["./meta-data", "./user-data"]
  cd_label = "cidata"

  boot_command = ["<wait>e<down><down><down><end> autoinstall ds=nocloud;<F10>"]

}

build {
  sources = [
    "source.vsphere-iso.this"
  ]

  provisioner "shell-local" {
    inline = ["echo the address is: $PACKER_HTTP_ADDR and build name is: $PACKER_BUILD_NAME"]
  }
}

Create ISO File
#

Initialize Packer Configuration
#

# Initialize the Packer configuration
packer init .

# Shell output:
Installed plugin github.com/hashicorp/vsphere v1.3.0 in "/home/ubuntu/.config/packer/plugins/github.com/hashicorp/vsphere/packer-plugin-vsphere_v1.3.0_x5.0_linux_amd64

Build the Ubuntu Template
#

# Build image
packer build .
# Shell output:
vsphere-iso.this: output will be in this color.

==> vsphere-iso.this: Creating CD disk...
    vsphere-iso.this: xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
    vsphere-iso.this: Drive current: -outdev 'stdio:/tmp/packer273668188.iso'
    vsphere-iso.this: Media current: stdio file, overwriteable
    vsphere-iso.this: Media status : is blank
    vsphere-iso.this: Media summary: 0 sessions, 0 data blocks, 0 data, 23.9g free
    vsphere-iso.this: xorriso : WARNING : -volid text does not comply to ISO 9660 / ECMA 119 rules
    vsphere-iso.this: Added to ISO image: directory '/'='/tmp/packer_to_cdrom1249690970'
    vsphere-iso.this: xorriso : UPDATE :       2 files added in 1 seconds
    vsphere-iso.this: xorriso : UPDATE :       2 files added in 1 seconds
    vsphere-iso.this: ISO image produced: 185 sectors
    vsphere-iso.this: Written to medium : 185 sectors at LBA 0
    vsphere-iso.this: Writing to 'stdio:/tmp/packer273668188.iso' completed successfully.
    vsphere-iso.this: Done copying paths from CD_dirs
==> vsphere-iso.this: Uploading packer273668188.iso to [Datastore1] packer_cache...
==> vsphere-iso.this: Creating virtual machine...
==> vsphere-iso.this: Customizing hardware...
==> vsphere-iso.this: Mounting ISO images...
==> vsphere-iso.this: Adding configuration parameters...
==> vsphere-iso.this: Set boot order temporary...
==> vsphere-iso.this: Power on VM...
==> vsphere-iso.this: Waiting 10s for boot...
==> vsphere-iso.this: Typing boot command...
==> vsphere-iso.this: Waiting for IP...
==> vsphere-iso.this: IP address: 192.168.70.254
==> vsphere-iso.this: Using SSH communicator to connect: 192.168.70.254
==> vsphere-iso.this: Waiting for SSH to become available...
==> vsphere-iso.this: Connected to SSH!
==> vsphere-iso.this: Running local shell script: /tmp/packer-shell3240343788
    vsphere-iso.this: the address is: 192.168.30.14:0 and build name is: this
==> vsphere-iso.this: Shutting down VM...
==> vsphere-iso.this: Deleting Floppy drives...
==> vsphere-iso.this: Ejecting CD-ROM media...
==> vsphere-iso.this: Clear boot order...
    vsphere-iso.this: Closing sessions ....
Build 'vsphere-iso.this' finished after 7 minutes 21 seconds.

==> Wait completed after 7 minutes 21 seconds

==> Builds finished. The artifacts of successful builds are:
--> vsphere-iso.this: tf-ubuntu22.04

Terraform Configuration
#

Overview
#

This Terraform project demonstrates how to deploy a new VM from the VM template previously created with Packer, and assigns a specified IP address to the VM.

The project file structure looks like this:

├── main.tf
├── provider.tf
├── terraform.tfvars
└── vars.tf

GitHub repository: https://github.com/jueklu/terraform-vsphere-example


Provider: provider.tf
#

# vSphere Provider
provider "vsphere" {
  user                 = var.vsphere_user
  password             = var.vsphere_password
  vsphere_server       = var.vsphere_server
  allow_unverified_ssl = true
  api_timeout          = 10
}


# vSphere Datacenter
data "vsphere_datacenter" "datacenter" {
  name = var.datacenter_name
}

# vSphere Datastore
data "vsphere_datastore" "datastore" {
  name          = var.datastore_name
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

# vSphere Cluster
data "vsphere_compute_cluster" "cluster" {
  name          = var.compute_cluster_name
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

# vSphere VM Network
data "vsphere_network" "network" {
  name          = var.vm_network_name
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

Variables: vars.tf
#

# vSphere Authentication
variable "vsphere_user" {
  type        = string
  description = "User name for vSphere API operations."
}

variable "vsphere_password" {
  type        = string
  description = "Password for vSphere API operations."
  sensitive   = true  // This will prevent the password from being displayed in logs or CLI output
}

variable "vsphere_server" {
  type        = string
  description = "vSphere server for API operations."
}


# vSphere Resources
variable "datacenter_name" {
  description = "Name of the vSphere datacenter"
  type        = string
}

variable "datastore_name" {
  description = "Name of the vSphere datastore"
  type        = string
}

variable "compute_cluster_name" {
  description = "Name of the vSphere compute cluster"
  type        = string
}

variable "vm_network_name" {
  description = "Name of the VM network"
  type        = string
}


# VM Template
variable "datacenter" {
  description = "vSphere data center"
  type        = string
}
variable "ubuntu_name" {
  description = "Ubuntu name (ie: image_path)"
  type        = string
}

Variable Values: terraform.tfvars
#

# vSphere Authentication
vsphere_user     = "Administrator@vsphere.local"
vsphere_password = "my-secure-pw"
vsphere_server   = "vcsa.vsphere.local"

# vSphere Resources
datacenter_name       = "Datacenter"
datastore_name        = "Datastore1"
compute_cluster_name  = "jkw-cluster-1"
vm_network_name       = "VM Network"

# VM Template
datacenter          = "Datacenter"
ubuntu_name         = "tf-ubuntu24.04"

Deploy VMs: main.tf
#

Deploy Single VM
# Define template
data "vsphere_virtual_machine" "ubuntu" {
  name          = "/${var.datacenter}/vm/${var.ubuntu_name}"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}


# virtual machine configuration
resource "vsphere_virtual_machine" "vm" {
  name             = "vm1" # Define VM name in vSphere
  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id

  num_cpus         = 2
  memory           = 2048

  network_interface {
    network_id = data.vsphere_network.network.id
  }

  wait_for_guest_net_timeout = -1
  wait_for_guest_ip_timeout  = -1
  
  disk {
    label          = "disk0"
    thin_provisioned = true
    size             = 16
  }

  guest_id = "ubuntu64Guest" # Define vSphere VM type

  # Clone VM from template
  clone { 
    template_uuid  = data.vsphere_virtual_machine.ubuntu.id

    # Customize the new VM
    customize {
      linux_options {
        host_name = "ubuntu-2"
        domain    = "local"
      }

      network_interface {
        ipv4_address = "192.168.70.111"
        ipv4_netmask = 24
      }

      ipv4_gateway = "192.168.70.1"
    }
  }
}
Deploy Multiple VMs: Ascending names

The following configuration below creates VMs with ascending names / hostnames and IP addresses:

# name  hostname  IP
vm1     ubuntu1   192.168.70.120
vm2     ubuntu2   192.168.70.121
vm3     ubuntu3   192.168.70.122
# Define template
data "vsphere_virtual_machine" "ubuntu" {
  name          = "/${var.datacenter}/vm/${var.ubuntu_name}"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}


# virtual machine configuration
resource "vsphere_virtual_machine" "vm" {
  count            = 3  # Number of VMs to create
  name             = "vm${count.index + 1}" # Dynamically create VM name
  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id

  num_cpus         = 2
  memory           = 2048

  network_interface {
    network_id = data.vsphere_network.network.id
  }

  wait_for_guest_net_timeout = -1
  wait_for_guest_ip_timeout  = -1

  disk {
    label           = "disk0"
    thin_provisioned = true
    size            = 16
  }

  guest_id = "ubuntu64Guest" # Define vSphere VM type

  # Clone VM from template
  clone {
    template_uuid = data.vsphere_virtual_machine.ubuntu.id

    # Customize the new VM
    customize {
      linux_options {
        host_name = "ubuntu-${count.index + 1}"  # Dynamically create hostname
        domain    = "local"
      }

      network_interface {
        ipv4_address = "192.168.70.12${count.index}" # Assign unique IP for each VM
        ipv4_netmask = 24
      }

      ipv4_gateway = "192.168.70.1"
    }
  }
}
Deploy Multiple VMs: Custom names, create vSphere folder

The following configuration below creates VMs with custom names / hostnames and ascending IP addresses. The VMs are deployed in a folder named “Terraform-Cluster-1”:

# name  IP
controller1   192.168.70.120
controller2   192.168.70.121
controller3   192.168.70.122
worker1       192.168.70.123
worker2       192.168.70.124
# Define template
data "vsphere_virtual_machine" "ubuntu" {
  name          = "/${var.datacenter}/vm/${var.ubuntu_name}"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}


# Create vSphere folder for the VMs
resource "vsphere_folder" "vm_folder" {
  path          = "Terraform-Cluster-1"  # Define the folder name
  type          = "vm" # Folder type (vm for virtual machines)
  datacenter_id = data.vsphere_datacenter.datacenter.id
}


# Define VM names
locals {
  vm_names = ["controller1", "controller2", "controller3", "worker1", "worker2"]
}


# virtual machine configuration
resource "vsphere_virtual_machine" "vm" {
  count            = 5  # Number of VMs to create
  name             = local.vm_names[count.index] # Use custom names from local variables
  folder           = vsphere_folder.vm_folder.path # Place VMs in the folder
  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id

  num_cpus         = 2
  memory           = 2048

  network_interface {
    network_id = data.vsphere_network.network.id
  }

  wait_for_guest_net_timeout = -1
  wait_for_guest_ip_timeout  = -1

  disk {
    label           = "disk0"
    thin_provisioned = true
    size            = 16
  }

  guest_id = "ubuntu64Guest" # Define vSphere VM type

  # Clone VM from template
  clone {
    template_uuid = data.vsphere_virtual_machine.ubuntu.id

    # Customize the new VM
    customize {
      linux_options {
        host_name = local.vm_names[count.index]  # Use custom hostnames from local variables
        domain    = "local"
      }

      network_interface {
        ipv4_address = "192.168.70.12${count.index}" # Assign unique IP for each VM
        ipv4_netmask = 24
      }

      ipv4_gateway = "192.168.70.1"
    }
  }
}

Deploy Resources
#

# Initialize repository / add the vSphere provider
terraform init
# Check for any syntax errors or inconsistencies in the Terraform configuration files
terraform validate

# List actions Terraform will take when run "terraform apply"
terraform plan
# Deploy the resources
terraform apply

# Deploy the resources: Skip approval
terraform apply -auto-approve

Verify the Deployment in vSphere
#

Deploy Multiple VMs: Ascending names


Deploy Multiple VMs: Custom names, create vSphere folder:


Delete the Deployment
#

# Destroy VMs
terraform destroy

Links #

# Install Packer
https://developer.hashicorp.com/packer/tutorials/docker-get-started/get-started-install-cli

# Packer Tutorial
https://developer.hashicorp.com/terraform/tutorials/virtual-machine/vsphere-provider
# Terraform vSphere Provider
https://registry.terraform.io/providers/hashicorp/vsphere/latest/docs

# Ubuntu Cloud Images
https://cloud-images.ubuntu.com/noble/current/
Terraform - This article is part of a series.
Part 4: This Article