Skip to content

NordicsSys/proxdeploy

Repository files navigation

ProxDeploy — Proxmox VE VM Deployment CLI

Create, provision, and manage KVM virtual machines on Proxmox VE via YAML config and templates. Supports cloud-init, SSH provisioning, dry-run, and full API resilience.

Features

  • Ticket + CSRF authentication against the Proxmox API (requests)
  • deploy: create a VM from config.yaml + a template, optionally apply cloud-init, then start it
  • list: show all QEMU VMs on a node (table or JSON)
  • destroy: stop and delete a VM with confirmation
  • --dry-run: validate config + template and show the plan without touching Proxmox
  • Cloud-init: set user, password, SSH keys, IP config, nameserver, search domain via template fields
  • UPID task polling: wait for async Proxmox tasks (create, start, stop, destroy) to finish before proceeding
  • Duplicate detection: warns if a VM with the same name already exists
  • Optional SSH provisioning: wait for port 22, connect with Paramiko (key and/or password), run a bash script
  • Resilience: connect/read timeouts, automatic retries with backoff for transient HTTP (5xx/429) and network issues
  • Validation: config.yaml, templates, and CLI flags are checked before any VM operation
  • Logging at INFO / DEBUG / ERROR (--debug or -v for sanitized API traces)
  • --no-color / NO_COLOR for log-friendly output; explicit exit codes for automation

Demo

Deploy a VM

$ python cli.py deploy -t templates/docker.yaml

Target API: https://pve.example.com:8006  (node: pve)
Template:   docker.yaml  →  VM name 'docker-host'
Resources:  4096 MB RAM, 4 cores, 40 GB disk
OS image:   debian-12-genericcloud-amd64 (used as disk source reference)
Cloud-init: user=admin, ssh_keys=1, ip=dhcp
Script:     scripts/install_docker.sh
  ✓ Created VM ID 101
  ✓ Cloud-init config applied
  ✓ Started VM 101 (docker-host)
  ✓ Provisioning finished on 10.0.0.101

Dry run (no changes made)

$ python cli.py deploy -t templates/docker.yaml --dry-run

Target API: https://pve.example.com:8006  (node: pve)
Template:   docker.yaml  →  VM name 'docker-host'
Resources:  4096 MB RAM, 4 cores, 40 GB disk
Cloud-init: user=admin, ssh_keys=1, ip=dhcp
Script:     scripts/install_docker.sh
  ✓ Dry-run complete — plan is valid. No changes made.

List VMs on a node

$ python cli.py list

Listing VMs on https://pve.example.com:8006 node=pve
------------------------------------------------------------------------
VMID     NAME                     STATUS       MEM(MB)    CPUS   PID
------------------------------------------------------------------------
100      docker-host              running      4096       4      18923
101      nginx-proxy              running      2048       2      19450
102      dev-ubuntu               stopped      1024       1      -

Destroy a VM

$ python cli.py destroy --vmid 102

Target: https://pve.example.com:8006 node=pve, VM 102
  VM 102: name='dev-ubuntu', status=stopped
Destroy VM 102 ('dev-ubuntu') on node pve? This cannot be undone [y/N]: y
  Destroying VM 102 (purge=True)...
  ✓ VM 102 (dev-ubuntu) destroyed

Requirements

  • Python 3.9+
  • A Proxmox VE node and credentials (user@realm, e.g. root@pam)

Install

cd proxdeploy
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Configure

  1. Copy the example config and edit it:

    cp config.yaml.example config.yaml
  2. Fill in your host, node, username, and defaults.

  3. Prefer a password via environment variable:

    export PROXDEPLOY_PASSWORD='your-secret'
  4. Set proxmox.verify_ssl: false only if you use a self-signed cert.

  5. Optional API tuning under proxmox:

    • connect_timeout / read_timeout (seconds)
    • max_retries, retry_backoff_base, retry_max_wait

Exit codes

Code Meaning
0 Success
1 General CLI / usage error
2 Configuration or template validation error
3 Proxmox authentication / session error
4 SSH provisioning error

Usage

Deploy

python cli.py deploy -t templates/docker.yaml
python cli.py deploy -t templates/docker.yaml --dry-run
python cli.py --debug deploy -t templates/docker.yaml

Deploy with cloud-init

Add cloud-init fields to your template:

name: cloud-vm
memory: 4096
cores: 4
disk: 40
os_image: debian-12-genericcloud-amd64
ci_user: admin
ci_password: changeme
ci_ssh_keys:
  - ssh-rsa AAAA...
ci_ip_config: ip=dhcp
python cli.py deploy -t templates/cloud-vm.yaml --ssh-key ~/.ssh/id_rsa --user admin

SSH provisioning (after boot)

If the template defines script:, deploy waits until SSH is reachable, then runs it:

export PROXDEPLOY_SSH_PASSWORD='guest-root-password'
python cli.py deploy -t templates/docker.yaml --ssh-key ~/.ssh/id_rsa --user root
python cli.py deploy -t templates/nginx.yaml --ssh-key ~/.ssh/id_rsa --ssh-host 192.168.1.80

List VMs

python cli.py list
python cli.py list --format json

Destroy a VM

python cli.py destroy --vmid 105
python cli.py destroy --vmid 105 --force

Template fields

Templates are validated before any API call. Unknown keys are rejected. Required keys:

Field Required Description
name yes VM name (non-empty string)
memory yes RAM in MB (integer >= 16)
cores yes vCPU count (integer >= 1)
disk yes Disk size in GB (integer >= 1)

Optional fields:

Field Description
os_image Disk image reference on storage (future / import workflows)
script Local bash file to run over SSH after boot
bridge Network bridge (default defaults.bridge)
storage Storage pool ID (default defaults.storage)
ostype Guest type (default l26)
vmid Fixed VM ID (otherwise next free cluster ID)
ssh_host Guest SSH address (overridden by --ssh-host)
ssh_password SSH password (prefer PROXDEPLOY_SSH_PASSWORD)

Cloud-init fields (all optional):

Field Description
ci_user Cloud-init user
ci_password Cloud-init password
ci_ssh_keys SSH public key(s) — string or list
ci_ip_config IP config string (default ip=dhcp)
ci_nameserver DNS nameserver
ci_searchdomain DNS search domain

Tests

pip install -r requirements-dev.txt
python -m pytest tests/ -v

53 tests covering: template validation, config validation, API auth/retries/errors, VM operations, UPID task polling, log sanitization, CLI dry-run/deploy/list/destroy.

Project layout

cli.py                  # Click CLI entrypoint (deploy, list, destroy)
proxmox/
  api.py                # Auth, retries, timeouts, HTTP helpers
  vm.py                 # KVM create / start / stop / destroy / list
  tasks.py              # UPID async task polling
  cloudinit.py          # Cloud-init config + disk import
  template.py           # Template validation + ValidatedTemplate
  config_validate.py    # config.yaml validation
  sanitize.py           # Redact secrets in API payloads for logs
  exceptions.py         # Typed errors + exit codes
  guest.py              # QEMU guest-agent IPv4 discovery
provision/
  ssh.py                # TCP wait + Paramiko script run
scripts/                # Example install_*.sh referenced by templates
templates/              # docker.yaml, nginx.yaml, basic-ubuntu.yaml
tests/                  # pytest suite (53 tests)
config.yaml.example     # Example config (copy to config.yaml)

Author

NordicsSys

License

MIT — see LICENSE.

About

CLI tool to deploy, list, and destroy KVM guests on Proxmox VE using YAML templates — with cloud-init, SSH provisioning, and dry-run support.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors