Reference

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.

VariableDefaultDescription
PROXMOX_HOST—Proxmox server IP or hostname.
PROXMOX_PORT8006Proxmox API port.
PROXMOX_VERIFY_SSLfalseVerify 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_NODEpveDefault node name to query.
PROXMOX_SSH_USERrootSSH 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_PORT22SSH port on the PVE node (remote mode only).
SECRET_KEY—Random string used to sign internal tokens. Change before production.
CORS_ORIGINShttp://localhost:5173,http://localhost:8080Comma-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.

EndpointMethodDescription
/api/healthGETHealth check (200 if the process is up).
/api/hostsGETList all LXC + QEMU hosts with status, type, and IP when running.
/api/hosts/connectionGETTest the Proxmox API token + connectivity.
/api/hosts/{vmid}/keysGETInspect deployed keys inside a single host.
/api/keysGETList stored SSH public keys.
/api/keysPOSTAdd an SSH public key.
/api/keys/{id}PUTUpdate key label or scope.
/api/keys/{id}DELETERemove a key from the library.
/api/deployPOSTDeploy one or more keys to one or more hosts.
/api/deploy/revokePOSTRevoke a key from one or more hosts.
/api/deploy/statsGETDashboard statistics.
/api/deploy/statusGETFull deployment record list.
/api/deploy/auditGETAudit log.
/api/hooks/lxc-startedPOSTHookscript 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