· 4 min read
RivetPanel: one small panel for bots, game servers and websites

on this page
Hosting panels tend to divide into two camps. One camp produces narrow utilities that administer game servers or websites and nothing else; the other assembles an imitation data centre, complete with billing, reseller hierarchies and enough interdependent components to justify a dedicated operations department. I wanted the unclaimed territory between them: a single, modest program capable of operating a Discord bot, a Minecraft world and a website side by side on personal hardware, without demanding perpetual supervision.
That program became RivetPanel. It operates panel.xenyc.ge, and the page you're reading is delivered by it.
What it runs
Bots came first, since they were the original motivation for the entire project. You can upload a ZIP archive, modify files inside the browser's integrated editor, or connect a GitHub repository, and the panel compiles the code in an isolated build stage for whichever runtime the application requires: Node.js, Python, Rust, Go, Java, Ruby or PHP. Starter templates exist for discord.js, discord.py, Poise, JDA and DiscordGo. For anything more unconventional, a repository analysis inspects the source and proposes the language, the build and start commands, and the environment variables the application anticipates.
Game servers followed. Minecraft arrives in eight distributions, from Paper and Purpur to Fabric, NeoForge and the Velocity proxy, and the panel selects a compatible Java version by examining the class files inside the server's own jar. Surrounding that machinery are a server.properties editor, a Modrinth installer for plugins and modifications, and scheduled console commands. Valheim, Rust and Project Zomboid install through SteamCMD. Everything in this category still carries a preview designation, and I'd rather understate its maturity than exaggerate it.
Websites receive the broadest coverage. A static site can originate from an archive or a repository; a framework project built with Vite, SvelteKit, Astro or Nuxt regenerates on every push; a complete application receives a persistent /data directory, health monitoring, release commands and instantaneous rollback. Custom domains undergo DNS verification before the panel agrees to answer for them.
Any bot can request a dedicated PostgreSQL, Redis, MongoDB or MariaDB instance. The database inhabits a private network without any route to the internet, and the panel injects the connection credentials as environment variables, so nobody embeds a database password inside source code.
Staying light
The control plane is written in Go with Fiber and SQLite, and the SvelteKit interface is compiled into the identical binary. Idle, the panel and its container runner consume about 37 MiB of memory. That figure matters. A management layer that monopolises half the memory of an inexpensive server has misunderstood its responsibility, which is to preserve resources for the applications people intended to host.
Every workload inhabits its own Docker container with restrictive defaults: limited memory, processor and process allocations, a read-only root filesystem, no Linux capabilities and an unprivileged user. Environment variables are encrypted with AES-256-GCM and concealed in every API response. Once a token is saved, the browser never receives it again.
Desired state instead of optimism
My favourite component is unglamorous by design. Pressing start doesn't dispatch a command to Docker and assume it succeeded. The panel records the operator's intention ("this bot should be running"), and a reconciliation loop keeps comparing that declared state with observable reality. A crash midway through an operation, or a response Docker never delivered, gets corrected during the subsequent pass. Restart attempts are spaced with exponential backoff, so an application that collapses on launch can't monopolise the processor in an infinite cycle.
Remote nodes
Sooner or later, one machine stops being sufficient. An additional server joins through rivet-agent, enrolled with a single-use token, and thereafter the agent initiates an outbound, mutually authenticated TLS connection to the panel. Consoles, file management, SFTP, backups and GitHub deployments behave the same way on a remote node as on the primary one. Push a commit during a node's outage and the deployment waits, then executes the moment communication resumes.
What it won't pretend to do
An honest inventory of limitations seems as valuable as a catalogue of features. RivetPanel provides no billing, no automatic scheduler redistributing workloads between nodes, and no high-availability configuration. The panel controls the Docker socket, which is equivalent to administrative access over the entire host; a sandbox restricts the potential damage but can't eliminate that privilege. Outbound traffic and storage quotas aren't enforced either, so anybody contemplating hosting code from untrusted strangers should study the isolation documentation beforehand.
Trying it
The panel is distributed as a container image, and three commands start it:
cp deploy/container.env.example .env
docker compose run --rm rivetpanel keygen
docker compose up -dOn its first start it prints a one-time setup code to the log. Open /setup, enter the code, and create the administrator account.
Something broken, or something missing? Open an issue. A report from a genuine installation teaches me far more than any amount of speculation about how people might use the software.
