Learn Ansible and Terraform for Free

How to provision and manage self-hosted virtual machines using custom disk images on Linux

If you want hands-on experience with Ansible and Terraform, you need infrastructure to manage. Most guides recommend paying for cloud resources on Amazon Web Services or Microsoft Azure. This article shows how to build a zero-cost local lab using your own Linux computer. We will use QEMU/KVM virtualization and the Terraform libvirt provider.

Lab requirements


Final Result ▶ Watch on YouTube

Here is what the complete workflow looks like once you have followed the how-to below:

1. Verify no VM is running

matthew@matthew-ThinkPad-E14-Gen-6:~$ virsh list --all
 Id   Name   State
--------------------

2. Provision with Terraform

matthew@matthew-ThinkPad-E14-Gen-6:~$ cd infra-lab/terraform/
matthew@matthew-ThinkPad-E14-Gen-6:~/infra-lab/terraform$ terraform apply
...
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

vm_ip = "192.168.122.100"

Terraform creates the disk, VM and Ansible inventory and outputs the IP.

3. Confirm tmux is not yet installed on the VM

matthew@matthew-ThinkPad-E14-Gen-6:~/infra-lab/terraform$ ssh matt@192.168.122.100
Welcome to elementary OS 8 (GNU/Linux 6.17.0-22-generic x86_64)

matt@ElementaryOS:~$ tmux
Command 'tmux' not found, but can be installed with:
sudo apt install tmux

4. Configure the VM with Ansible

matthew@matthew-ThinkPad-E14-Gen-6:~/infra-lab/ansible$ ansible-playbook -i inventory.ini playbook.yml

PLAY [Base lab configuration] *************************************************

TASK [Gathering Facts] ********************************************************
ok: [vm1]

TASK [Install packages] *******************************************************
changed: [vm1]

PLAY RECAP ********************************************************************
vm1      ok=2    changed=1    unreachable=0    failed=0

changed=1: Ansible detected tmux was missing and installed it.

5. Confirm tmux is now installed

matthew@matthew-ThinkPad-E14-Gen-6:~/infra-lab/ansible$ ssh matt@192.168.122.100
matt@ElementaryOS:~$ tmux
[exited]

tmux is installed and working.

6. Run again to demonstrate idempotency

matthew@matthew-ThinkPad-E14-Gen-6:~/infra-lab/ansible$ ansible-playbook -i inventory.ini playbook.yml

PLAY RECAP ********************************************************************
vm1      ok=2    changed=0    unreachable=0    failed=0

changed=0: Ansible verified the desired state is already satisfied and did nothing.

7. Cleanup

matthew@matthew-ThinkPad-E14-Gen-6:~/infra-lab/terraform$ terraform destroy

How To

1

Install Linux Mint (or your preferred flavour of Ubuntu)

2

Update your system

sudo apt update && sudo apt upgrade
3

Install Ansible

The cleanest way is to install with pipx as per the Ansible documentation:

sudo apt install pipx
pipx ensurepath
pipx install --include-deps ansible
4

Install Terraform

Ensure that your system is up to date and that you have installed the gnupg and software-properties-common packages. You will use these packages to verify HashiCorp's GPG signature and install HashiCorp's Debian package repository.

sudo apt-get update && sudo apt-get install -y gnupg software-properties-common

Install HashiCorp's 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's fingerprint:

gpg --no-default-keyring \
--keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg \
--fingerprint

The gpg command reports the key fingerprint:

/usr/share/keyrings/hashicorp-archive-keyring.gpg
-------------------------------------------------
pub   rsa4096 XXXX-XX-XX [SC]
      AAAA AAAA AAAA AAAA
uid         [ unknown] HashiCorp Security (HashiCorp Package Signing) <security+packaging@hashicorp.com>
sub   rsa4096 XXXX-XX-XX [E]

Add the official HashiCorp repository to your system:

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com \
$(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list

Update apt and install Terraform:

sudo apt update
sudo apt-get install terraform
5

Configure virtualization

First check that your CPU supports virtualization:

egrep -o 'vmx|svm' /proc/cpuinfo | head -1

If this returns vmx (Intel) or svm (AMD), your CPU supports virtualization.

Optionally install the cpu-checker package and check virtualization is enabled at the BIOS level:

sudo apt install cpu-checker
kvm-ok

If output is:

INFO: /dev/kvm exists
KVM acceleration can be used

You can proceed. Otherwise if you see:

INFO: Your CPU does not support KVM extensions
KVM acceleration can NOT be used

Try enabling virtualization at the BIOS level.

In this lab we are using Virtual Machine Manager (virt-manager) to configure the base virtual machine image using its GUI. Install it with:

sudo apt install virt-manager

Then reboot your system.

In this guide I will use Elementary OS (based on Ubuntu 24 LTS) for the virtual machine, feel free to use whatever distribution you like. Navigate to https://elementary.io/ and download the ISO image (just enter $0 unless you wish to support the project).

Now start up Virtual Machine Manager and check you can see QEMU/KVM support is working. Navigate to File → New Virtual Machine and browse for local installation media to find the ISO. Choose Ubuntu 24 LTS as the operating system since this is what Elementary is based upon, you can leave installation defaults.

When you boot into the live environment, choose to install the operating system onto the virtual machine and it will reboot. I'm setting the user as matt, if you use something else you will need to update the Terraform ansible_user variable later.

Once the OS is installed, you should be able to use the internet fine on the VM due to NAT. Update the environment:

sudo apt update && sudo apt upgrade
6

Configure SSH access

Find the VM's private IP address (within the libvirt NAT network):

ip a

On the VM, install the OpenSSH server:

sudo apt install openssh-server
sudo systemctl enable --now ssh

On the host machine, generate an SSH key pair:

ssh-keygen

Accept the default file path. If you already have an SSH key you use for other purposes, specify a different filename with -f ~/.ssh/id_ansible to avoid overwriting it.

Copy your public key to the VM:

ssh-copy-id youruser@VM_IP

Validate the SSH connection:

ssh youruser@VM_IP

Then configure the SSH daemon on the VM:

nano /etc/ssh/sshd_config

Disable password authentication and root login:

PasswordAuthentication no
PermitRootLogin no

Restart the SSH service to apply changes:

sudo systemctl restart ssh
systemctl status ssh

You have now configured passwordless SSH access from the host to the virtual machine!

The machine image created by virt-manager is stored at /var/lib/libvirt/images/ubuntu24.04.qcow2. This qcow2 file is the base image Terraform will clone for each VM it provisions. If your image name is different, you will need to update the Terraform base_image_path variable later.

⚠ Important: Now that we have finished configuring the base environment, delete the virtual machine in virt-manager. Right click the VM → Delete. When prompted, uncheck the option to delete the storage volume, this preserves the qcow2 file that Terraform will use.
7

Clone the lab environment and configure Ansible and Terraform

Clone the base config:

git clone https://github.com/MatthewBieda/HomeLabCore.git
cd HomeLabCore

Create an Ansible vault for secure secret storage by running this from the repo root:

ansible-vault create ansible/group_vars/lab/vault.yml

Add your sudo password for the VM to the vault like this:

ansible_become_password: <vm sudo password>

It will be encrypted. The ansible.cfg file refers to the vault password file to look up the vault password automatically.

Store the vault password in a hidden file outside the project:

echo "your_vault_password" > ~/.ansible_vault_pass

For a more enterprise-ready solution you can look into Red Hat Ansible Automation Platform.

Now configure Terraform. Navigate to the terraform directory and initialise:

cd terraform
terraform init

Update variables.tf for your environment if needed — the key variables are:

# Path to your base qcow2 image
variable "base_image_path" {
  default = "/var/lib/libvirt/images/ubuntu24.04.qcow2"
}
 
# Username configured during OS installation
variable "ansible_user" {
  default = "matt"
}

You are now ready to run the demo as shown at the top of this article!


Congratulations, you now have a complete infrastructure-as-code solution and playground, self-hosted for free.

Coming next: In a future post I plan to extend this lab into a multi-node Kubernetes cluster bootstrapped with kubeadm on the self-hosted virtual machines.

Thanks for reading!