m4nifest0-tech

Search the site

Automation

Automated VM provisioning with Ansible and cloud-init

Repeatable creation of Proxmox virtual machines from a cloud-init template, with Ansible and Git as the source of truth for configuration.
Completed

Problems faced

  • Creating VMs by hand produced machines with slightly different configurations, depending on who had prepared them.
  • Every new VM required many repetitive steps: cloning, networking, users, keys, and base packages.
  • There was no reliable list of the running systems and what they were for.
  • Changes were not tracked: it was impossible to reconstruct when and why a configuration had changed.

Implemented solution

A VM template based on a Debian cloud image is cloned through the Proxmox API by an Ansible playbook. On first boot, cloud-init sets the automation user, public keys, hostname, and network parameters. Once first boot completes, Ansible applies the base roles (updates, time, monitoring agent, hardening) and then the service-specific roles. Inventory, variables, and playbooks live in a Git repository, so every change goes through a commit and review. Credentials are protected with Ansible Vault and the API user has minimal privileges.

Results

  • A new VM is ready to use with a single run, reproducible and identical on every repetition.
  • The inventory in the repository is the source of truth for the existing systems and their role.
  • Configuration changes are tracked in Git and can be reviewed or reverted.
  • Recreating a broken VM only requires rerunning the playbook, with no manual configuration.

Technical description

Creating a VM by hand means repeating the same steps and accepting small differences between one machine and another. This case study turns creation into a declarative flow: the VM is described in a versioned file and obtained with a single run.

Provisioning flow

  1. The VM is described in the Ansible inventory, in the Git repository: name, role, resources, network segment, and address.
  2. A playbook clones the template through the Proxmox VE API and sets the cloud-init parameters.
  3. On first boot, cloud-init configures the automation user, public keys, hostname, and network, then installs and starts the guest agent.
  4. Once first boot completes, Ansible connects and applies the base roles and the service-specific ones.
  5. The VM is registered with monitoring and included in the backup jobs.

Components and choices

  • Template from the official Debian cloud image, updated periodically and cloned in full mode, so that each VM is independent.
  • Proxmox API with a dedicated service user and a least-privilege role, instead of full administrative access. Credentials are protected with Ansible Vault and never appear in clear text in the repository.
  • Idempotent roles: rerunning the playbook on an already configured VM produces no changes.
  • Git as the source of truth: every change goes through a commit and review, with the option to go back to the previous version.
  • Addresses and names assigned by the inventory rather than picked by hand, to avoid duplicates and keep the address plan aligned with what is actually running.

The playbook automates the same sequence that, done by hand and in simplified form, corresponds to these commands:

Manual equivalent on the Proxmox node
qm clone 9000 120 --name vm-esempio --full
qm set 120 --ciuser automazione --ipconfig0 ip=192.0.2.50/24,gw=192.0.2.1
qm start 120

Note

The template contains no credentials: users and public keys are injected by cloud-init at clone time.

Testing and acceptance

Every role is tried on a throwaway VM before real use. A second run of the playbook must report no changes, confirming idempotency; static analysis with ansible-lint catches common mistakes and the --check --diff mode shows in advance what would change. I also check that cloud-init finishes without errors with cloud-init status --wait. The most telling test is to destroy a VM and recreate it from the repository: it must come back identical, confirming that the declared state matches reality.

Possible next steps

A continuous integration pipeline that runs lint and tests on every change, a dynamic inventory tied to the Proxmox API, automatic registration of DNS names and DHCP reservations, and a comparison with declarative tools such as OpenTofu. The details are in Ansible and cloud-init for provisioning on Proxmox.