Appearance
Using Terraform to provision single node virtual machine on KVM
Environment
- Virtualization: KVM (via Libvirt)
- Tool: Terraform
- Operating System: Rocky Linux or another Red Hat-based OS (e.g., CentOS, RHEL)
Diagram

Configure Terraform
Steps
- Set up a directory for your terraform project:
bash
mkdir -p ~/workspace/homelab-kvm/
cd ~/workspace/homelab-kvm/- Create
provider.tfto connect to the local KVM instance:
hcl
provider "libvirt" {
uri = "qemu:///system"
}
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}qemu:///system: Connects to the system-wide KVM instance (requires root or Libvirt permissions).
- Create
variables.tfto store customizable settings. Adjust the defaults to match your environment:
hcl
variable "libvirt_pool_name" {
description = "KVM storage pool"
default = "pool-1"
}
variable "base_image_path" {
description = "images path to be used by virtual machine"
type = string
default = "/pool/1/images/Rocky-8-GenericCloud-Base.latest.x86_64.qcow2"
}
variable "vm_domain" {
description = "domain for virtual machine"
default = "example.com"
}
variable "vm_hostname" {
description = "vm hostname"
default = "homelab"
}
variable "vm_cpu" {
description = "number of CPU cores for the virtual machine"
type = number
default = 2
}
variable "vm_memory" {
description = "memory size for the virtual machine in MB"
type = number
default = 4096
}
variable "ip_address" {
type = string
description = "IP Addresses to be used by virtual machine (with CIDR)"
default = "172.23.0.89/26"
}- Use
virsh pool-listto find available storage pools if unsure aboutlibvirt_pool_name. - Adjust IP addresses and network settings to match your setup.
- Create
main.tffor the master VM:
hcl
data "template_file" "user_data" {
template = file("${path.module}/conf/user_data.yaml")
vars = {
hostname = var.vm_hostname
domain = var.vm_domain
}
}
data "template_file" "network_config" {
template = file("${path.module}/conf/network_config.yaml")
vars = {
ip_address = var.ip_address
}
}
resource "libvirt_volume" "instance_vol" {
name = "${var.vm_hostname}-vol"
pool = var.libvirt_pool_name
source = var.base_image_path
format = "qcow2"
}
resource "libvirt_cloudinit_disk" "cloudinit" {
name = "${var.vm_hostname}-cloudinit.iso"
user_data = data.template_file.user_data.rendered
network_config = data.template_file.network_config.rendered
pool = var.libvirt_pool_name
}
resource "libvirt_domain" "node" {
name = var.vm_hostname
memory = var.vm_memory
vcpu = var.vm_cpu
cloudinit = libvirt_cloudinit_disk.cloudinit.id
autostart = true
cpu {
mode = "host-passthrough"
}
network_interface {
macvtap = "enp4s7"
wait_for_lease = "false"
hostname = var.vm_hostname
}
console {
type = "pty"
target_port = "0"
target_type = "serial"
}
console {
type = "pty"
target_port = "1"
target_type = "virtio"
}
disk {
volume_id = libvirt_volume.instance_vol.id
}
}- Replace
enp4s7with your network interface if using MacVTAP (check withip link).
- Create a
configdirectory and add the following files:
bash
mkdir confnetworks_config.yml:
yaml
version: 2
ethernets:
eth0:
dhcp4: false
dhcp6: false
addresses:
- ${ip_address}
gateway4: 172.23.0.1
nameservers:
addresses:
- 8.8.8.8Adjust
gateway4to match your network.user_data.yml:
yaml
#cloud-config
# vim: syntax=yaml
hostname: ${hostname}
fqdn: ${hostname}.${domain}
prefer_fqdn_over_hostname: true
ssh_pwauth: true
disable_root: false
chpasswd:
list: |
root:password
expire: false
users:
- name: apps
sudo: ALL=(ALL) NOPASSWD:ALL
groups: users, admin
home: /home/apps
shell: /bin/bash
lock_passwd: true
ssh-authorized-keys: # list SSH Keys to authenticate
- ssh-rsa <your-id-rsa>- Replace
passwordwith a secure password and<your-id-rsa>with your public SSH key.
Deploy Terraform
Steps
- Initialize Terraform, Download required plugins and initialize the project:
bash
terraform initExample output:
bash
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of dmacvicar/libvirt from the dependency lock file
- Reusing previous version of hashicorp/template from the dependency lock file
- Using previously-installed hashicorp/template v2.2.0
- Using previously-installed dmacvicar/libvirt v0.8.3- Plan the Deployment, Preview the changes Terraform will make:
bash
terraform plan- Apply the Configuration, Deploy the VMs:
bash
terraform apply- Confirm with
yeswhen prompted. Wait for the process to complete (time depends on image size and system resources).
Verify the vm instance by running virsh list to check vm state
bash
$ virsh list
Id Name State
--------------------------
28 homelab runningTest the VMs
From the KVM host, test SSH connectivity to a VM:
bash
ssh root@<your-ip-address>- Replace
<your-ip-address>with an IP fromip_address_masterorip_address(e.g.,172.23.0.89). - If SSH fails, ensure the VM is running (
virsh list --all) and the network is configured correctly.