Reference
Environment variables, HTTP API endpoints, Proxmox token setup, Docker deployment, and the usual things to check when something looks off.
Environment variables
ProxKey reads everything from .env. See
.env.example
for the annotated list.
| Variable | Default | Description |
|---|---|---|
PROXMOX_HOST | — | Proxmox server IP or hostname. |
PROXMOX_PORT | 8006 | Proxmox API port. |
PROXMOX_VERIFY_SSL | false | Verify TLS certs. Leave off for self-signed PVE certs. |
PROXMOX_TOKEN_ID | — | API token ID, formatted as user@realm!tokenname. |
PROXMOX_TOKEN_SECRET | — | API token secret. |
PROXMOX_NODE | pve | Default node name to query. |
PROXMOX_SSH_USER | root | SSH user on the PVE node (remote mode only). |
PROXMOX_SSH_PASSWORD | — | Password for the PVE node. Only needed when ProxKey runs off the node and must SSH in. |
PROXMOX_SSH_PORT | 22 | SSH port on the PVE node (remote mode only). |
SECRET_KEY | — | Random string used to sign internal tokens. Change before production. |
CORS_ORIGINS | http://localhost:5173,http://localhost:8080 | Comma-separated list of allowed UI origins. |
Proxmox API token setup
On the Proxmox node, create a user and a token with just enough permission 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 printed secret → PROXMOX_TOKEN_SECRET in .env
Token format on the ProxKey side is the full identifier:
proxkey@pve!proxkey.
HTTP API
Live Swagger at http://<backend>:8000/docs. The
endpoints below are the contract the frontend depends on.
| Endpoint | Method | Description |
|---|---|---|
/api/health | GET | Health check (200 if the process is up). |
/api/hosts | GET | List all LXC + QEMU hosts with status, type, and IP when running. |
/api/hosts/connection | GET | Test the Proxmox API token + connectivity. |
/api/hosts/{vmid}/keys | GET | Inspect deployed keys inside a single host. |
/api/keys | GET | List stored SSH public keys. |
/api/keys | POST | Add an SSH public key. |
/api/keys/{id} | PUT | Update key label or scope. |
/api/keys/{id} | DELETE | Remove a key from the library. |
/api/deploy | POST | Deploy one or more keys to one or more hosts. |
/api/deploy/revoke | POST | Revoke a key from one or more hosts. |
/api/deploy/stats | GET | Dashboard statistics. |
/api/deploy/status | GET | Full deployment record list. |
/api/deploy/audit | GET | Audit log. |
/api/hooks/lxc-started | POST | Hookscript receiver — auto-injects global keys. |
Hookscript for auto-inject
Drop this on the Proxmox node, then attach it to whichever containers should auto-receive global keys.
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
pct set <vmid> --hookscript local:snippets/proxkey-hook.sh
Docker deployment
git clone https://github.com/harshithsunku/proxkey.git
cd proxkey
cp .env.example .env
# edit .env with your Proxmox credentials
docker compose up -d
# UI: http://localhost:8080
# API: http://localhost:8000
For pinned releases, pull pre-built images from GHCR:
docker pull ghcr.io/harshithsunku/proxkey-backend:latest
docker pull ghcr.io/harshithsunku/proxkey-frontend:latest
Both images are multi-arch (linux/amd64 + linux/arm64). The
:latest tag tracks the most recent tagged release;
:main follows the main branch. Pin to a
specific version with :0.1.0 &c.
CI
.github/workflows/build.yml
builds backend + frontend images for linux/amd64 and
linux/arm64 on every push to main and on
pull requests. Tagged pushes (v*) stitch the per-arch
tags into multi-arch manifests and create a GitHub Release with
docker-compose.yml attached.
.github/workflows/pages.yml
publishes this static site from docs/ to GitHub Pages.
Troubleshooting
/api/hosts/connection returns 401
The API token ID or secret is wrong. Recreate the token with pveum user token add ... and double-check the ID is the full user@realm!tokenname form.
pct exec returns "Permission denied"
The API token needs PVEAdmin on /vms, not just PVEAuditor. Re-run pveum aclmod /vms -user ... -role PVEAdmin.
QEMU host shows no IP / qm guest exec fails
The VM needs qemu-guest-agent installed and the agent option enabled in the VM config: qm set <vmid> --agent 1. Then start the agent inside the guest.
Remote mode hangs on sshpass
ProxKey is trying to SSH to the PVE node. Make sure PROXMOX_SSH_PASSWORD is set and the node accepts root password logins, or run ProxKey directly on the Proxmox node and leave PROXMOX_SSH_PASSWORD empty.
LXC IP missing for a running container
The IP-discovery API only works for running containers and only after the network stack has come up. Wait ~5 seconds after start; if it still doesn't appear, check pct exec <vmid> -- ip a on the node.
Same key shows up twice in authorized_keys
Shouldn't happen — the injector grep -qFs first. If it does, the deployed key probably has trailing whitespace or a comment that drifts between deploys. Normalize the key in the UI (strip extra whitespace, fix the comment) and re-deploy.
Hookscript doesn't fire
Check journalctl -u pve-container@<vmid> on the node. The hookscript runs as part of the container start lifecycle; if it's not in /var/lib/vz/snippets/ with the executable bit set, Proxmox silently skips it.
Project layout
proxkey/
├── backend/ # FastAPI backend
│ └── app/
│ ├── main.py
│ ├── config.py
│ ├── database.py
│ ├── models.py
│ ├── routers/ # hosts, keys, deploy, hooks
│ └── services/ # proxmox client + injector
├── frontend/ # React 19 + Vite + Tailwind v4 SPA
│ └── src/
│ ├── App.tsx
│ ├── api/ # client, types, hooks
│ ├── components/ # Dashboard, HostGrid, KeyManager, AuditLog, ...
│ └── store/app.ts
├── docs/ # GitHub Pages docs site (this)
├── docker-compose.yml
├── .github/workflows/
│ ├── build.yml # multi-arch images + GHCR push + release
│ └── pages.yml # GitHub Pages deploy
├── VERSION
├── LICENSE (MIT)
├── README.md
└── CLAUDE.md