Video
Below is a video explanation and demo.
Video Chapters
You can skip to the relevant chapters below:
- 00:00 – Introduction
- 01:26 – Whiteboard Setup
- 04:32 – vSphere UI
- 05:49 – Terraform Cloud UI
- 07:39 – Variable Sets New to TFC
- 09:06 – Windows Server AD Domain Joined
- 10:55 – Terraform Main.tf File Walk-through
- 13:15 – Packer Used for the Template
- 14:20 – Closing
Overview
In previous blog posts, we’ve worked with the VMware vSphere provider. I’m still of the opinion that there is a lot of infrastructure automation efficiencies to be realized in on-premises environments running VMware. The appeal of Terraform is that it is cloud/platform agnostic. Once you understand how the logic works, in theory, it should be a relatively painless transition to working with a different provider. If you’re not familiar with what a provider is, think of it as a plugin into the Terraform core that allows you to talk to the API of the cloud/platform you’re provisioning on. These providers don’t necessarily need to be cloud-related. Some examples of these providers are for: Azure, GCP, AWS, VMware vSphere, Datadog, F5 networks, and so on. Many vendors build these providers to facilitate access to their APIs via Terraform.
I did some research and found that there aren’t too many terraform vSphere Windows examples out there. So I decided to create one and demonstrate it. Our aim here is to build a terraform vSphere Windows server 2019 virtual machine and add it to the few terraform vsphere provider examples available. We will also show how to join this VM to an active directory domain during the build process. So let’s get started.
Code
Pre-requisites
The following is required to follow along:
- Terraform
- Terraform Cloud
- Access to a vSphere instance (tested on vSphere v6.7)
Setup
Below is our setup diagram. It is fairly straightforward. We are running Terraform on Terraform Cloud (TFC). GitLab is our version control system (VCS). We are using the VCS workflow in TFC. This allows us to commit and push code into GitLab which will automatically trigger a terraform init, plan, and apply. TFC also can provide cost estimates and run some policy checks. Since we’re provisioning on-prem, cost estimation doesn’t apply. Also, we are not creating policies in this example.
We are running a cloud agent internally in the form of a docker container. This agent polls TFC regularly asking if there is a job ready to provision. Cloud agents are a TFC business tier feature that allows us to provision on-prem without having to open a firewall inbound. You could sign up for a trial or use TFC with local execution in this example.

Terraform vSphere Windows Example Configuration
In this section, we will explore the main points in the Terraform configuration. You can get access to the full code here.
Terraform Folder Structure
Below is a screenshot of our folder structure. It’s flat, one folder with a few files.
Terraform Initialization
In the main.tf
file, we will need vSphere admin user credentials to be defined in the vsphere provider
block. Basically a name and password of a vSphere user with access to vCenter to create VMs by cloning templates. These credentials are defined as variables and need to be stored securely in Terraform Cloud (TFC) encrypted at rest. If you are not using TFC and using Terraform CLI open-source or TFC local execution, then you could use a terraform.tfvars
file, but make sure you don’t check that into git. My personal recommendation is to use TFC as it stores both the credentials and the state file securely in a central location.
The very first step to using Terraform is to initialize things. So if you are using the CLI, you first need to run the command terraform init
to download terraform vsphere provider. If you are using TFC with the VCS workflow, upon a commit and a push, TFC will run terraform init
in TFC’s own infrastructure. It will then proceed with the terraform plan
and other steps shown in the video demo above.
Terraform Variables
Take a close look at the variables.tf
file. You will see all the necessary Terraform variables there. These variables are referenced in the main.tf
file. One example of a variable I’m using is a Terraform dns_server_list example and is found in the dns_server_list
variable. Notice that we declare the variables in the variables.tf
file and assign them in the values.auto.tfvars
file. TFC doesn’t allow you to use a terraform.tfvars
file. So you need to use a filename with this format: *.auto.tfvars
.
Terraform Data Blocks
In addition to variables, we are also making use of the data blocks
in Terraform. A data block
allows you to retrieve data, whereas a resource block
allows you to provision a resource. A couple of examples of data blocks
are found in the main.tf
file:
- Datacenter block
data "vsphere_datacenter" "dc" { name = "Datacenter" }
Notice the first argument vsphere_datacenter
is a reserved keyword used by the vSphere provider. The second one dc
is an arbitrary name you can give it and will be referenced in other blocks in your Terraform configuration.
- Datastore block
data "vsphere_datastore" "datastore" { name = "Datastore2_NonSSD" datacenter_id = data.vsphere_datacenter.dc.id }
Notice in this datastore block we are referencing the datacenter block’s IP like this: data.vsphere_datacenter.dc.id
Terraform vSphere Virtual Machine Resource Block
The vsphere_virtual_machine
resource block is what actually provisions the Windows Server 2019 VM. The configuration is straightforward and most parameters are referencing variables already defined. We also have the ability to provision multiple VMs in parallel with the count
parameter. Terraform saves us time by doing so. This count
parameter is referencing the vm-count
variable. In my example, I’m only using a vm-count
of 1, but you could change this to however many VMs you need to create at the same time. Now let’s take a look at the customization block that allows us to define a few parameters for our VM.
Terraform Customization Block
You can find the entire code here, but let’s focus our attention on the customize block for the Windows VM.
resource "vsphere_virtual_machine" "vm" { ...truncated customize { windows_options { computer_name = "${var.vm-name}-${count.index + 1}" # workgroup = "test" join_domain = var.domain domain_admin_user = var.domain_admin_user domain_admin_password = var.domain_admin_password admin_password = var.local_adminpass # product_key = var.productkey # organization_name = var.orgname # run_once_command_list = var.run_once # auto_logon = var.auto_logon # auto_logon_count = var.auto_logon_count # time_zone = var.time_zone # product_key = var.productkey # full_name = var.full_name } network_interface { ipv4_address = var.ipv4_addresses[count.index] ipv4_netmask = var.ipv4_netmasks[count.index] dns_server_list = var.dns_server_list dns_domain = var.domain } ipv4_gateway = var.vmgateway timeout = 30 } ...truncated
Notice the 3 parameters below which are essential in joining this VM to the Windows domain:
1. join_domain
– Domain used for DNS and Active Directory
2. domain_admin_user
– Domain username with necessary privileges to join a computer to the domain
3. domain_admin_password
– Domain password with necessary privileges to join a computer to the domain
There are other parameters that you can define such as the product_key
to activate Windows. You could also use a workgroup
instead of a domain if that’s what you need.
Terraform Clones a Packer Generated vSphere Template
Terraform can be used to clone an existing vSphere template image. We had already created a vSphere template image with another HashiCorp tool called Packer. Packer is a great tool to speed up the creation of VM images. Similar to Terraform, Packer is cloud/platform agnostic and can be used with other clouds such as AWS, Azure, and GCP. Here is a link to learn how to use Packer to Create a Windows Server 2019 image.
Below we use a data block
to call on the image already created, then we use a clone block inside the resource block
to reference this template and use it to build our VM.
...truncated data "vsphere_virtual_machine" "template" { name = var.windows_template datacenter_id = data.vsphere_datacenter.dc.id } ...truncated resource "vsphere_virtual_machine" "vm" { ...truncated clone { template_uuid = data.vsphere_virtual_machine.template.id ...truncated }
Logging in as an Active Directory User
Once Terraform finishes provisioning our VM, we can move on to the next step of testing. Let’s test by logging into the new Windows Server 2019 VM using an already existing active directory (AD) user. Below is a screenshot from my domain controller showing the user sam
in the domain adhome
.

After logging into the domain with the following username: adhome\sam
let’s take a look at the new Windows 2019 server information:

As you can see in the screenshot above, our VM is successfully joined to the domain. Congratulations, you did it!
Conclusion
In this post, we learned how to build a Terraform vSphere Windows Server 2019 VM and join it to an AD Domain. We saw the benefits of using Terraform for infrastructure as code and how the process is well documented in code and repeatable. We were able to customize our VM by configuring the Computer Name, IP address, the Windows domain to join and a few other parameters.
We also used a Windows Server 2019 image that was built with another HashiCorp tool called Packer. Here is the link to learn how to use Packer to Create a Windows Server 2019 image.
If you would like to see a Linux example in VMware vSphere, we also created a post on building a Ubuntu 20.04 image in vSphere. Then another post to use this image to Build a Kubernetes k3s Cluster in vSphere with Terraform and Packer.
Informative article, totally what I wanted to find.
Thank you!
I have been having issues with Terraform-generated VMs having their NICs in a disconnected state upon boot-up in my VSphere environment. They are generated from a Packer-sourced Windows template. Is there a particular setting (Packer or Terraform) that needs to be configured in order to have the VMs boot with a connected NIC?
Hi Paul, pls make sure VMware Tools is installed in your Windows template. Sometimes connectivity issues can be attributed to missing or outdated VMware Tools.
Also did you use my Packer template for Windows? Pls check this post: https://tekanaid.com/posts/hashicorp-packer-vmware-windows-templates-and-terraform-for-vms