A web-based infrastructure monitoring dashboard for VMware vCenter, standalone ESXi hosts, and Proxmox VE clusters. Displays VM count, CPU, memory, and storage utilization across all your hypervisors in one place.
- Multi-platform — monitors VMware vCenter, standalone ESXi, and Proxmox VE (including LXC containers)
- Live overview — single dashboard with gauge charts for CPU, memory, and storage per device, plus a last-hour CPU sparkline on each card
- Drill-down detail — per-device page with utilization history charts and a VM/container list with text search, power-state filter, and sortable columns
- Configurable polling — per-device poll interval (default 5 minutes); first poll runs immediately on device add or edit
- Per-device thresholds — set CPU/memory/storage alert percentages that drive the gauge colour bands
- Users & roles — multiple accounts with admin (full control) or viewer (read-only) roles, managed from a Users page
- Data export — download a device's metric history as CSV, or scrape latest metrics from a Prometheus-format endpoint
- Automatic retention — a daily job prunes metric history older than a configurable window (default 90 days)
- Security hardening — credentials encrypted at rest (Fernet); JWT signing key auto-generated and stored, not hardcoded; bcrypt password hashing; login rate limiting
- System service — runs as a hardened systemd service, starts automatically on boot; schema upgrades apply automatically
| Component | Version |
|---|---|
| Ubuntu | 24.04 LTS or 26.04 LTS |
| Python | 3.12 (installed automatically) |
| Node.js | 18+ (installed automatically) |
| Disk space | ~300 MB (app + venv + node_modules) |
The installer handles all dependencies. No manual setup is needed before running it.
git clone https://github.com/your-org/vmdashboard.git
cd vmdashboard
sudo ./install.shThis installs to /opt/vmdashboard, creates a vmdashboard system user, builds the frontend, and starts the service on port 8080.
To use a different port:
sudo ./install.sh --port 9090The repository ships with a multi-stage Dockerfile and a docker-compose.yml. The image builds the frontend, installs the backend, and serves both from a single container on port 8080. The SQLite database and encryption keys live on a named volume (/data inside the container) so they survive restarts and image rebuilds.
docker compose up -d --buildThen open http://<host>:8080 and complete first-time setup.
docker compose logs -f # follow logs
docker compose down # stop (data volume is kept)
docker compose up -d --build # upgrade to new code (migrations run on startup)Configuration is via environment variables (set them inline or in a .env file next to the compose file):
| Variable | Default | Purpose |
|---|---|---|
PORT |
8080 |
Host port to publish |
DASHBOARD_RETENTION_DAYS |
90 |
Days of metric history to keep (0 = forever) |
DASHBOARD_SECRET_KEY |
auto-generated | Pin the JWT signing key (otherwise generated into the volume on first run) |
PORT=9090 DASHBOARD_RETENTION_DAYS=30 docker compose up -d --builddocker build -t virtualization-dashboard:latest .
docker volume create dashboard-data
docker run -d --name virtualization-dashboard \
-p 8080:8080 \
-v dashboard-data:/data \
--restart unless-stopped \
virtualization-dashboard:latestAll state is in the dashboard-data volume. Back it up with:
docker run --rm -v dashboard-data:/data -v "$PWD":/backup alpine \
tar czf /backup/vmdashboard-backup.tar.gz -C /data .The container runs as an unprivileged user and includes a
/api/healthhealthcheck. The image does not bundle any database or keys — they are created in the volume on first run.
If you prefer to understand each step, here is what the installer does:
sudo apt-get update
sudo apt-get install -y curl software-properties-common rsync
# Python 3.12
sudo add-apt-repository -y ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install -y python3.12 python3.12-venv python3.12-dev
# Node.js 20.x
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt-get install -y nodejssudo useradd --system --no-create-home --shell /bin/false vmdashboardsudo mkdir -p /opt/vmdashboard
sudo rsync -a --exclude='backend/venv' --exclude='frontend/node_modules' \
./ /opt/vmdashboard/cd /opt/vmdashboard/backend
sudo python3.12 -m venv venv
sudo venv/bin/pip install -r requirements.txtcd /opt/vmdashboard/frontend
sudo npm install
sudo npm run build
sudo cp -r dist /opt/vmdashboard/backend/distsudo chown -R vmdashboard:vmdashboard /opt/vmdashboard
sudo chmod 750 /opt/vmdashboard/backendCreate /etc/systemd/system/vmdashboard.service:
[Unit]
Description=VM Dashboard
After=network.target
[Service]
Type=simple
User=vmdashboard
Group=vmdashboard
WorkingDirectory=/opt/vmdashboard/backend
ExecStart=/opt/vmdashboard/backend/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8080
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=vmdashboard
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/vmdashboard/backend
ProtectHome=true
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable vmdashboard
sudo systemctl start vmdashboard- Open your browser and navigate to
http://<server-ip>:8080 - You will be redirected to the login page
- Click the First Time Setup tab
- Enter a username and password for the first admin account and click Create Account & Sign In
- Public registration is permanently closed once the first account is created
Note: The first account is always an admin. Additional accounts (admin or read-only viewer) are created afterwards from the Users page in the sidebar — see Users & Roles below. If you ever lose all admin access, stop the service, delete
/opt/vmdashboard/backend/dashboard.db, restart, and go through first-time setup again (this also erases devices and history).
Once logged in as an admin, open the Users page from the sidebar to manage accounts:
| Role | Can do |
|---|---|
| admin | Everything — add/edit/remove devices, manage users, change settings |
| viewer | Read-only — view the dashboard, device details, and export data, but cannot modify devices or users |
- The first account created during first-time setup is always an admin.
- Admins can add users, reset any user's password, and delete users.
- You cannot delete your own account or remove the last remaining admin (this prevents lock-out).
- Viewers see the dashboard and device pages but the add/edit/delete controls are hidden, and the API rejects write attempts with
403.
Navigate to the Devices page using the sidebar and click Add Device.
| Field | Example |
|---|---|
| Device Type | vCenter |
| Display Name | Production vCenter |
| Hostname / IP | vcenter.example.com |
| Port | 443 |
| Username | administrator@vsphere.local |
| Password | your-password |
| Verify SSL | unchecked for self-signed certs |
vCenter discovery pulls metrics from all ESXi hosts and VMs managed by that vCenter.
| Field | Example |
|---|---|
| Device Type | ESXi |
| Display Name | ESXi-01 |
| Hostname / IP | 192.168.1.10 |
| Port | 443 |
| Username | root |
| Password | your-password |
| Verify SSL | unchecked for self-signed certs |
| Field | Example |
|---|---|
| Device Type | Proxmox |
| Display Name | Home Lab Proxmox |
| Hostname / IP | 192.168.1.20 |
| Port | 8006 |
| Username | root@pam |
| Password | your-password |
| Verify SSL | unchecked for self-signed certs |
Proxmox discovery pulls metrics from all nodes in the cluster, including both QEMU VMs and LXC containers.
The Add/Edit Device dialog has a Monitoring section:
| Field | Meaning |
|---|---|
| Poll interval (min) | How often this device is polled (default 5). Lower it for near-real-time data, raise it for large/slow environments. |
| CPU / Memory / Storage alert % | Optional critical thresholds. When set, the gauge for that metric turns red at/above the value and shades through orange/yellow below it. Leave blank to use the default colour scale. |
After adding a device, the first poll runs immediately. Dashboard cards show No data yet for a few seconds until the first collection completes.
On any device detail page, click Export CSV to download that device's metric history (timestamp, CPU/memory/storage %, capacity figures, VM counts) as a CSV file for reporting or analysis.
The endpoint GET /api/metrics/prometheus returns the latest metric for every device in Prometheus text-exposition format (gauges for CPU/memory/storage %, VM counts, and a vmdashboard_device_up series). It requires authentication, so configure a bearer token in your scrape job:
scrape_configs:
- job_name: vmdashboard
metrics_path: /api/metrics/prometheus
bearer_token: "<a-jwt-from-the-app>"
static_configs:
- targets: ["dashboard-host:8080"]| Task | Command |
|---|---|
| Check status | sudo systemctl status vmdashboard |
| Start | sudo systemctl start vmdashboard |
| Stop | sudo systemctl stop vmdashboard |
| Restart | sudo systemctl restart vmdashboard |
| View logs (live) | sudo journalctl -u vmdashboard -f |
| View last 100 lines | sudo journalctl -u vmdashboard -n 100 |
| Disable autostart | sudo systemctl disable vmdashboard |
Pull the latest code and re-run the installer. The installer preserves your runtime state — the database (dashboard.db), the Fernet encryption key (secret.key), the JWT signing key (jwt_secret.key), any vmdashboard.env overrides, and the backups/ directory are never overwritten.
cd /path/to/vmdashboard
git pull
sudo ./install.sh --port 8080Database schema upgrades are automatic. On startup the app runs backend/migrate.py, which additively adds any new columns to the existing SQLite database (it never drops data) and backfills sensible defaults. The frontend is rebuilt and the service is restarted at the end.
Backing up before a major upgrade is still wise: copy
backend/dashboard.db,backend/secret.key, andbackend/jwt_secret.keysomewhere safe first.
Re-run the installer with the new port — it rewrites the service file and restarts:
sudo ./install.sh --port 9090Run the included uninstall script:
sudo ./uninstall.shIt will ask you to confirm before removing anything, then stops and disables the service, removes the unit file, deletes /opt/vmdashboard (including the database and encryption key), and removes the vmdashboard system user.
System packages installed as dependencies (Python 3.12, Node.js) are intentionally left in place since they may be used by other software. To remove them manually:
sudo apt-get remove --purge python3.12 python3.12-venv python3.12-dev nodejs
sudo add-apt-repository --remove ppa:deadsnakes/ppaIf you want to serve the dashboard on port 80/443 using nginx:
sudo apt-get install -y nginxCreate /etc/nginx/sites-available/vmdashboard:
server {
listen 80;
server_name dashboard.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
}
}sudo ln -s /etc/nginx/sites-available/vmdashboard /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxFor HTTPS, use Certbot with the nginx plugin.
| Path | Purpose |
|---|---|
/opt/vmdashboard/ |
Application root |
/opt/vmdashboard/backend/dashboard.db |
SQLite database (devices, metrics, VMs, users) |
/opt/vmdashboard/backend/dashboard.db-wal, …-shm |
SQLite WAL sidecar files (leave in place) |
/opt/vmdashboard/backend/secret.key |
Fernet encryption key for stored passwords |
/opt/vmdashboard/backend/jwt_secret.key |
Auto-generated key used to sign JWT login tokens |
/opt/vmdashboard/backend/vmdashboard.env |
Optional env-var overrides (not created by default) |
/opt/vmdashboard/backend/dist/ |
Built frontend static files |
/etc/systemd/system/vmdashboard.service |
Systemd service unit |
Back up
dashboard.db,secret.key, andjwt_secret.keytogether.secret.keyis required to decrypt stored device credentials;jwt_secret.keykeeps existing login sessions valid across a restore (losing it just forces everyone to log in again).
The app reads a few optional environment variables. To set them, create /opt/vmdashboard/backend/vmdashboard.env (the systemd unit loads it automatically) and restart the service:
# /opt/vmdashboard/backend/vmdashboard.env
DASHBOARD_SECRET_KEY=<64-hex-char string> # pin the JWT signing key explicitly
DASHBOARD_RETENTION_DAYS=90 # delete metric rows older than this (0 = keep forever)| Variable | Default | Purpose |
|---|---|---|
DASHBOARD_SECRET_KEY |
auto-generated jwt_secret.key |
Overrides the JWT signing key. Useful to share one key across multiple instances. Generate with python3 -c "import secrets; print(secrets.token_hex(32))". |
DASHBOARD_RETENTION_DAYS |
90 |
How long metric history is kept. The daily prune job deletes older rows. Set 0 to disable pruning. |
sudo systemctl restart vmdashboardCheck the device's last error on the Devices page. Common causes:
- Wrong credentials — re-add the device with corrected username/password
- SSL error — uncheck "Verify SSL certificate" for self-signed certs
- Network unreachable — ensure the server running the dashboard can reach the hypervisor on the configured port
- vCenter permission denied — the account needs at least read-only access to the vSphere tree
sudo journalctl -u vmdashboard -n 50 --no-pagerCommon causes:
- Port already in use — change the port with
sudo ./install.sh --port <other> - Permissions error —
sudo chown -R vmdashboard:vmdashboard /opt/vmdashboard
An account already exists. Use the Sign In tab instead. If you need to reset, delete the database:
sudo systemctl stop vmdashboard
sudo rm /opt/vmdashboard/backend/dashboard.db
sudo systemctl start vmdashboardThis removes all devices, users, and metric history. The encryption key (
secret.key) and JWT key (jwt_secret.key) are preserved, so on next start you go through first-time setup again.
Proxmox uses a self-signed certificate by default. Make sure Verify SSL is unchecked when adding the device.
Use the full UPN format for the username: administrator@vsphere.local — not just administrator.
- Credential encryption — device passwords are encrypted with AES-128 Fernet before being stored in SQLite. The key lives in
secret.key. - JWT signing key — tokens are signed with a random 32-byte key generated on first run and stored in
jwt_secret.key(mode600). It is no longer a hardcoded value. To pin it explicitly (e.g. across multiple instances), setDASHBOARD_SECRET_KEY— see Configuration. Changing the key invalidates all existing login sessions. - Password hashing — account passwords are hashed with bcrypt.
- Login rate limiting — the token endpoint is limited to 10 attempts per minute per IP to slow brute-force attempts.
- Role-based access — viewer accounts cannot modify devices or users; all mutating API endpoints require an admin token.
- Service hardening — runs as the unprivileged
vmdashboarduser withNoNewPrivileges,PrivateTmp,ProtectSystem=strict,ProtectHome, andReadWritePathsscoped to the backend directory. - Internet exposure — if exposing beyond your LAN, place it behind nginx with HTTPS (see the reverse proxy section above).