Skip to main content
OpenTofu made the box. This makes it Proxmox.
Language: Shell  ·  Status: active  ·  Last active: this week  ·  Source on GitHub
ansible-proxmox is the middle tier of the Proxmox stack. It owns everything that needs to be true about a Proxmox host before any workload runs on it. That includes ZFS, networking, swap, and CPU tuning, users, hardening, and monitoring agents.

What it does

  • Provisions ZFS pools and datasets with sensible defaults for VM and LXC storage
  • Realizes the OpenTofu node_storage contract (the zfs_pools role): creates datasets, sets quotas, and registers PVE storage. Pool creation from raw disks stays a per-host commissioning step
  • Sets up network bonding and bridges to match the homelab topology
  • Applies performance tuning (CPU governor, swap, sysctl) for VM density
  • Installs and configures monitoring agents that feed Splunk via Cribl
  • Hardens the host: SSH config, firewall rules, baseline auditd

How it fits

Run once per host after OpenTofu finishes. Re-running is safe. Every role is idempotent.

Inventory conventions

  • Declare once. A value true for every Proxmox node lives in inventory/group_vars/proxmox/; one true for both rack servers lives in group_vars/rack_servers.yml. Never copy it into host_vars.
  • Group plus node maps. A map that needs per-node additions is a group <var>_common merged with a host <var>_node through combine(..., recursive=True). sanoid_datasets and zfs_pools_vm_disk_properties use this pattern.
  • Split at the token limit. CI caps each file’s size. A file that grows past it becomes a directory of numbered files (00-core.yml, 01-storage.yml); Ansible loads them with the same precedence.
  • Name the storage, not the pool. pve_dataset_root_by_storage maps each Proxmox storage id to its ZFS dataset root from the node’s own declared storage. Snapshot policies reference a storage, so they follow a guest whose disk moves tier.
  • Null-safe defaults. Expressions over the published inventory use default(x, true). The one-argument form replaces only undefined values, so a published null passes through and fails later with an unrelated parse error.
  • Snapshots feed replication. Replication pulls with --no-sync-snap, so a dataset without a local snapshot policy is never copied off the node.
  • Tests use synthetic values. Test inventories and fixtures use placeholder names, never real node, pool or dataset names.

Getting started

1

Clone and enter the dev shell

git clone https://github.com/dryvist/ansible-proxmox && cd ansible-proxmox && nix develop
2

Point at the OpenTofu inventory

Ansible reads the host list that tofu-proxmox wrote out. The README covers the exact path and var precedence.
3

Required environment

The playbooks read host passwords, SSH keys, and monitoring tokens as environment variables.
4

Run the playbook

ansible-playbook -i inventory site.yml. The first run is the slow one; subsequent runs only converge what’s drifted.

tofu-proxmox

The provisioner. Run this first.

ansible-proxmox-apps

The app deployer. Run this third.

Configuration overview

How all the Ansible repos fit together.

Source on GitHub

Roles, inventory examples, full README.