Note: The description in this article is based on Terraform version 1.14.4, VMware Cloud Director Provider version 3.14, and VMware Cloud Director 10.6.
VM Guest OS Customization
Guest OS Customization is a feature of VMware Cloud Director that allows you to customize (configure) certain settings of the guest operating system. It is used for VMs created from a template. Configuration takes place at first boot of the VM, or when the VM is started with the Power on, Force Recustomization option. VMware Tools are used for this purpose. Cloud Director creates a package that it copies to, runs on, and removes from the VM during customization.
You can configure the hostname, network parameters, change the SID and join a domain (for Windows), set the administrator password, or run a custom script. This article focuses specifically on the ability to run a custom script inside the virtual machine's OS. The script is limited to a maximum length of 1500 characters.
Guest Customization must be enabled for it to run. When the VM is powered on, the computer name and network parameters should be configured. This setting cannot be disabled if you want customization to be enabled. On first power-on (or when forced), the remaining settings are applied, including running the script. It is recommended to disable customization after it has been applied for the first time.
Note: For customization, you can also use Guest Properties on a VM. These two options should not be combined.
Terraform VMware Cloud Director Provider
We will build on the example described in the previous article about creating a new VM from a template using Terraform and the VMware Cloud Director Provider. Some values that were previously entered directly into the configuration will now be replaced with variables.
We will add new variable definitions to the variables.tf file.
# VM parameters
variable "vm_name" {
type = string
}
variable "vm_ip" {
type = string
}
variable "vm_ip_mask" {
type = string
}
variable "vm_gw" {
type = string
}
variable "vm_dns" {
type = string
}
In the terraform.tfvars file, we will set their values.
# VM parameters
vm_name = "Demo-VM"
vm_ip = "172.30.21.100"
vm_ip_mask = "22"
vm_gw = "172.30.20.1"
vm_dns = "172.30.20.1"
Creating a VM and configuring customization (the customization block)
In the vcd_vapp_vm resource, we can use the customization block to configure the Guest OS Customization parameters.
# create VM from template wtih manual IP assignment
resource "vcd_vapp_vm" "DemoVM" {
vapp_name = vcd_vapp.Demo.name
name = var.vm_name
computer_name = var.vm_name
vapp_template_id = data.vcd_catalog_vapp_template.my-vapp-template.id
memory = 2048
cpus = 2
cpu_cores = 1
depends_on = [ vcd_vapp_org_network.vappOrgNet ]
network {
type = "org"
name = vcd_vapp_org_network.vappOrgNet.org_network_name
ip_allocation_mode = "MANUAL"
ip = var.vm_ip
is_primary = true
}
customization {
enabled = true
}
}
We can also use the force argument, which causes a VM restart on every terraform apply operation along with forced customization. It can be used when guest reconfiguration is needed and then disabled again afterwards.
Entering the script directly into the initscript attribute
The customization block contains an initscript argument, into which you can directly insert the contents of the customization script. Here is a simple example that sets the hostname and network parameters (even though most of these settings are handled automatically by Guest Customization) for our VM running AlmaLinux 10.1.
customization {
enabled = true
initscript = <<-EOF
#!/bin/bash
hostnamectl hostname "${var.vm_name}"
nmcli connection modify ens192 ipv4.addresses "${var.vm_ip}/${var.vm_ip_mask}" ipv4.gateway ${var.vm_gw} ipv4.method manual ipv4.dns ${var.vm_dns} ipv6.method disable
nmcli device reapply ens192
EOF
}
Using the templatefile function to load the script
It may be cleaner to store the script in a separate file and use the templatefile function, which loads it and substitutes the specified variables.
We will create a file called initscript.tftpl and place our script content inside it.
#!/bin/bash
hostnamectl hostname "${vm_name}"
nmcli connection modify ens192 ipv4.addresses ${vm_ip_mask} ipv4.gateway ${vm_gw} ipv4.method manual ipv4.dns ${vm_dns} ipv6.method disable
nmcli device reapply ens192
We then update the customization block as follows.
customization {
enabled = true
initscript = templatefile("initscript.tftpl", { vm_name = var.vm_name, vm_ip_mask = "${var.vm_ip}/${var.vm_ip_mask}", vm_gw = var.vm_gw, vm_dns = var.vm_dns })
}

Running part of the script before or after customization
The VMware documentation states that the script is called with the precustomization parameter before customization and the postcustomization parameter after customization. Conditions can be used to control which commands should run at which point.
Note: In practice, I have experienced (seemingly only sometimes) that the network connection gets renamed from ens192 to VMware customization ens192. I therefore added a command to the script that renames it back.
#!/bin/bash
if [ x$1 == x"precustomization" ]; then
echo "Do Precustomization tasks"
elif [ x$1 == x"postcustomization" ]; then
echo "Do Postcustomization tasks"
hostnamectl hostname "${vm_name}"
nmcli connection modify "VMware customization ens192" connection.id ens192
nmcli connection modify ens192 ipv4.addresses ${vm_ip_mask} ipv4.gateway ${vm_gw} ipv4.method manual ipv4.dns ${vm_dns} ipv6.method disable
nmcli device reapply ens192
fi
In some cases, it may be necessary to replace == with = in the comparison. VCloud Guest Customization Script : [: postcustomization: unexpected operator
Applying configuration changes
Once the configuration is ready, we run the update using the Terraform CLI command terraform apply. The VM is created and customization is configured, including our script, which will run after the VM starts.
Only part of the output is shown below.
PS D:\VCD-terraform> terraform apply
data.vcd_catalog.my-catalog: Reading...
...
Terraform will perform the following actions:
...
# vcd_vapp_vm.DemoVM will be created
+ resource "vcd_vapp_vm" "DemoVM" {
...
+ customization {
+ admin_password = (sensitive value)
+ allow_local_admin_password = (known after apply)
+ auto_generate_password = (known after apply)
+ change_sid = (known after apply)
+ enabled = true
+ initscript = <<-EOT
#!/bin/bash
if [ x$1 == x"precustomization" ]; then
echo "Do Precustomization tasks"
elif [ x$1 == x"postcustomization" ]; then
echo "Do Postcustomization tasks"
hostnamectl hostname "Demo-VM"
nmcli connection modify "VMware customization ens192" connection.id ens192
nmcli connection modify ens192 ipv4.addresses 172.30.21.100/22 ipv4.gateway 172.30.20.1 ipv4.method manual ipv4.dns 172.30.20.1 ipv6.method disable
nmcli device reapply ens192
fi
EOT
+ join_domain = (known after apply)
+ join_domain_account_ou = (known after apply)
+ join_domain_name = (known after apply)
+ join_domain_password = (sensitive value)
+ join_domain_user = (known after apply)
+ join_org_domain = (known after apply)
+ must_change_password_on_first_login = (known after apply)
+ number_of_auto_logons = (known after apply)
}
...
Disabling customization on the VM
After the VM's first boot, during which customization ran, it is recommended to disable Guest OS Customization. This means updating the customization block configuration by setting the argument enabled = false, and then running terraform apply again.
customization {
enabled = false
initscript = templatefile("setup.tftpl", { vm_name = var.vm_name, vm_ip_mask = var.vm_ip_mask, vm_gw = var.vm_gw, vm_dns = var.vm_dns })
}
Note: I tried to find a way to trigger these two steps at once, but did not find a solution. Based on various discussions, this is a known limitation of the declarative approach.
Guest OS Customization logs
VMware Cloud Director saves our provided script (on Linux) to the file /root/.customization/customize.sh in the guest OS. Guest OS Customization logs are stored in the /var/log/vmware-imc/ directory. The primary log file is toolsDeployPkg.log, and optionally also customization.log.
Cloud-init
I came across mentions of other ways to customize the OS configuration of new VMs. One such approach is cloud-init. I have not tested it hands-on, but a description can be found in the article Using cloud-init for Customization with VCD and Terraform.
There are no comments yet.