Open source · MIT

SSH keys, on every container,
the moment it boots.

ProxKey distributes SSH public keys across Proxmox LXC containers and QEMU VMs via the Proxmox API — no prior SSH access required. Create a container, click deploy, you're in.

license python proxmox deploy bootstrap
ProxKey hero illustration with key distribution flow
What it does

One tool, every key, every container.

Proxmox API for discovery, pct exec / qm guest exec for injection. Idempotent, auditable, and zero-bootstrap by design.

⚡

Zero-bootstrap injection

Keys go in through Proxmox's pct exec and qm guest exec, not via SSH. Brand-new containers with no SSH access yet are first-class.

🔍

Auto-discovery

Every LXC and QEMU VM on the node appears in the host grid. Running hosts surface their IP addresses automatically via the Proxmox API.

👨‍💻

Idempotent & safe

The inject script grep -qFs before appending. Deploying the same key twice is a no-op. Revoke removes only matching lines.

📱

Live dashboard

Stats cards, host breakdown by type and status, key-coverage donut, and per-deployment metrics — updates as you deploy.

🛡

Hookscript receiver

A Proxmox hookscript that POSTs to /api/hooks/lxc-started auto-injects every global key the moment a container boots.

🧮

Per-host inspect

See exactly which keys live on any host — pulled live from the guest's authorized_keys via the same exec channel.

⚙

Search, filter, sort

Find hosts by name, VMID, or IP. Filter by status or type. Sort by any column. One-click ssh root@<ip> copy from the card.

💾

Audit log

Every deploy, revoke, and key CRUD event lands in the audit table with timestamp, host, key, and outcome.

🔅

Dual execution paths

Runs on the PVE node? pct exec direct. Runs anywhere else? SSH to the node first, then pct exec. Same wire, one extra hop.

Quick start

Up and running in under a minute.

One .env file, one docker compose up. Or build from source if you want to hack on it.

1

Clone & configure

Copy the example env file and fill in your Proxmox host and API token.

git clone https://github.com/harshithsunku/proxkey.git
cd proxkey
cp .env.example .env
# edit .env: PROXMOX_HOST, PROXMOX_TOKEN_ID, PROXMOX_TOKEN_SECRET, PROXMOX_NODE
2

Create an API token on Proxmox

On the PVE node, give ProxKey just enough to discover hosts and exec inside them.

pveum user add proxkey@pve
pveum aclmod /        -user proxkey@pve -role PVEAuditor
pveum aclmod /vms     -user proxkey@pve -role PVEAdmin
pveum user token add proxkey@pve proxkey --privsep 0
# copy the secret → PROXMOX_TOKEN_SECRET in .env
3

Boot the stack

Backend + nginx-fronted frontend come up together. Open http://localhost:8080.

docker compose up -d
# UI:  http://localhost:8080
# API: http://localhost:8000  (Swagger at /docs)
1

Drop a hookscript on the Proxmox node

One-liner that POSTs the VMID to ProxKey whenever a container moves into the running state.

cat > /var/lib/vz/snippets/proxkey-hook.sh <<'EOF'
#!/bin/bash
VMID="$1"
PHASE="$2"
[ "$PHASE" = "post-start" ] || exit 0
curl -fsS -X POST "http://proxkey-host:8000/api/hooks/lxc-started" \
     -H "Content-Type: application/json" \
     -d "{\"vmid\": ${VMID}}" || true
EOF
chmod +x /var/lib/vz/snippets/proxkey-hook.sh
2

Attach the hookscript to your containers

Per-container, or as part of your provisioning template.

pct set <vmid> --hookscript local:snippets/proxkey-hook.sh
3

Mark a key as "global" in the UI

Any container that boots with the hookscript attached now gets every global key written before the first login window opens.

1

Run the backend

cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp ../.env.example .env
uvicorn app.main:app --reload  # http://localhost:8000
2

Run the frontend

Vite proxies /api to the backend automatically.

cd frontend
npm install
npm run dev  # http://localhost:5173
3

Iterate

Backend autoreloads on save. The Vite dev server hot-reloads the UI. /api/docs on the backend has live Swagger.

Prerequisites. Proxmox VE 7+ or 8+; an API token with PVEAdmin on /vms; QEMU VMs need qemu-guest-agent running for IP discovery and qm guest exec. If ProxKey runs off the PVE node, set PROXMOX_SSH_PASSWORD so it can SSH in to call pct exec.
Pipeline

How a key travels.

The browser hits the FastAPI backend. The backend talks to the Proxmox API via proxmoxer to discover hosts and call pct exec / qm guest exec. The injection script is base64-encoded before being piped through the exec channel so shell quoting can't mangle it. The script is idempotent: grep -qF before echo >>.

Every deploy and revoke is recorded in SQLite — deployment state, audit log, and the key library all live in a single mounted volume.

Read the architecture →
ProxKey architecture diagram
Design rules

Built to a short list.

Zero-bootstrap, always

If a feature needs prior SSH access to the guest, it doesn't belong here. The Proxmox API is the only injection channel.

Idempotent everything

Deploy twice, revoke twice, re-fire a hookscript — none of it should corrupt state or produce duplicates.

Defensive shell

Anything crossing SSH or pct exec is base64-framed. Never concatenate user input into a shell command.

Generic & open

MIT-licensed. No proprietary names, IPs, credentials, or company-specific anything in code, docs, or history.