A friendly, AI-friendly command-line tool to explore and manage a Proxmox VE cluster.
pmox wraps the Proxmox API with clean commands, pretty tables for humans, and a
--json mode for machines. It is read-only by default so you (or an AI) can
explore safely, with two layers of protection before anything can change.
pmox health
pmox vm list
pmox vm describe 100
# Spin up a cloud-init VM in one command:
pmox --dangerous vm new web --image ubuntu-24.04 --size small --disk 50 \
--ssh-key ~/.ssh/id_ed25519.pub --ip dhcp --wait
# Edit a running VM:
pmox --dangerous vm set 100 -o cores=4 -o memory=4096
pmox --dangerous vm resize 100 --disk scsi0 --size +10G
pmox --dangerous vm rename 100 web01
pmox --dangerous vm tag 100 --add prod,k3s
pmox --dangerous vm start 100
pmox --dangerous vm delete 100 --yes
Two independent gates protect your cluster:
| Gate | Flag | Applies to | Default |
|---|---|---|---|
| Dangerous mode | --dangerous (or PMOX_DANGEROUS=1) |
any state change (power, create, delete, clone, migrate, snapshot) | off — read-only |
| Confirmation | --yes |
destructive ops: delete, stop, reset, migrate, rollback, snapshot delete, and set with a delete= key |
required when non-interactive |
So:
- Explore with no flags — nothing can be modified.
- Change something benign (e.g.
start) — add--dangerous. - Destroy something (e.g.
delete) — add--dangerousand--yes. Note:--dangerousis global, but--yesis a per-subcommand flag, so it goes after the subcommand —pmox --dangerous vm delete 100 --yes, notpmox --dangerous --yes vm delete 100.
When running non-interactively (e.g. an AI calling the CLI), a destructive op
without --yes is refused rather than silently prompted. Exit codes:
0 ok · 1 error · 2 config · 3 confirmation required · 4 read-only.
Under --json, errors are a JSON envelope {"ok": false, "error": "...", "need": [...], "message": "..."}.
From PyPI:
pip install pmoxOr install from source (editable), e.g. for development:
macOS / Linux
git clone https://github.com/lukebward/pmox.git
cd pmox
python3 -m venv .venv
source .venv/bin/activate
pip install -e .Windows (PowerShell)
git clone https://github.com/lukebward/pmox.git
cd pmox
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -e .This puts a pmox command on your PATH. You can also run it without installing
via python -m pmox.
Create an API token in Proxmox: Datacenter → Permissions → API Tokens. For full management, give the token the privileges it needs (or, for a homelab, uncheck "Privilege Separation" so it inherits the user's permissions).
Provide configuration via environment variables, a .env file, a TOML config
file, or CLI flags (highest priority last):
.env (copy from .env.example):
PROXMOX_HOST=192.168.1.10
PROXMOX_TOKEN_ID=root@pam!pmox
PROXMOX_TOKEN_SECRET=00000000-0000-0000-0000-000000000000
PROXMOX_VERIFY_SSL=falseTOML at ~/.config/pmox/config.toml (or point --config / PMOX_CONFIG at one):
host = "192.168.1.10"
token_id = "root@pam!pmox"
token_secret = "..."
verify_ssl = falseTLS verification defaults to off because homelab Proxmox uses self-signed certificates. Set
PROXMOX_VERIFY_SSL=true(or--verify-ssl) if your node has a CA-signed cert.
pmox version Proxmox version of the connected node
pmox health One-shot cluster health triage (read-only)
pmox nodes list nodes + CPU/mem/uptime
pmox nodes status <node> detailed node status
pmox cluster status cluster membership/quorum
pmox cluster resources [--type] everything the cluster sees (vm|node|storage|...)
pmox vm list [--node N] QEMU VMs (cluster-wide)
pmox vm status <vmid> live status (node auto-resolved)
pmox vm config <vmid> raw configuration
pmox vm describe <vmid> consolidated view: status + config + snapshots + tasks
pmox vm ip <vmid> [--all] live IP(s) from the guest agent (--all: loopback/link-local/MAC)
pmox vm set <vmid> -o key=val update config (needs --dangerous; delete=key needs --yes)
pmox vm resize <vmid> --disk D --size [+]G grow a disk (needs --dangerous)
pmox vm rename <vmid> <newname> rename (needs --dangerous)
pmox vm tag <vmid> --add t1,t2 add tags; --remove / --set also available (needs --dangerous)
pmox vm start|shutdown|reboot|suspend|resume <vmid> (needs --dangerous)
pmox vm stop|reset <vmid> (needs --dangerous --yes)
pmox vm new [name] [--image img | --from-template id] create VM (needs --dangerous; see Provisioning)
pmox vm up <name> --image <name> ready-to-SSH VM (DHCP; --ip or [network] pool for a static IP)
pmox vm create <vmid> --node N [-o key=val ...] low-level create
pmox vm clone <vmid> --newid <id> [--name X] [--full] [--target N]
pmox vm migrate <vmid> --target N [--online] (needs --dangerous --yes)
pmox vm delete <vmid> [--purge] (needs --dangerous --yes)
pmox vm snapshot list|create|delete|rollback <vmid> ...
pmox ct ... same as `vm`, for LXC containers
pmox ct describe <ctid> consolidated view of a container
pmox ct new [name] --template <t> create container from template (needs --dangerous; see Provisioning)
pmox storage list [--node N] storage usage
pmox storage content <id> --node N
pmox task list --node N recent tasks
pmox task status|log <upid> --node N
pmox image list list VM cloud-image catalog
pmox image list --ct --node N list LXC container templates available on a node
pmox image pull <name|url> --storage S --node N [--as-template] download image (needs --dangerous)
--node is optional for guest commands — pmox finds which node a VMID lives on
via the cluster resources endpoint.
Global flags are position-independent — they work before or after the subcommand:
--json/--no-json, --dangerous, --wait/--no-wait, --timeout <s> (default 600),
--dry-run, --host, --port, --token-id, --token-secret, --verify-ssl/--no-verify-ssl,
--config.
pmox can build cloud-init VMs, containers, and golden templates in a single
command.
# One-call cloud-init VM — downloads the image, creates the VM, injects SSH key + network:
pmox --dangerous vm new web \
--image ubuntu-24.04 \
--size small \
--disk 50 \
--ssh-key ~/.ssh/id_ed25519.pub \
--ip dhcp \
--wait
# Sizing profiles: small = 1 core / 1 GiB · medium = 2 / 4 GiB · large = 4 / 8 GiB--image accepts a catalog name (e.g. ubuntu-24.04), an https:// URL, or a
Proxmox volume ID. Cloud-init options: --ssh-key (repeatable), --ip dhcp|<cidr>,gw=<ip>,
--ciuser, --cipassword, --nameserver.
Requirements: PVE 8.2+ (8.4+ recommended). The target storage must have the
import content type enabled. pmox uses an API token, so it imports by volume ID
(absolute paths would need root@pam).
No-checksum caveat: catalog images are downloaded over HTTPS without checksum
verification (no warning in v1). For integrity, supply --image <url> from a
trusted source or a pre-verified image.
Zero config — the VM gets its address via DHCP:
pmox --dangerous vm up web --image ubuntu-24.04 --wait
pmox routes the image import to a file-based storage, ensures an SSH key
(generating ~/.ssh/id_ed25519.pub if absent), and creates the VM. With DHCP the
address isn't known on return (find it in your router's leases).
For a known static IP, either pass --ip 192.168.0.50/24,gw=192.168.0.1, or
configure a pool once so pmox auto-allocates the lowest free address (scanning
existing static ipconfigN across the cluster — no guest agent required):
[network]
cidr = "192.168.0.0/24"
gateway = "192.168.0.1"
pool = "192.168.0.200-192.168.0.250" # MUST be outside your DHCP scopeWant a known IP on a DHCP VM? Clone a template that already runs
qemu-guest-agent — the agent then reports the address, so no static IP or pool
is needed:
pmox --dangerous vm up web --from-template 9000 --wait
pmox vm ip <vmid> # returns the DHCP-assigned address via the agent
Build that agent template once (pmox stays token-only, so this part is yours):
pmox --dangerous vm up base --image ubuntu-24.04 --ip 192.168.0.250/24,gw=192.168.0.1 --wait
ssh ubuntu@192.168.0.250 "sudo apt-get update && sudo apt-get install -y qemu-guest-agent && sudo systemctl enable --now qemu-guest-agent"
pmox --dangerous --yes vm stop <vmid>
# then convert it to a template: Proxmox UI -> Convert to template, or `qm template <vmid>` on the node
pmox --dangerous vm new web --from-template 9000 --ssh-key ~/.ssh/id_ed25519.pub --ip dhcp --waitClones inherit the template's disk size — pass --disk or resize afterward.
pmox --dangerous ct new box \
--template ubuntu-24.04 \
--ssh-key ~/.ssh/id_ed25519.pub \
--ip dhcp \
--wait--template accepts a catalog/aplinfo name or a vztmpl volume ID. Discover
available templates with pmox image list --ct --node N.
--template-storage (default local, stores the vztmpl) differs from
--storage (default local-lvm, the rootfs). SSH public keys are sent raw — no
manual encoding needed.
pmox --dangerous image pull ubuntu-24.04 --storage local --node pve1 --as-template
# then clone it:
pmox --dangerous vm new web --from-template <id> --ssh-key ~/.ssh/id_ed25519.pub --ip dhcp --wait--as-template conversion is one-way. Same PVE 8.2+/import content-type
requirements apply.
After creating a DHCP guest, read the address it actually got:
pmox vm ip 100 # primary IP + per-interface table
pmox vm ip 100 --all # also show loopback, IPv6 link-local, and MACs
pmox ct ip 200 # same for containersFor VMs this reads the live interfaces from the QEMU guest agent, so the
guest needs qemu-guest-agent installed and running and agent: 1 set —
cloud-init VMs from vm new already enable agent: 1. Containers report their
interfaces directly, so ct ip needs no agent. JSON output (the default when
piped) carries every interface and address; the table view hides loopback and
link-local unless you pass --all.
Point the AI at the CLI and let it run commands via the shell. Because your shell
captures pmox's output, it emits JSON automatically — the model gets
structured output to parse with no flag, while you still see tables at your own
terminal. (Force it either way with --json / --no-json, or globally with
PMOX_JSON=1 / PMOX_JSON=0.)
- Leave dangerous mode off for exploration. The AI literally cannot change
anything without you adding
--dangerous(and--yesfor destructive ops), so accidental damage is impossible during read-only investigation.
pmox health # AI explores freely, read-only (JSON auto)
pmox vm describe 100
pmox image listWhen you want the AI to act, tell it to include the flags explicitly:
pmox --dangerous vm start 100
pmox --dangerous vm snapshot create 100 before-upgradeThis repo is also a Claude Code plugin (in plugin/), so Claude can
drive pmox for you with the safety gates intact:
/plugin marketplace add lukebward/pmox
/plugin install pmox@pmox-marketplace
It adds a proxmox skill (auto-activates when you ask about your cluster) plus
/pmox:cluster-status, /pmox:list-guests, and /pmox:run. Install the CLI
first (pipx install pmox). See plugin/README.md.
The test-suite mocks the Proxmox API, so no live cluster is required.
pip install -e ".[dev]"
pytestTests cover config precedence, the safety gates, output formatting, the API client's endpoint mapping, and the CLI end-to-end with an injected fake client.
pmox/
config.py Settings + precedence merge (file < env < flags)
client.py thin, injectable wrapper over proxmoxer (the only API surface)
catalog.py VM cloud-image catalog and container template discovery
views.py composite read queries (describe, health)
provision.py VM / container creation workflows (cloud-init, clones, ct new)
output.py Rich tables + plain JSON; byte/uptime/percent formatters
safety.py the two gates: require_dangerous() and confirm()
cli.py Typer app wiring config + client + output + safety together
Each module has one job and a clear interface, which is what makes the whole thing straightforward to test with mocks.
MIT © 2026 Luke Ward