← Back
sebastianflint

sebastianflint/pve-panel

View on GitHub ↗
Stars
35
Forks
6
Watchers
35
Open issues
7
Contributors
2
Language
JavaScript
License
—
Default branch
main
Created Sep 27, 2026Updated Sep 28, 2026

Star growth

Today—
This week—
This month—

Star history will appear here once this repo has been tracked for a couple of days.

README

PVE Panel

A modern self-service portal for Proxmox VE

Give users a clean, secure interface to manage only their own virtual machines and containers — without exposing the Proxmox VE interface or API credentials.

Proxmox VE Node.js Docker OIDC GHCR Release AI-assisted

Features · Screenshots · Architecture · Quick Start · Networking · Authentication · Deployment · Versions · Security · AI use · Full guide


Overview

PVE Panel turns a Proxmox VE environment into a lightweight self-service platform.

Users receive a dedicated portal where they can manage assigned servers, open consoles, view usage data, work with snapshots, and — when enabled — provision new systems from administrator-approved templates.

Administrators retain control over templates, quotas, ownership, networking, authentication, and lifecycle operations.

The browser never communicates directly with Proxmox VE and never receives the Proxmox API token.

┌──────────────────────┐
│      Customers       │
└──────────┬───────────┘
           │ HTTPS :3000
           ▼
┌──────────────────────────────────────────────┐
│                  PVE Panel                   │
│                                              │
│  Customer Portal        Admin Interface      │
│  :3000                  :3001                │
│                         localhost by default │
│                                              │
│  Fastify · SQLite · Jobs · Auth · Audit      │
└──────────────────────┬───────────────────────┘
                       │ Proxmox API Token
                       ▼
              ┌──────────────────┐
              │   Proxmox VE     │
              │ VM · LXC · SDN   │
              └──────────────────┘

The customer and administration portals run as separate web servers in the same process. They use separate session cookies and signing keys, and the customer-facing server does not expose administrator routes.

📘 This README is the overview. Every setup step, permission, firewall rule and troubleshooting tip is in the administrator guide.


🤖 AI-assisted development

PVE Panel was built with substantial help from an AI assistant (Claude by Anthropic). To be transparent about what that means:

Created with AI assistance

  • most of the source code (backend, customer and admin portals, scripts)
  • the Docker, Compose and GitHub Actions setup
  • the documentation, including this README's content and the administrator guide
  • automated tests during development, largely against simulated Proxmox, guest-agent, SMTP and WireGuard environments and a certified test OpenID provider
  • the screenshots, taken from such a test environment with sample data

Done by the maintainer

  • defining the requirements and deciding on features and design
  • running and testing the panel against a real Proxmox VE environment and reporting problems, which were then fixed
  • publishing, versioning and operating the project

What this means for you

  • Not every feature has been tested in every real-world combination — especially provider-specific setups (OIDC providers, mail servers, NAS models, Windows editions).
  • AI-generated code can contain mistakes, including security-relevant ones. Review the code and the security model before using PVE Panel in production, and validate customer isolation yourself (see Recommended validation).
  • Issues and pull requests are reviewed and handled by the maintainer.

✨ Features

Customer self-service

  • Start, shut down, restart and force-stop assigned systems
  • Manage both QEMU VMs and LXC containers
  • Live network map of the customer's private network, servers and VPN
  • View live status and guest information, with Windows / Linux recognition
  • Display CPU, memory, disk and network usage graphs
  • Create, restore and delete snapshots (optionally including RAM)
  • Launch an integrated browser console
  • Create new servers from administrator-approved templates
  • Reinstall self-created servers from a template (same name, size and network address)
  • Resize self-created servers within their plan (CPU, memory, disk)
  • Delete self-created servers
  • View provisioning progress and actionable failure messages
  • Manage personal VPN devices
  • Connect servers to a personal Tailscale network
  • Manage their own two-factor authentication
  • See which panel version they're using

Administrator experience

  • Create and manage customer accounts
  • Assign existing Proxmox VMs or containers to users
  • Configure provisioning quotas and limits
  • Publish selected templates
  • Start, stop, restart and delete customer-owned servers
  • Review provisioning jobs and failures
  • Reset passwords and 2FA, require 2FA per user, link/unlink single sign-on
  • Review the activity/audit log
  • Monitor WireGuard and Tailscale usage
  • Delete customers together with their owned infrastructure
  • See the running version and whether an update is available
  • Configure email (SMTP) in the admin portal and invite users by email

Provisioning

PVE Panel supports automated provisioning for both Linux and Windows workloads.

Linux

  • Cloud-init
  • Hostname
  • User credentials
  • SSH keys
  • CPU and memory sizing
  • Disk expansion
  • DHCP networking
  • QEMU Guest Agent integration

Windows

  • Sysprep-based templates
  • Automated OOBE handling
  • Administrator password replacement (verified inside Windows)
  • Computer rename
  • QEMU Guest Agent configuration
  • DHCP cleanup and validation
  • Windows setup-state detection
  • Automatic reboot and readiness checks

🖼️ Screenshots

Customer dashboard Server details
Customer dashboard Server details
Admin portal Provisioning
Admin portal Provisioning
Sign-in Version & updates
Sign-in About

🏗️ Architecture

PVE Panel deliberately separates the customer-facing application from Proxmox VE.

Customer Browser
      │
      │ HTTPS
      ▼
┌───────────────┐
│ Customer UI   │
│ Port 3000     │
└───────┬───────┘
        │
        ▼
┌───────────────────────────────┐
│           PVE Panel           │
│                               │
│ Authentication                │
│ Ownership checks              │
│ Provisioning jobs             │
│ Snapshot / power operations   │
│ Console proxy                 │
│ Audit logging                 │
│ SDN / VPN management          │
└───────────────┬───────────────┘
                │
                │ API token
                ▼
        ┌───────────────┐
        │ Proxmox VE    │
        └───────────────┘

Every server-specific request is checked against the internal ownership database before PVE Panel contacts Proxmox.

A resource that does not belong to the signed-in customer is returned as 404, just like a resource that does not exist.


🚀 Quick Start

Requirements

  • Proxmox VE 8 or 9
  • Node.js 22.13+ for a native installation
  • or Docker / Docker Compose (also Portainer or a NAS Docker app)
  • A dedicated Proxmox API user/token
  • Optional: Proxmox SDN for isolated customer networks
  • Optional: QEMU Guest Agent for richer VM integration

1. Prepare Proxmox VE

Create a dedicated service account and a restricted role:

pveum user add panel@pve --comment "PVE Panel"

# Proxmox VE 9
pveum role add PanelCustomer --privs \
"VM.Audit VM.PowerMgmt VM.Console VM.Snapshot VM.Snapshot.Rollback VM.GuestAgent.Audit"

# On PVE 8 use VM.Monitor instead of VM.GuestAgent.Audit.

pveum pool add customers
pveum acl modify /pool/customers --users panel@pve --roles PanelCustomer

# Token inherits the user's permissions.
pveum user token add panel@pve panel --privsep 0

Store the generated token secret securely.

Add customer-managed systems to the dedicated pool:

pveum pool modify customers --vms 101,102

Systems outside the configured customer pool remain outside the panel's effective permission scope.

Additional permissions for self-service provisioning

Only add these permissions when customers should be allowed to create servers:

pveum role modify PanelCustomer --append 1 \
  --privs "VM.Allocate VM.Clone VM.Config.CPU VM.Config.Memory VM.Config.Disk VM.Config.Cloudinit VM.Config.Options VM.Config.Network Datastore.AllocateSpace Datastore.Audit"

pveum role modify PanelCustomer --append 1 \
  --privs "VM.GuestAgent.Unrestricted"

pveum pool add templates
pveum pool modify templates --vms 9000,9001
pveum acl modify /pool/templates --users panel@pve --roles PanelCustomer

pveum acl modify /storage/VMStorage \
  --users panel@pve --roles PanelCustomer

Newly provisioned servers are automatically added to the pool defined by PVE_POOL.


2. Configure PVE Panel

git clone https://github.com/sebastianflint/pve-panel.git
cd pve-panel

npm install
cp example.env .env

Configure at minimum:

PVE_URL=https://pve.example.com:8006
PVE_TOKEN_ID=panel@pve!panel
PVE_TOKEN_SECRET=your-token-secret
JWT_SECRET=replace-with-a-long-random-secret

Create the first administrator:

npm run user:create -- admin@example.com 'a-long-password' --admin

Start the application:

npm start

Development mode:

npm run dev

Default endpoints:

Interface Address Exposure
Customer portal http://host:3000 Customer-facing
Admin portal http://127.0.0.1:3001 Localhost only by default

For local HTTP testing, set:

COOKIE_SECURE=false

⚠️ Set COOKIE_SECURE=true again for production and serve the customer portal over HTTPS.


🐳 Deployment

Docker Compose

cp example.env .env
docker compose up -d --build

Create the first administrator inside the container:

docker compose exec panel \
  npm run user:create -- admin@example.com 'a-long-password' --admin

Or, without a terminal, let the panel create it on first start:

INITIAL_ADMIN_EMAIL=admin@example.com
INITIAL_ADMIN_PASSWORD=replace-me

Remove INITIAL_ADMIN_PASSWORD after the initial account has been created.

Prebuilt image

Published container image:

ghcr.io/sebastianflint/pve-panel

The GitHub workflow builds amd64 and arm64 images from main and release tags, with an SBOM and signed build provenance.

For a server or NAS deployment, use one of:

File For
deploy/docker-compose.yml Docker Compose, UGREEN Docker app (reads .env)
deploy/docker-compose.portainer.yml Portainer stacks (reads the stack's variables)

The image defaults to ghcr.io/sebastianflint/pve-panel:latest. To pin a release:

PANEL_IMAGE=ghcr.io/sebastianflint/pve-panel:1.1

Then deploy:

docker compose up -d

Portainer

Paste deploy/docker-compose.portainer.yml into Stacks → Add stack → Web editor and enter the settings under Environment variables (or load your .env).

Portainer saves stack variables to stack.env. The Portainer file passes them into the container with env_file: stack.env; with env_file: .env the container would start without settings.

HTTPS with Caddy

The repository includes a Caddyfile.

Set:

PANEL_DOMAIN=panel.example.com
PANEL_PUBLIC_URL=https://panel.example.com
COOKIE_SECURE=true

Then start the HTTPS profile:

docker compose --profile https up -d --build

Caddy handles certificate issuance and WebSocket proxying for the browser console.

UGREEN NAS / UGOS Pro

PVE Panel can also run as a Docker project on compatible UGREEN NAS systems.

A typical deployment directory is:

/volume1/docker/pve-panel

For a prebuilt-image deployment, place the deployment docker-compose.yml and .env in the directory and create a Docker Project in UGOS (Docker → Project → Create).

Add the NAS's IP to the Proxmox management IPSet so the panel can reach the Proxmox API.


📦 Versions & updates

The running version is stamped into the image by the release workflow — nothing to edit by hand.

  • Admin portal: version badge in the top bar (a dot = update available) and an About tab with version, commit, build date, uptime and update instructions
  • Customer portal: the version number in the sidebar and on the Account page (SHOW_VERSION_TO_CUSTOMERS=false hides it)
  • Update check: asks GitHub for the latest release, at most every 6 hours (UPDATE_CHECK=false turns it off)

Publishing a release

npm version patch      # 1.1.0 -> 1.1.1  (fixes)
npm version minor      # 1.1.1 -> 1.2.0  (new features)
npm version major      # 1.2.0 -> 2.0.0  (breaking changes)
git push --follow-tags

npm version raises the version in package.json, commits it and creates the tag. The workflow then:

  1. builds the image as 1.2.0, 1.2, 1 and moves latest to it
  2. creates a GitHub Release with generated release notes
  3. refuses to build if the tag and package.json disagree

Pushes to main without a tag build development versions such as 1.2.0-dev.a1b2c3d and publish them as edge — they never replace latest.

Image tag Contains Use it for
latest the newest release normal deployments
1.4 / 1 newest release of that line updates within a version line only
1.4.0 exactly that release fully pinned deployments
edge the newest development build from main testing only

Updating a deployment

Deployment Update
Portainer Stacks → your stack → Update the stack with Re-pull image and redeploy
UGREEN Docker app Project → your project → redeploy
Command line docker compose pull && docker compose up -d

Data stays in the volume; database changes are applied automatically at start.


👤 Server ownership

PVE Panel does not rely only on Proxmox permissions to determine which systems a customer can see.

Each assigned resource is tracked in the internal ownership database.

Before any per-server operation is sent to Proxmox, PVE Panel validates ownership.

Request
  │
  ▼
Authenticated customer
  │
  ▼
Ownership lookup
  │
  ├── not owner ──► 404
  │
  └── owner
        │
        ▼
   Proxmox API

This applies to:

  • status
  • power operations
  • snapshots
  • usage data
  • consoles
  • deletion
  • remote-access operations

🌐 Customer network isolation

PVE Panel can automatically create one private Proxmox SDN network for each customer.

Enable it with:

CUSTOMER_NETWORKS=true

Example:

Customer 1
   │
   └── cu0001 ── 10.100.1.0/24 ──┐
                                   │
Customer 2                         ├── NAT ──► Internet
   │                               │
   └── cu0002 ── 10.100.2.0/24 ──┘

Each network receives:

  • its own SDN VNet, with the customer's name as alias (e.g. lena (example.com))
  • dedicated /24 subnet
  • gateway
  • DHCP
  • NAT
  • Proxmox firewall rules
  • IP filtering
  • MAC filtering
  • inter-customer isolation

By default, customer workloads can access the public internet while private/internal networks are blocked.

Typical blocked ranges include:

10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
100.64.0.0/10
169.254.0.0/16

This prevents customer workloads from using the Proxmox host as a route into internal networks.

Required SDN permissions

pveum role add PanelNetwork --privs "SDN.Allocate SDN.Audit SDN.Use"
pveum acl modify /sdn --users panel@pve --roles PanelNetwork

Install dnsmasq on the Proxmox host:

apt install dnsmasq
systemctl disable --now dnsmasq

Host firewall

Set these up before enabling the datacenter firewall:

# Keep the Proxmox UI/API reachable for your admin network and the panel host
pvesh create /cluster/firewall/ipset --name management
pvesh create /cluster/firewall/ipset/management --cidr 192.168.50.0/24

# DHCP for customer networks — no source filter (requests come from 0.0.0.0)
pvesh create /cluster/firewall/rules --type in --action ACCEPT --proto udp --dport 67 --enable 1

# Ping to the host (blocked by default)
pvesh create /cluster/firewall/rules --type in --action ACCEPT --macro Ping --enable 1

The panel creates and maintains the necessary customer SDN objects when provisioning is enabled.

Important

Customer isolation depends on the Proxmox datacenter firewall being enabled. Validate management access before enabling it remotely. Locked out? From the console: pvesh set /cluster/firewall/options --enable 0.


🔐 Authentication

Local authentication

PVE Panel supports traditional username/password authentication with secure server-side controls and rate limiting. Password sign-in can be switched off per portal once SSO works.

Two-factor authentication

TOTP-based two-factor authentication is built in.

Compatible apps include:

  • 1Password
  • Microsoft Authenticator
  • Google Authenticator
  • Authy
  • other RFC 6238-compatible authenticators

Features include:

  • QR-code enrollment
  • recovery codes
  • forced 2FA per user
  • administrator reset
  • one-time use protection
  • encrypted TOTP secrets

Locked out yourself? npm run user:reset-2fa -- admin@example.com

OpenID Connect / SSO

PVE Panel supports OpenID Connect for both the customer and administrator portals.

Designed for standards-compliant providers such as:

  • Microsoft Entra ID
  • Keycloak
  • Authentik
  • Google Workspace
  • Okta

The implementation is tested against a certified OpenID provider and uses:

  • Authorization Code Flow
  • PKCE (S256)
  • state
  • nonce
  • discovery metadata
  • ID-token validation (via the OpenID-certified openid-client)
  • Pushed Authorization Requests (PAR) when the provider supports them
  • optional verified-domain restrictions
  • issuer + sub account binding

Example configuration:

OIDC_ENABLED=true
OIDC_ISSUER=https://id.example.com/realms/example
OIDC_CLIENT_ID=pve-panel
OIDC_CLIENT_SECRET=replace-me

PANEL_PUBLIC_URL=https://panel.example.com
ADMIN_PUBLIC_URL=http://localhost:3001

Callback URLs:

https://panel.example.com/api/auth/oidc/callback
http://localhost:3001/api/auth/oidc/callback

Only the panel needs to reach the provider (outbound HTTPS); the provider never connects to the panel.

Microsoft Entra ID doesn't send email_verified: set OIDC_REQUIRE_VERIFIED_EMAIL=false together with OIDC_ALLOWED_DOMAINS. Provider notes are in the guide.

Once SSO is validated, local password login can optionally be disabled independently for each portal.


✉️ Email & invitations

Email is configured in the admin portal → Settings, not in .env:

  • SMTP server, port and encryption (TLS, STARTTLS or none for an internal relay)
  • username and password — the password is stored encrypted and never shown again
  • sender name and address
  • the panel address used in links
  • a Send test email button to verify everything end to end

Once email works, Add a customer offers Send an invitation email. Instead of a password chosen by the admin, the user receives:

  • the panel address and their sign-in email
  • a personal link to set their own password
  • a note when 2FA is required or single sign-on is used
Admin adds user ──► Invitation email ──► User opens link ──► Sets password ──► Signs in
                                          (single-use, 3 days)

Invitation links:

  • work once and expire after 3 days
  • are stored only as a SHA-256 hash
  • carry the token after #, so it never reaches server logs or Referer headers
  • are replaced by Resend invitation (the old link stops working)

The customer list shows Invitation pending or Invitation expired until the user has set a password.

Changing JWT_SECRET makes the stored SMTP password unreadable — enter it again in Settings afterwards.


🪟 Windows templates

Windows systems can be deployed without cloud-init.

The repository includes:

docs/windows/unattend.xml

Recommended template workflow:

  1. Install Windows.
  2. Install VirtIO drivers.
  3. Install the QEMU Guest Agent.
  4. Enable the QEMU Guest Agent in Proxmox.
  5. Configure the desired base applications and settings.
  6. Ensure the network adapter uses DHCP.
  7. Copy and customize docs/windows/unattend.xml.
  8. Run Sysprep.
  9. Shut down the VM.
  10. Convert it to a Proxmox template without booting it again.

Example:

copy unattend.xml C:\Windows\System32\Sysprep\unattend.xml

C:\Windows\System32\Sysprep\sysprep.exe ^
  /generalize ^
  /oobe ^
  /shutdown ^
  /unattend:C:\Windows\System32\Sysprep\unattend.xml

During provisioning PVE Panel can:

  • wait for Windows setup to complete
  • set the final Administrator password
  • verify the password
  • rename the computer
  • force DHCP on network adapters
  • validate that an address was received
  • reboot the VM
  • report provisioning failures to the customer

🐧 Linux templates

Linux provisioning uses cloud-init.

A template should contain:

  • a cloud-init-compatible image
  • a cloud-init drive
  • DHCP networking
  • optionally qemu-guest-agent

PVE Panel applies:

  • hostname
  • username/password
  • SSH keys
  • CPU
  • memory
  • network configuration
  • disk expansion

🔌 Remote access

PVE Panel supports multiple ways to reach customer systems without exposing Proxmox itself.

Browser console

Customers can launch one-time console sessions directly from the panel.

Console sessions are:

  • bound to the requesting user
  • single-use
  • short-lived
  • proxied through PVE Panel

WireGuard VPN

An optional central WireGuard gateway can provide remote access into each customer's private network.

Laptop / Phone
      │
      │ WireGuard
      ▼
Public IP : UDP 51820
      │
      ▼
┌──────────────────┐
│ WireGuard Gateway│
└─────────┬────────┘
          │
          ├── Customer 1 VPN clients → Customer 1 VNet only
          └── Customer 2 VPN clients → Customer 2 VNet only

Customers can:

  • add VPN devices
  • download configuration files
  • scan QR codes
  • see connection state
  • remove devices

Private keys are shown once and are not retained by the panel.

The gateway is prepared once with docs/vpn/setup-gateway.sh — see the guide for the routing and port-forward steps.

Tailscale

When enabled, customers can connect individual servers to their own Tailscale account.

Supported modes:

  • Single server — expose only that server through the customer's tailnet
  • Private-network gateway — advertise the customer's private subnet through a Linux VM

PVE Panel installs and configures Tailscale through the QEMU Guest Agent.

The customer's Tailscale account itself is never managed by the panel.


🛡️ Security model

PVE Panel was designed so that giving customers self-service access does not mean giving them Proxmox access.

Key controls include:

  • Dedicated Proxmox service account
  • Restrictive custom Proxmox roles
  • Resource pool scoping
  • Application-level ownership validation
  • Separate customer and admin HTTP servers
  • Separate customer/admin sessions
  • Admin interface bound to localhost by default
  • httpOnly session cookies
  • SameSite=Strict
  • Rate limiting
  • Short-lived, user-bound console tickets
  • User-bound task tracking
  • Audit logging
  • Optional TOTP 2FA
  • OIDC with PKCE and token validation
  • Customer-specific SDN isolation
  • VM firewall enforcement (incoming and outgoing)
  • IP and MAC filtering
  • Restricted VPN routing
  • Proxmox API token kept server-side
  • No secrets in the container image

TLS to Proxmox

For production deployments, use a trusted Proxmox certificate or provide the Proxmox CA:

PVE_CA_FILE=/app/certs/pve-root-ca.pem

Avoid:

PVE_VERIFY_TLS=false

outside development environments.


🔒 Admin interface

The administrator portal listens on localhost by default:

127.0.0.1:3001

Access it securely through SSH:

ssh -L 3001:127.0.0.1:3001 you@panel-host

Then browse to:

http://localhost:3001

If the admin interface is exposed on another interface, protect it with network-level access controls.


🔁 Reverse proxy

The customer portal can be placed behind an existing reverse proxy.

WebSocket upgrades are required for the integrated console.

Example NGINX configuration:

server {
    listen 443 ssl http2;
    server_name panel.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;

        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 1h;
    }
}

📦 Backups

The application database lives in the persistent Docker volume.

Create a consistent backup:

docker compose exec panel npm run db:backup

Copy backups from the container:

docker compose cp panel:/app/data/backups ./backups

🧰 Useful CLI commands

Create an administrator:

npm run user:create -- admin@example.com 'a-long-password' --admin

Create a customer (optionally requiring 2FA):

npm run user:create -- customer@example.com 'another-long-password' --require-2fa

Assign an existing VM or container:

npm run vm:assign -- 101 customer@example.com "Web server"

Reset 2FA:

npm run user:reset-2fa -- admin@example.com

Backup the database:

npm run db:backup

🔧 Branding

Change the product name through .env:

PANEL_NAME=PVE Panel

The configured name is used on:

  • sign-in pages
  • navigation
  • browser titles
  • customer portal
  • administrator portal
  • the authenticator app entry for 2FA

Own OS icons

Windows and Linux servers are shown with neutral built-in glyphs. To use your own icons, place any of these files in the branding folder:

os-windows.svg   os-linux.svg     (or .png / .webp)

The folder is BRANDING_DIR (default data/branding/ next to the database). With Docker, mount it:

volumes:
  - ./branding:/app/branding:ro
BRANDING_DIR=/app/branding

Only these file names are served, with a strict sandbox policy. Make sure you're allowed to use the images you add.


📂 Project structure

.
├── src/
│   ├── server.js
│   ├── config.js
│   ├── db.js
│   ├── pve.js
│   ├── auth.js
│   ├── bootstrap.js
│   ├── provision.js
│   ├── network.js
│   ├── windows.js
│   ├── cleanup.js
│   ├── tailscale.js
│   ├── totp.js
│   ├── oidc.js
│   ├── vpn.js
│   ├── agent.js
│   ├── version.js
│   ├── mail.js
│   ├── invite.js
│   ├── secrets.js
│   └── routes/
│       ├── vms.js
│       ├── console.js
│       ├── vpn.js
│       └── admin.js
│
├── web/
│   ├── customer/
│   ├── admin/
│   └── shared/
│
├── docs/
│   ├── GUIDE.md
│   ├── screenshots/
│   ├── windows/
│   │   └── unattend.xml
│   └── vpn/
│       └── setup-gateway.sh
│
├── scripts/
├── deploy/
│   ├── docker-compose.yml
│   └── docker-compose.portainer.yml
│
├── .github/
│   ├── workflows/docker-publish.yml
│   ├── dependabot.yml
│   └── release.yml
├── Dockerfile
├── docker-compose.yml
├── Caddyfile
├── example.env
└── package.json

🔌 API overview

Customer API

Method Endpoint Purpose
GET /api/vms List owned servers
GET /api/vms/:vmid Server details and live status
POST /api/vms/:vmid/power/:action Power operations
GET /api/vms/:vmid/rrd Usage metrics
GET/POST /api/vms/:vmid/snapshots List or create snapshots
POST /api/vms/:vmid/snapshots/:name/rollback Roll back snapshot
DELETE /api/vms/:vmid/snapshots/:name Delete snapshot
POST /api/vms/:vmid/console Create console session
GET (WS) /api/console/:session Console WebSocket
GET /api/account Limits, usage, network, panel version
GET /api/templates Available templates
POST /api/vms Provision a server
POST /api/vms/:vmid/reinstall Reinstall a self-created server from a template
POST /api/vms/:vmid/resize Change CPU cores, memory and disk within the plan
DELETE /api/vms/:vmid Delete a self-created server
GET /api/vpn VPN state
POST /api/vpn/devices Add VPN device
DELETE /api/vpn/devices/:id Remove VPN device
GET/POST/DELETE /api/vms/:vmid/tailscale Manage Tailscale
GET/POST /api/account/2fa/... Manage 2FA
POST /api/invite/check, /api/invite/accept Invitation link: check it, set the password

The administrator API is available only through the administrator server and an authenticated admin session. GET /healthz reports liveness for Docker and monitoring.


⚙️ How provisioning works

1. Customer selects template and sizing
                 │
                 ▼
2. Quotas and permissions are validated
                 │
                 ▼
3. A free VMID is reserved
                 │
                 ▼
4. Full clone is created
                 │
                 ▼
5. CPU / RAM / disk / network are configured
                 │
                 ▼
6. Guest-specific setup runs
      ┌──────────┴──────────┐
      │                     │
      ▼                     ▼
 Linux / cloud-init     Windows / QGA
      │                     │
      └──────────┬──────────┘
                 ▼
7. Readiness is verified
                 │
                 ▼
8. Server becomes available to the customer

Only one provisioning job per customer can run at a time.

Reinstall

Customers can start a self-created server fresh from any offered template that fits its disk:

  • everything on the disk is erased, including all snapshots (and a Tailscale connection)
  • name, VM ID, size and MAC address stay — so DHCP usually hands out the same IP
  • the server stays in the customer's private network; isolation rules are re-applied
  • new sign-in details are chosen like at creation; Windows ⇄ Linux switches are possible
  • confirmed by typing the server name; a failed reinstall can simply be retried

Servers assigned by an administrator cannot be reinstalled by customers.

Resize

Customers can change the size of self-created servers within their plan:

Resource How it changes
CPU cores, memory Saved immediately; a running server uses them after a restart from the panel (optional right away)
Disk Grow only, applied immediately — Linux cloud images expand at the next boot, Windows drive C: is extended by the panel
  • the plan check counts pending changes too, so resizing several running servers can't exceed the plan
  • the server page shows Size change waiting for a restart until the change is active
  • Windows needs at least 2 GB of memory; disks can never shrink

If provisioning fails, the customer receives a visible failure state instead of a permanently spinning setup process.


🧪 Recommended validation

Before giving users access, validate the deployment with at least two test customers.

Confirm that:

  • Customer A cannot see Customer B's servers
  • Customer A cannot access Customer B's network
  • Customer workloads cannot access internal management networks
  • Public internet access works where intended
  • Browser consoles are customer-bound
  • VPN devices can reach only their assigned customer network
  • Admin port 3001 is not publicly reachable
  • OIDC and 2FA behave as expected
  • Proxmox API permissions are limited to the intended resources

🗺️ Roadmap

Potential future improvements include:

  • Cross-node placement
  • Integrated backup management
  • Customer-managed firewall rules
  • Additional lifecycle automation
  • PostgreSQL support for multi-instance deployments
  • More quota and billing-oriented functionality

Contributions and ideas are welcome through GitHub Issues and Pull Requests.


🤝 Contributing

Contributions are welcome.

A typical workflow:

git clone https://github.com/sebastianflint/pve-panel.git
cd pve-panel
git checkout -b feature/my-feature

After making and testing your changes:

git add .
git commit -m "Add my feature"
git push origin feature/my-feature

Then open a Pull Request.

For larger changes, consider opening an Issue first so the design can be discussed before implementation.


🐛 Issues & feature requests

Found a bug or have an idea?

Use the repository issue tracker:

https://github.com/sebastianflint/pve-panel/issues

When reporting a problem, useful information includes:

  • Proxmox VE version
  • deployment method
  • PVE Panel version / commit (admin portal → About)
  • browser
  • relevant container/application logs
  • the reason shown in the Activity tab, if any
  • exact error message
  • steps to reproduce

Never include API tokens, passwords, OIDC secrets or other credentials in an issue.


⚠️ Project status

PVE Panel directly controls virtualization, networking, guest provisioning and remote-access functionality.

Large parts of the project were written with AI assistance (see AI-assisted development).

Before using it in a production or internet-facing environment:

  • review the code
  • review Proxmox permissions
  • test customer isolation
  • use TLS
  • protect the admin interface
  • back up the database
  • validate firewall rules
  • keep Proxmox, Node.js and dependencies up to date

Built for self-service Proxmox environments

Give customers the controls they need — while keeping the hypervisor, credentials and other customers out of reach.

⭐ Star the project · 🐛 Report an issue · 📦 Container image · 📘 Administrator guide