Improving the mk1 vSphere deployment with variables, file organization, and tfvars.
Problem
The mk1 deployment works but everything is hardcoded.
Changing an IP, VM name, or CPU count means editing main.tf directly. Not reusable.
Solution
“Hardcoded values are tomorrow’s technical debt with interest.” - Infrastructure as Code Best Practice
“I don’t like it. I don’t agree with it. But I accept it.” - Bruce, Jaws. Hardcoding works until it doesn’t. Extract variables. Future-you will thank present-you.
Source: vsphere-vm-mk2
File Organization
.
├── main.tf # Resources and data sources
├── variables.tf # Variable declarations
├── outputs.tf # Output definitions
├── versions.tf # Terraform and provider versions
└── terraform.tfvars # Variable values (git-ignored)
Single-file Terraform is fine for learning. For anything you plan to maintain or share, split it.
“File organization is documentation. Future-you will thank present-you for variables.tf.” - Terraform maintainability
Files named *.auto.tfvars are loaded automatically without being specified on the command
line. Useful for layering environment-specific values (e.g., dev.auto.tfvars,
prod.auto.tfvars) when combined with workspaces.
Variables in the Resource Block
The mk2 resource block replaces hardcoded values with var.* references:
resource "vsphere_virtual_machine" "vm" {
name = var.vm_name
resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
datastore_id = data.vsphere_datastore.datastore.id
num_cpus = var.vm_num_cpus
memory = var.vm_memory
guest_id = data.vsphere_virtual_machine.template.guest_id
scsi_type = data.vsphere_virtual_machine.template.scsi_type
network_interface {
network_id = data.vsphere_network.network.id
adapter_type = data.vsphere_virtual_machine.template.network_interface_types[0]
}
disk {
label = "disk0"
size = data.vsphere_virtual_machine.template.disks.0.size
eagerly_scrub = data.vsphere_virtual_machine.template.disks.0.eagerly_scrub
thin_provisioned = data.vsphere_virtual_machine.template.disks.0.thin_provisioned
}
clone {
template_uuid = data.vsphere_virtual_machine.template.id
customize {
network_interface {
ipv4_address = var.vm_ipv4_address
ipv4_netmask = var.vm_ipv4_netmask
}
ipv4_gateway = var.vm_ipv4_gateway
dns_server_list = var.vm_dns_server_list
dns_suffix_list = var.vm_dns_suffix_list
linux_options {
host_name = var.vm_name
domain = var.vm_domain
}
}
}
lifecycle {
ignore_changes = [
annotation,
clone[0].template_uuid,
clone[0].customize[0].dns_server_list,
clone[0].customize[0].network_interface[0]
]
}
}
Variable Declarations
Each variable gets a type, description, and optional default in variables.tf:
variable "vsphere_user" {
type = string
description = "Username for vSphere API operations."
default = "admin"
}
variable "vsphere_password" {
type = string
description = "Password for vSphere API operations."
sensitive = true
default = "password"
}
variable "vsphere_server" {
type = string
description = "vCenter server for vSphere API operations."
default = "vc-01"
}
variable "vsphere_allow_unverified_ssl" {
type = bool
description = "Disable SSL certificate verification."
default = true
}
Type Keywords
| Type | Use |
|---|---|
string |
Text values (names, IPs, hostnames) |
number |
Numeric values (CPU count, memory) |
bool |
Flags (true/false) |
list(string) |
Collections (DNS servers, suffixes) |
Add validation blocks to variables to catch bad input early. For example, validate that
vm_num_cpus is at least 1 or that vm_ipv4_address matches an IP regex. Terraform will
reject the plan before anything is created.
Outputs
Same as mk1, just in their own file:
output "vm_ip_address" {
value = vsphere_virtual_machine.vm.default_ip_address
}
output "vm_hostname" {
value = vsphere_virtual_machine.vm.name
}
tfvars
The terraform.tfvars file provides values for declared variables:
vsphere_datacenter = "Datacenter"
vsphere_datastore = "tpa-lab-01-ds-01"
vsphere_compute_cluster = "lab"
vsphere_network = "pg-v110"
vsphere_template = "templates/packer/ubuntu/linux-ubuntu-20"
vm_name = "test-vm-01"
vm_domain_name = "foggyclouds.net"
vm_num_cpus = 2
vm_memory = 1024
Full example: terraform.tfvars
Never commit a populated terraform.tfvars to a public repository. Add it to .gitignore
and provide a .tfvars.example with placeholder values instead.
terraform console is a REPL for testing expressions before putting them in code. Use it to
verify variable interpolation, function behavior, or data source lookups:
terraform console
> var.vm_name
"test-vm-01"
> cidrhost("10.1.110.0/24", 185)
"10.1.110.185"
> length(var.vm_dns_server_list)
1
Saves time versus running plan repeatedly to check if an expression evaluates correctly.
What’s Next
Variables make the deployment reusable, but the code is still in one project. mk3 packages it into a Terraform module for sharing and composition.