Between my ever-expanding IoT gizmo collection and the never-ending VM and LXC images I want to tinker with, I’ve been getting deeper down the automation rabbit hole with each passing week. On the home lab front, I’ve been playing with high-availability PVE clusters and SBC-powered Kubernetes nodes, with Terraform configs and Ansible playbooks helping me automate every aspect of these systems.

But considering the sheer utility of Ansible, I figured I should try ditching Terraform for a few days and provision LXCs and virtual machines using a handful of playbooks and inventory files. Turns out, Ansible is more than capable of taking care of the tedious VM creation and configuration tasks all on its own.

The Proxmox host needed some tweaks to support Ansible playbooks

But installing the Python wrapper was a breeze

Up until now, I’d relied solely on Terraform to automate VM deployment on Proxmox. Since Terraform has a couple of Proxmox providers, all I had to do was create an API token on my PVE node and feed some credentials into a .tf file. Ansible, however, requires a couple more tweaks.

For starters, I had to look for a utility that could leverage the Proxmox Rest API to execute commands on my experimentation server, and the Python-based wrapper, Proxmoxer, is perfect for this task. Of course, I also had to grab the necessary Python dependencies, including virtualenv, to get Proxmoxer up and running. So, I headed to the Shell tab of my Proxmox node and ran these commands to set up the necessary packages:

apt install build-essential python3-dev libguestfs-tools -y
apt install python3 virtualenv
apt install python3 proxmoxer

I also set up a custom VM template while creating the API token

Next up, I needed an API token to allow my Ansible instance to run the contents of a playbook on Proxmox. Since I was working on a spare node, I decided to go with my root user to avoid dealing with permission issues when first attempting this project. Once I’d entered the API Token tab inside Datacenter, I created an easy-to-identify Token ID for the root@pam user and unchecked the Privilege Separation box to avoid dealing with messy ACLs down the line.

With the API token copied into my Vaultwarden instance, it was time to pick a virtual machine template for this project. Technically, going with a cloud-init template made more sense, since they’re light enough for automation experiments and have easy-to-modify configurations. But I wanted to make troubleshooting easier on my future self if things were to go wrong with the VM, which is why I decided to go for a custom, GUI-based Debian template.

So, I quickly spun up a Debian virtual machine and installed the king of vanilla distros on it. Once the installation was complete, I removed the Debian ISO mounted on the VM from the Hardware tab on Proxmox’s web UI. I’d planned to feed the VM deployed from this template to my Ansible playbooks, so I configured a static IP address for the VM before installing OpenSSH server and enabling SSH using these commands:

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

Connecting Ansible to Proxmox involved a lot of trial and error

I ended up going with an older Proxmox module for my Ansible instance

Like Terraform, Ansible needs a module to connect with Proxmox, and this is where I encountered some trouble. For reference, I’ve used a Semaphore LXC, which includes the official ansible (and not ansible-core) package. Yes, I know I wanted to make this an Ansible-only project, but I can’t go back to a terminal interface after using Semaphore’s simple web UI to execute playbooks. Anyway, the latest version of the Proxmox module has the community.proxmox.proxmox_lxc path, which should theoretically work after I’ve updated the LXC and all its packages, right?

Well, not quite. When I attempted to use this module to deploy a VM from my Debian template, Ansible failed to recognize it. After nearly an hour of sifting through forum posts, I used the older version of the module – specifically community.general.proxmox_kvm – and it worked just fine. So, my final vm_deployment.yml configuration file looked something like this:

---
- name: Proxmox VM automation
hosts: all
tasks:
- name: Task1
community.general.proxmox_kvm:
api_user: root@pam #The root user associated with the API token
api_token_id: ansible #The ID of the API token
api_token_secret: 09318c14-d9e7-4c77-acce-e25e6b1cfce5 #The API token from earlier
api_host: 192.168.0.100 #The IP address of the target Proxmox machine
clone: debian-template #The name of the VM template
name: debian-vm #The name of the soon-to-be-created VM
node: ayush #The name of the target Proxmox machine
storage: local-lvm
full: true
format: unspecified
timeout: 500

You'll have to follow the indentation rules when creating Ansible playbooks

If you’re wondering about how I executed this file, I used my pre-configured Semaphore instance. First, I created a Key Store containing my Proxmox credentials. Then, I added the following static code alongside the key store to an inventory file:

[proxmox-host]
192.168.0.100

I also created a Repository at the /home/proxmox-projects folder, where I’d added the YAML file earlier. Then, I used the Task Template tab to create an Ansible Playbook containing all the artifacts I’d created so far and pressed the Run button to see the VM spin up right before my eyes.

I created a playbook for LXCs (even though I didn't really need one)

Before arming my virtual machine with the necessary packages, I figured I could try my hand at automating LXC deployment as well. Truth be told, I didn’t really care that much about automatically creating LXC from code, because that’s precisely what the ultra-useful Proxmox VE Helper-Scripts repo does. Technically, it’s even more convenient because it deploys specialized containers for FOSS services.

Regardless, I created another YAML file in my Semaphore LXC for a Debian LXC. The only difference between this one and the VM-centric file from earlier is that it uses the community.general.proxmox module instead of the KVM one. Likewise, it uses the ostemplate parameter for the LXC template, and I also had to give it a password, so I could log in to the container once Ansible deploys it. After a few minutes of going back and forward between the Ansible documentation and the nano editor running on my Semaphore LXC, I managed to come up with this configuration:

- name: Create LXC container
hosts: all
tasks:
- name: lxc
community.general.proxmox:
node: ayush
hostname: lxc1
password: qwerty123
api_user: root@pam
api_token_id: ansible
api_token_secret: 09318c14-d9e7-4c77-acce-e25e6b1cfce5
api_host: 192.168.0.100
ostemplate: local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst
cores: 1
memory: 512
storage: local-lvm

I ran a couple of Ansible playbooks to customize the VM

Semaphore made everything a lot easier

I’ve been relying on Ansible to install apps and configure packages on freshly baked VMs for a while now, so I already had a couple of templates from my old projects. I tend to install Vim on all Linux virtual machines, and I’ve got this playbook configured to do just that:

---
- hosts: all
become: true
tasks:
- name: Vim installation
apt:
name: vim
state: latest
update_cache: true

Once I’d confirmed this playbook worked without any issues, I began importing the rest of my templates. I plan to turn this virtual machine into a productivity platform, so I used an old playbook that includes LibreOffice, Obsidian, Krita, and Darktable.

Proxmox and Ansible: A powerful duo for automation lovers

Now that my Ansible-only setup works without any issues, I’ll spend some time tinkering with cool playbooks I can run on my PVE node. I’ll probably look into cloud-init templates for my disposable coding VMs next. And since I’m already knee-deep in the networking rabbit hole, I’ll see if I can automate SDN stack deployment on Proxmox with Ansible in the coming weeks.