DEV Community

Cover image for I built a self-hosted dashboard for the handful of VPSs I run
Sandakelum Priyamantha
Sandakelum Priyamantha

Posted on AI-assisted

I built a self-hosted dashboard for the handful of VPSs I run

The problem

I run a handful of Linux VPSs: a couple of web apps, a database box, a side project or two. Not enough to justify a monitoring stack, but too many to keep SSH-ing into each one to run htop, df -h, or npm run build.

The usual answers felt wrong for that size. Prometheus plus Grafana plus exporters is a project in itself. Hosted monitoring means another account, another bill, and my server data on someone else's machine. Full platforms like Coolify are great, but I didn't want a deploy platform. I wanted to see my servers and run the few commands I always run.

So I built VPS Pilot.

What VPS Pilot is

VPS Pilot is one self-hosted dashboard for the servers you run. The hub is a single Go binary with SQLite and the React dashboard built in. Each server runs a small Rust agent. No separate database server, no queue, no cloud account.

VPS Pilot dashboard showing CPU, memory, disk and network charts

It's free and open source under AGPL-3.0. v0.3.0 just shipped, and it's the release where VPS Pilot goes from watching your servers to doing things on them, safely.

How it works

One hub, one agent per server, nothing in between. Your browser talks to the hub over HTTPS. Each agent dials out to the hub over TLS 1.3, so your servers need no open inbound ports.

# on your hub
$ ./vps_pilot
TCP server (TLS) listening on port 55001

# on each server, with a token from Nodes → Add node
$ VPS_PILOT_TOKEN=vpsp1.… vps-pilot-agent --hub hub.example.com
INFO enrolled; the join token is no longer needed
Enter fullscreen mode Exit fullscreen mode
  1. Start the hub. On first run it creates its SQLite databases and a self-signed certificate for the agent channel.
  2. Add a node. The dashboard gives you a join token that works once, within an hour. It carries the fingerprint of the hub's certificate.
  3. Run the agent. It dials the hub, checks the certificate against that fingerprint, and saves its own key for next time.
  4. Watch. Stats arrive every five seconds and the dashboard charts them.

Because the token pins the certificate, a self-signed cert is fine. There's no CA to set up and no trust-on-first-use gap.

You don't have to run the agent by hand, either. Add node gives you a one-line install command. It downloads the agent release matching your hub, checks it against SHA256SUMS, sets it up as a systemd service, enrolls it, and removes the token from disk. Running it again upgrades the agent. There's also a .deb for amd64 and arm64.

New in v0.3.0: run commands, by name

A dashboard that can run things on your servers is also a big target. If someone takes over the hub, what can they do to every server connected to it?

In VPS Pilot, each project on a server carries a config.vpspilot.json, written by whoever owns that server. It's the complete list of what the dashboard may ever do with that project.

{
  "name": "shop",
  "tech": ["laravel", "mysql"],
  "commands": [
    { "name": "build", "command": "npm run build" },
    { "name": "migrate", "command": "php artisan migrate --force" }
  ],
  "logs": ["storage/logs"]
}
Enter fullscreen mode Exit fullscreen mode

Each declared command now gets a Run button on the project page. Here's what happens when you press it:

  1. The hub sends the agent only the command's name, "build", never a command string.
  2. The agent reads config.vpspilot.json again and looks that name up.
  3. It runs the command written there, in the project directory, as the owner of the config file, not as root.
  4. Output streams live to the dashboard. You can cancel a run, and a timeout kills it if it hangs (600 seconds by default, set with --command-timeout).

So even a fully compromised hub can't run anything that isn't already in a file you wrote on the server. There's no open shell and no way to run arbitrary commands, by design.

The agent also guards the file itself. It refuses to run commands from a config that's a symlink or writable by its group or others. Then nobody but the owner can quietly add a command.

Every run is recorded with who started it, the exact command line, the user it ran as, how it ended, the exit code, the duration and the output. That gives you an audit log for free.

What works today

  • Live metrics: CPU per core, memory, disk and network for every server, charted from the last 5 minutes to the last 7 days, plus whether each agent is online.
  • Threshold alerts: Discord, Slack or email when CPU, memory, disk or network goes over your limit, with a repeat interval you choose.
  • Project discovery: the agent finds every config.vpspilot.json on the server, with per-server scan settings and on-demand rescans.
  • Run commands: run a declared command by name, watch its output live, and keep a history of every run.
  • Safe enrollment: one-time join tokens, a pinned certificate, and a revocable key per server. Re-enroll without losing history.
  • Tiny agent: one Rust binary, installed as a systemd service with one command or a .deb.

What's coming

  • Tail the log folders a project declares
  • Backups of declared folders, the env file and the database
  • Cron jobs declared per project
  • Docker Compose setup and multiple users with roles

What it's not

VPS Pilot watches your servers and does what each project allows. It deliberately doesn't try to be a platform, so it won't deploy from Git, provision servers, orchestrate containers, edit files, or give you a shell.

Those jobs already have good tools: Coolify or Dokploy for Git deploys and containers, Ansible for provisioning, and plain SSH for the rest. Keeping the scope small is what keeps the hub one binary.

The stack, and why

  • Go for the hub. The API, the agent channel and the dashboard ship as one executable. Copy it to a box and run it.
  • SQLite for storage. Two database files in one data directory, using a pure Go driver, so there's no CGO and no database server. Seven days of metrics fits comfortably.
  • Rust for the agent. It runs on every server, so it should be small, fast and boring. It's a single binary with no runtime to install.
  • React for the dashboard. It's built with Vite and embedded into the hub binary at build time.

Try it

If you run a few VPSs and want one place to see them and run your usual commands, give it a spin. The quick start takes a few minutes: start the hub, add a node, run the install command.

I'd love feedback, especially on the names-not-commands model and what you'd want it to do next. Issues and stars on GitHub help a lot. You can also tell me in the comments what your current setup looks like.

Top comments (0)