Mastering Proxmox Automation: CLI Tools and Infrastructure as Code for Your Homelab

Transform your Proxmox homelab from manual management to fully automated infrastructure with CLI tools, Terraform, Ansible, and modern DevOps practices.

• 10 min read
proxmoxautomationinfrastructure-as-codehomelabdevops
Mastering Proxmox Automation: CLI Tools and Infrastructure as Code for Your Homelab

The Manual Management Trap

You’ve built an impressive homelab. Your Proxmox cluster hums along, running a dozen VMs and containers for everything from Home Assistant to your media server. But there’s a problem lurking beneath the surface: everything is configured manually.

When you need to spin up a new test VM, you click through the web UI. When a drive fails, you scramble to remember how you set up that ZFS pool six months ago. When you want to migrate a service, you’re manually exporting configs and hoping nothing breaks.

This isn’t sustainable. Worse, it’s brittle. One wrong click in the production VM’s settings, and you’re spending your Saturday night restoring from backup.

The solution? Automation and Infrastructure as Code.

In this guide, we’ll transform your Proxmox management from ad-hoc clicks to repeatable, version-controlled infrastructure. You’ll learn the native CLI tools that power Proxmox, how to integrate Terraform and Ansible for declarative management, and build workflows that make your homelab self-documenting and disaster-resistant.

The Proxmox CLI Landscape

Before diving into third-party tools, let’s understand what Proxmox provides out of the box. The native CLI tools are powerful—they’re what the web UI uses under the hood.

The Core Tools

Proxmox provides several command-line utilities that form the foundation of any automation:

ToolPurposeYour Automation Foundation
pveshREST API shell interfaceDirect API access, scripting backbone
qmQEMU/KVM VM managementCreate, clone, modify VMs
pctLXC container managementContainer lifecycle operations
pvecephCeph storage managementDistributed storage automation
pveperfPerformance benchmarkingCapacity planning data

pvesh: Your Automation Swiss Army Knife

pvesh is the most powerful native tool because it mirrors the entire REST API. Anything you can do in the web UI, you can do with pvesh:

# Get cluster information
pvesh get /version

# List all containers on a node
pvesh get /nodes/pve1/lxc

# Create a new user programmatically
pvesh create /access/users --userid automation@pve

# Create and start a container in one sequence
pvesh create /nodes/pve1/lxc \
  -vmid 200 \
  -hostname "monitoring" \
  --storage local-lvm \
  --password "secure-password-here" \
  --ostemplate "local:vztmpl/ubuntu-24.04-standard.tar.gz" \
  --memory 2048 \
  --cores 2 \
  --net0 "name=eth0,bridge=vmbr0,ip=dhcp"

pvesh create /nodes/pve1/lxc/200/status/start
Pro Tip

Pro tip: Use pvesh get to explore available endpoints. Start with pvesh get / to see the top-level paths, then drill down. The API structure mirrors the web UI navigation.

qm and pct: VM and Container Operations

For day-to-day operations, qm (for VMs) and pct (for containers) are more convenient:

# Clone a VM template for testing
qm clone 9000 120 --name "ci-test-ubuntu" --full 1

# Apply cloud-init configuration
qm set 120 \
  --cipassword 'TemporaryPassword!' \
  --sshkeys /root/.ssh/authorized_keys \
  --ipconfig0 ip=10.0.0.120/24,gw=10.0.0.1

# Start it up
qm start 120

# When testing is done, clean up
qm stop 120 --skiplock 1
qm destroy 120 --purge 1

For containers, the workflow is similar:

# Pull a container template
pveam download local ubuntu-24.04-standard

# Create a container
pct create 300 local:vztmpl/ubuntu-24.04-standard.tar.gz \
  --hostname "webserver" \
  --memory 1024 \
  --cores 1 \
  --rootfs local-lvm:8 \
  --net0 "name=eth0,bridge=vmbr0,ip=dhcp"

# Execute commands inside
pct exec 300 -- apt update && apt install -y nginx
Warning

Be careful with --skiplock: It bypasses Proxmox’s safety mechanisms. Only use it in scripts where you’re certain the VM isn’t running critical operations. For production, let the graceful shutdown complete.

Infrastructure as Code: The Declarative Approach

While the CLI tools are powerful, they’re imperative—you’re describing how to do something. Infrastructure as Code (IaC) tools let you describe what you want, and the tool figures out the how.

This distinction matters. With imperative scripts, changing a VM’s memory requires remembering what command to run. With declarative IaC, you update a configuration file and apply it—the tool detects the change and makes only the necessary updates.

Terraform + OpenTofu: Provisioning Foundation

The BPG Terraform Provider has become the gold standard for Proxmox integration. It supports both Terraform and OpenTofu (the open-source Terraform fork), giving you flexibility in your toolchain.

# main.tf
terraform {
  required_providers {
    proxmox = {
      source  = "bpg/proxmox"
      version = "~> 0.60"
    }
  }
}

provider "proxmox" {
  endpoint  = "https://proxmox.local:8006/api2/json"
  username = "root@pam"
  api_token = var.proxmox_api_token
  insecure = true  # Only for homelab with self-signed certs
}

# Define a reusable VM module
module "ubuntu_server" {
  source = "./modules/vm"
  
  name        = "webserver-01"
  target_node = "pve1"
  vm_id       = 100
  
  cpu_cores   = 2
  memory_mb   = 4096
  disk_gb     = 20
  
  network_bridge = "vmbr0"
  storage_pool   = "local-lvm"
  
  template_id = 9000
}

The power becomes apparent when provisioning multiple similar resources:

# Provision 3 worker nodes for a Kubernetes cluster
locals {
  k8s_workers = ["worker-01", "worker-02", "worker-03"]
}

resource "proxmox_virtual_environment_vm" "k8s_worker" {
  for_each = toset(local.k8s_workers)
  
  name      = each.value
  node_name = "pve1"
  vm_id     = 200 + index(local.k8s_workers, each.value)
  
  clone {
    vm_id = 9000  # Your base template
    full  = true
  }
  
  cpu {
    cores = 4
    type  = "host"
  }
  
  memory {
    dedicated = 8192
  }
  
  # Cloud-init for automatic IP assignment
  initialization {
    ip_config {
      ipv4 {
        address = "10.0.0.${201 + index(local.k8s_workers, each.value)}/24"
        gateway = "10.0.0.1"
      }
    }
    
    user_account {
      username = "admin"
      ssh_keys = [file("~/.ssh/id_rsa.pub")]
    }
  }
}

Apply it:

tofu init
tofu plan
tofu apply

Need to scale up? Add another entry to the list, run tofu apply, and Proxmox provisions the new VM with identical configuration.

Pro Tip

Start with templates: Create a minimal Ubuntu/Debian template with cloud-init pre-configured. All your VMs will then derive from a consistent base. The BPG provider handles cloud-init customization automatically.

Ansible: Configuration Management

Terraform excels at provisioning, but once VMs exist, you need to configure them. That’s where Ansible shines.

# playbooks/proxmox-provision.yml
---
- name: Create Proxmox VMs
  hosts: localhost
  connection: local
  tasks:
    - name: Create web server VM
      community.general.proxmox_kvm:
        api_host: "proxmox.local"
        api_user: "root@pam"
        api_token_id: "{{ vault_proxmox_token }}"
        api_token_secret: "{{ vault_proxmox_secret }}"
        node: "pve1"
        vmid: "100"
        name: "webserver"
        cores: 2
        memory: 4096
        disk: "local-lvm:20,format=qcow2"
        netif: '{"net0":"virtio,bridge=vmbr0"}'
        state: present
        clone: "ubuntu-24.04-template"

- name: Configure web server
  hosts: webservers
  become: true
  tasks:
    - name: Install NGINX
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true
    
    - name: Deploy application
      ansible.builtin.git:
        repo: "https://github.com/yourorg/webapp"
        dest: /var/www/webapp
        version: main

The real power comes from combining both tools in a pipeline:

  1. Terraform provisions VMs from templates with basic networking
  2. Ansible installs software and configures services
  3. Both configurations are version-controlled in Git
Warning

Store secrets properly: Never commit API tokens or passwords to Git. Use Ansible Vault, HashiCorp Vault, or environment variables. The extra setup time pays off when you need to share configs with others or recover after a disaster.

Packer: Automated Template Creation

Templates shouldn’t be created manually. Packer automates building consistent base images:

# ubuntu-24.04.pkr.hcl
packer {
  required_plugins {
    proxmox = {
      version = ">= 1.1.8"
      source  = "github.com/hashicorp/proxmox"
    }
  }
}

source "proxmox-iso" "ubuntu" {
  proxmox_url              = "https://proxmox.local:8006/api2/json"
  username                 = "root@pam"
  token                    = var.proxmox_api_token
  
  node                     = "pve1"
  vm_name                  = "ubuntu-24.04-golden"
  vm_id                    = 9000
  
  iso_url                  = "https://releases.ubuntu.com/24.04/ubuntu-24.04-live-server-amd64.iso"
  iso_storage_pool         = "local"
  iso_checksum             = "file:https://releases.ubuntu.com/24.04/SHA256SUMS"
  
  unmount_iso              = true
  format                   = "qcow2"
  
  ssh_username             = "installer"
  ssh_password             = "temporary"
  
  boot_command = [
    "<wait><enter>",
    "<wait5>o<enter>",
    "install<enter>"
  ]
  
  # Cloud-init preparation
  cloud_init               = true
  cloud_init_storage_pool  = "local-lvm"
}

build {
  sources = ["source.proxmox-iso.ubuntu"]
  
  provisioner "shell" {
    inline = [
      "apt-get update",
      "apt-get install -y qemu-guest-agent cloud-init",
      "systemctl enable qemu-guest-agent"
    ]
  }
}

Build templates on-demand:

packer init .
packer build -var "proxmox_api_token=${TF_VAR_proxmox_api_token}" .

Now you have reproducible templates. Security patch released? Rebuild the template, re-apply Terraform, and every new VM inherits the patches.

Putting It Together: Automation Workflows

Let’s walk through practical workflows you can implement today.

Workflow: CI/CD Pipeline for Test Environments

Create disposable test environments on every commit:

# .gitlab-ci.yml
variables:
  PROXMOX_API_ENDPOINT: "https://proxmox.local:8006/api2/json"

stages:
  - provision
  - test
  - destroy

provision_test_vm:
  stage: provision
  script:
    - tofu init
    - tofu apply -auto-approve -var="vm_name=test-${CI_COMMIT_SHA}"
    - echo "TEST_VM_ID=$(tofu output vm_id)" > deploy.env
  artifacts:
    reports:
      dotenv: deploy.env

run_tests:
  stage: test
  script:
    - ansible-playbook -i "${TEST_VM_ID}," test-playbook.yml
  needs:
    - provision_test_vm

cleanup:
  stage: destroy
  script:
    - tofu destroy -auto-approve
  when: always  # Run even if previous stages fail

Workflow: GitOps for Proxmox Configuration

Version control your Proxmox host configurations:

#!/bin/bash
# /opt/scripts/sync-pve-config.sh

CONFIG_REPO="/opt/pve-config-git"
LOG_FILE="/var/log/pve-config-sync.log"

log() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}

cd "$CONFIG_REPO" || exit 1

# Sync critical configs
rsync -a /etc/pve/storage.cfg "${CONFIG_REPO}/storage.cfg"
rsync -a /etc/pve/datacenter.cfg "${CONFIG_REPO}/datacenter.cfg"
rsync -a /etc/pve/qemu-server/ "${CONFIG_REPO}/qemu-server/"
rsync -a /etc/pve/lxc/ "${CONFIG_REPO}/lxc/" --exclude='*.pending'
rsync -a /etc/network/interfaces "${CONFIG_REPO}/interfaces"

# Commit changes
git add -A
if git diff --staged --quiet; then
  log "No changes detected"
else
  git commit -m "Auto-sync: $(date '+%Y-%m-%d %H:%M')"
  git push origin main
  log "Changes committed and pushed"
fi

Add to cron:

# Sync every 6 hours
0 */6 * * * /opt/scripts/sync-pve-config.sh
Pro Tip

Exclude sensitive files: Don’t sync files containing passwords or API tokens. Use .gitignore in your config repo and consider separate secret management.

Backup Automation and Disaster Recovery

Automation isn’t just about convenience—it’s about resilience. Your backup strategy should be as automated as everything else.

Proxmox Backup Server Integration

Proxmox Backup Server (PBS) provides enterprise-grade backup for free:

  • Incremental forever: Only changed blocks are backed up
  • Deduplication: Save storage across similar VMs
  • Encryption: Files are encrypted at rest
  • Scheduling: Set-and-forget retention policies
# Add PBS to Proxmox
pvesm add pbs pbs-backup \
  --server pbs.local \
  --datastore main \
  --username root@pam \
  --password "${PBS_PASSWORD}" \
  --fingerprint "${PBS_FINGERPRINT}"

# Configure automatic backups
pvesh create /cluster/backup \
  --storage pbs-backup \
  --mode schedule \
  --dow 'mon,tue,wed,thu,fri' \
  --starttime "02:00" \
  --compress zstd \
  --mode snapshot

Offsite Replication

For true disaster recovery, replicate backups offsite:

# On your PBS server, configure remote sync
# /etc/proxmox-backup/sync-remote.cfg
[remote:offsite]
remote = offsite-pbs.example.com
remote-store = backup-store
schedule = "daily" 02:00

This implements the 3-2-1 rule: 3 copies of data, on 2 different media types, with 1 stored offsite.

Warning

Test your restores: The most important backup statistic isn’t how many you’ve made—it’s how many you’ve successfully restored. Schedule quarterly restore tests and document the results.

Monitoring: See What’s Happening

Automated infrastructure needs automated monitoring. The standard stack remains Prometheus + Grafana.

Deploy PVE Exporter

# Install on each Proxmox node
apt install -y prometheus-pve-exporter

# Configure API access
cat > /etc/default/prometheus-pve-exporter << 'EOF'
PVE_USER=root@pam
PVE_PASSWORD="${PVE_PASSWORD}"
PVE_VERIFY_SSL=false
EOF

systemctl enable --now prometheus-pve-exporter

Add to your Prometheus config:

# prometheus.yml
scrape_configs:
  - job_name: 'proxmox'
    static_configs:
      - targets:
          - 'pve1:9221'
          - 'pve2:9221'
          - 'pve3:9221'
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
        replacement: "${1}:8006"

Import a pre-built Grafana dashboard, and you have real-time visibility into CPU, memory, storage, and VM states across your cluster.

Community Tools Worth Knowing

Beyond the core tools, the Proxmox community has built utilities worth exploring:

ToolPurposeWhy It Matters
ProxMenuxTerminal management UIk9s-like interface for Proxmox
PVE-AutoSnapSnapshot automationRetention policies without manual cleanup
Community Scripts400+ setup scriptsQuick deployments of popular containers
Proxmox NtfyPush notificationsGet alerts on your phone without complex setup

Community Scripts Repository

The Proxmox Community Scripts project provides ready-to-use automation for common setups:

# Install Home Assistant in LXC
bash -c "$(wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/ct/homeassistant.sh)"

# Install Pi-hole
bash -c "$(wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/ct/pihole.sh)"

# Post-install optimizations
bash -c "$(wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/misc/post-pve-install.sh)"
Pro Tip

Review before running: Always read the script source before executing. These are community-contributed, and while most are excellent, understanding what you’re running is good hygiene.

Getting Started: Your First Week

Ready to start automating? Here’s a realistic first-week plan:

Day 1-2: Learn the CLI

  • Practice qm, pct, and pvesh commands
  • Create and destroy test VMs from the command line
  • Understand the API structure with pvesh get

Day 3-4: Set up Infrastructure as Code

  • Install Terraform or OpenTofu
  • Create a simple VM template
  • Write your first resource definition
  • Run tofu plan and tofu apply

Day 5: Add Ansible

  • Write a playbook to install software on your Terraform VMs
  • Integrate with your existing configs

Day 6-7: GitOps and Backups

  • Initialize a Git repo for your configs
  • Set up PBS if you haven’t already
  • Schedule automated backups

Conclusion: From Homelab to Infrastructure

The journey from manual management to automated infrastructure transforms more than just your workflow—it changes how you think about your systems.

When every VM is defined in code, you stop asking “what did I configure?” and start asking “what do I want to configure?” Template-based provisioning means you can rebuild any service in minutes, not hours. Version control means you can see exactly what changed and when. And automated backups mean you’re not hoping disasters won’t happen—you’re prepared for when they do.

The tools we covered—Proxmox’s native CLI, Terraform/OpenTofu, Ansible, Packer, and backup automation—work together to create a cohesive workflow:

  1. Define your desired state in code
  2. Provision resources consistently with templates
  3. Configure services automatically with Ansible
  4. Monitor health with Prometheus
  5. Protect everything with automated backups

Your homelab doesn’t need enterprise complexity to benefit from these practices. Start small—automate one VM’s provisioning with Terraform. Add a backup schedule to PBS. Version control your /etc/pve configs.

Before you know it, you’ll have an infrastructure that’s not just automated—it’s self-documenting, reproducible, and resilient.

And when someone asks “how did you set this up?” you can finally answer: “Let me show you the code.”

Anthony Lattanzio

Anthony Lattanzio

Tech Enthusiast & Builder

I'm a tech enthusiast who loves building things with hardware and software. By night, I run a homelab that's grown way beyond what any reasonable person needs. Check out about me for more.

Comments

Powered by GitHub Discussions