🌳 Yggdrasil Panel
Yggdrasil Panel is a self-hosted game & app server control panel for
Debian/Ubuntu (Docker) — think AMP + Pterodactyl, but radically easier to
install, update and maintain, and not just for games: run databases and homelab
apps (Vaultwarden, Gitea, Uptime Kuma, Grafana, WordPress, Nextcloud…) from the
same one-click "Runes".
📖 Documentation · 🌐 yggdrasilpanel.com · 💬 Discord
- One binary. A single static Go binary with the web UI embedded. No separate
database, no Redis, no required reverse proxy.
- Runs on a Raspberry Pi. Pure-static builds for amd64 and arm64; the
installer auto-detects your architecture, so a Pi 4/5 (64-bit) is a first-class
low-power host.
- One command to install. The installer handles Docker and everything else.
- Extensible. A game or app = one declarative gameskill file (shown as a
Rune in the UI). Minecraft Java, Minecraft Bedrock, Uptime Kuma and Vaultwarden
are built in; a growing library of community runes — DayZ, Rust, Terraria,
Factorio, databases and homelab apps — is in
community-runes/
to import in one click.
- Installable PWA. Mobile-friendly, light and dark themes, works as an app on iOS/Android.
A hobby project, maintained by one person, in the open. The latest release is the supported
one — fixes ship forward instead of being backported, and the panel can update itself in place.
The releases are the changelog;
SECURITY.md has the threat model and how to report a vulnerability.
🤖 Built with Claude Code. Provided as‑is,
with no warranty and no liability whatsoever — you use it entirely at your own
risk. See the Disclaimer below.
Install
curl -fsSL https://raw.githubusercontent.com/kristianwind/yggdrasil/main/install.sh | sudo bash
The installer detects your distro, installs Docker if missing, creates a
dedicated service user, drops a systemd unit, and prints your login URL and a
generated admin password. Re-running it upgrades or repairs an existing install.
Updating later is just: swap the binary + restart the service (or re-run the
one-liner — it's idempotent).
What it reports
Yggdrasil Panel phones home, on by default, and this is the whole of it:
{ "instance_id": "a random uuid generated on this install", "version": "v0.3.0" }
Once a day, to https://beacon.yggdrasilpanel.com/api/beacon. No server names, no addresses, no
configuration, no user data — and the collector stores no IP. The id identifies an install to nobody;
it exists only so repeat pings can be counted as one, so the project can see roughly how many people
run it.
The panel tells you this itself the first time an admin signs in, with a one-click way to decline.
You can also turn it off, or point it at your own collector, under Settings → Beacon, which shows
the literal payload. Setting beacon_enabled to 0 is respected permanently — upgrades never turn it
back on.
Nothing else in the panel reports anything anywhere.
Documentation
📖 Full documentation — or jump straight to
Getting started, which takes you from a bare box to a
running game server.
Guides for servers, runes,
users & permissions,
backups & schedules,
networking,
monitoring & alerts,
notifications, and
Kvasir, the AI assistant.
Reference: configuration,
HTTP API, rune schema.
Key features
Servers & runtime
- Create / start / stop / restart / delete servers, each in its own Docker
container with per-server CPU & RAM limits and clean isolation
- Real-time console (WebSocket) with command input, plus live log streaming
- One-time install flow that runs the gameskill's setup in an ephemeral
container with live progress (e.g. downloads the right Paper/Vanilla/Fabric jar)
- Live status —
installing → starting → running → stopped with a clear
starting state until the server logs it's actually ready to accept players
- Restart-on-crash + crash detection; conflict-free port allocation with
per-server ports published 1:1 for Steam games (so they advertise correctly)
- Resource monitoring (CPU / RAM) and player count / status via query
protocols (A2S for Steam games, Minecraft Java SLP, Bedrock ping)
- RCON where the game has it — Source/Minecraft (TCP), Rust (WebSocket),
DayZ (BattlEye)
- Reclaims disk on delete — removing a server frees its full data directory
(game files, world, configs), and Steam installs pre-check free space with a
clear error instead of a cryptic Steam failure
- Graceful stop — rune-tunable shutdown grace period + an optional pre-stop
save command, so games that flush persistence on shutdown (DayZ's Central
Economy, Minecraft) aren't
SIGKILL'd mid-save on a restart
Live admin & moderation (rune-declared, generic across games)
- Safe restart — broadcast an in-game countdown to players first
(rune-declared warnings), with an optional pre-restart backup; manual or scheduled
- Auto-restart — a per-server "restart every N hours" toggle, managed for you
(a scheduled restart under the hood, with the same warnings + backup options)
- Watchdog / auto-heal — when a running server stops answering its query for
several checks in a row (hung, not merely crashed), the panel auto-restarts it and
notifies, with a cooldown so it never restart-loops
- Wipe — reset a game's world / persistence (rune-declared paths) behind a
confirm dialog with a backup-first option; config, whitelist and mods are kept
- Players tab — a live roster over RCON with kick, broadcast and
lock joins, all rune-declared (DayZ via BattlEye BERcon)
- Activity feed — the game's admin log parsed into joins / disconnects /
deaths / kills, newest first (rune declares the log path + parse patterns)
Kvasir — the AI assistant (optional; a fresh install has no AI at all)
- Bring your own LLM — Anthropic, OpenAI, OpenRouter, DeepSeek, Mistral, Ollama or any
OpenAI-compatible endpoint. The key is encrypted at rest and never echoed back
- Chat that knows your panel — streaming answers grounded in the panel's own data
and its documentation: "were there players overnight?", "why did it crash?", "is
any site being attacked?". Read-only lookups (player sessions, metrics, backups, log
search, security events) run scoped to the servers the asker is allowed to see
- You choose how much it may read — snapshot only, panel data, or panel data plus
logs. The panel detects whether the configured model is local or in the cloud and
warns before log content would leave the box
- Proactive watchers explain crashes and anomalies in plain language; anything that
changes a server is propose-then-confirm, never automatic
- Claude connector (MCP) — add the panel to Claude and ask it directly. It acts as
you, with exactly your permissions, and everything it does lands in the audit log
Games & apps (gameskills / "Runes")
- Built in (no import needed): Minecraft Java, Minecraft Bedrock, Uptime Kuma,
Vaultwarden and Cloudflare Tunnel — useful the minute the installer finishes
- Steam games as community runes: Rust and DayZ — DayZ with Workshop
mods (auto-downloaded + signed) and BattlEye RCON, plus a Mods tab that shows
each mod's on-disk + live Steam Workshop status (so a mod removed upstream is
flagged instead of silently breaking joins), and Norn, a loot-economy editor
that fixes modded loot despawning too fast
- Browse GitHub — install community runes with one click straight from a repo's
YAML folder (defaults to this repo's
community-runes/,
grouped into databases/ apps/ games/); or upload a .yaml yourself
- Community runes (grouped, 60+ and growing): databases (MongoDB, MariaDB,
PostgreSQL); apps — media (Jellyfin, Plex, Navidrome, Audiobookshelf, Immich,
the *arr stack), homelab (Gitea, Grafana, Portainer, Uptime Kuma, Home Assistant,
Pi-hole, AdGuard Home, Nginx Proxy Manager, ntfy, Beszel), productivity (Nextcloud,
Paperless-ngx, Karakeep, linkding, Memos, Mealie, FreshRSS, Excalidraw,
Stirling-PDF, IT-Tools), publishing (WordPress, static + PHP sites), NAS (Samba,
Syncthing, Duplicati); games (Terraria, Factorio, Luanti/Minetest,
Genshin/Grasscutter)
- App stacks — a rune can bring its own sidecars (database, cache, search) and the
panel wires them together on a private network: WordPress + MariaDB, Immich +
Postgres + Redis, Karakeep + Meilisearch, all as one server you start and stop
- Not just games — a rune is just a Docker image + ports + env, so most things
that run in Docker run here. Rune fields
data_path (where the volume mounts),
user (run-as uid) and keep_entrypoint (use the image's own entrypoint) make
off-the-shelf app images first-class
- Author your own in declarative YAML; auto-generated settings form
(string/int/bool/dropdown) per rune
- Import existing definitions: Pterodactyl eggs (JSON) and XML
- Steam authorization: anonymous by default; a one-time login flow for games
that require an account (DayZ), with the SteamCMD cache persisted so Steam Guard
isn't re-triggered
Organize & control access
- Realms to group servers; default grouping by game type, custom realms supported
- Multiple admins with scoped permissions — grant
view / control / console / files / create / delete / backup / schedule at global / realm / game-type /
single-server scope
- Built-in file manager + config editor (browse, edit, upload, download)
- Full audit log of admin actions; API tokens to drive everything from
automation (a documented REST API)
- 2FA (TOTP) and passkeys on login, self-service password reset by email
(bring your own SMTP — Yggdrasil never runs a mail server), and a
yggdrasil reset-password break-glass CLI for a lockout
Networking & connectivity
- Automatic port forwarding — when a server starts, Yggdrasil opens its ports
on your router and closes them again when the last server using them stops:
- UPnP/IGD for consumer routers (toggle in Settings → Network)
- UniFi OS (UDM / UDR / Cloud Key G2+) via the local Network API — enter the
gateway URL + a local admin, Test connection, and rules are managed for you
(tagged
[ygg:…] so it only ever touches its own forwards)
- Per-server toggle — choose whether each server opens its firewall ports
(default on) or stays LAN-only
- "Online from outside" check — the panel probes each server on its public
address (not just localhost) and shows a 🌐 reachable / 🚫 badge, so you can
confirm at a glance that the port-forward actually works from the internet
- Connect address shown per server — the panel detects your public IP (or uses
a hostname you set) and shows the exact
host:port players join on
- BattleMetrics (optional) — set a server's BattleMetrics ID for a live
online/players/rank badge on its page
- Per-server subdomains for HTTP apps — the panel creates the proxy host for you
in Nginx Proxy Manager (with the certificate) or a Cloudflare Tunnel route,
so an app is reachable at
app.example.com without touching a router
- Update notifications — the panel checks GitHub releases and shows an
in-app banner when a newer Yggdrasil is available, and can update itself
Automation & operations
- Backups to local / mounted NFS-CIFS, SFTP, SMB, Proxmox Backup Server, or the
cloud — Amazon S3 and S3-compatibles (R2, B2, Wasabi, MinIO…), Google Cloud
Storage, Azure Blob Storage — or a cloud drive (Jottacloud, Dropbox, OneDrive,
Google Drive, Box, pCloud, Mega, Nextcloud/WebDAV) — on-demand or
scheduled, with restore and retention (keep N / X days)
- Scheduler (cron) for backups, restarts, updates, start/stop, console
commands, and in-game messages with editable templates (
{{minutes}},
{{server_name}}) and a "skip if players online" guard
- Notifications via Telegram, Discord, generic webhook, or email (SMTP) —
server up/down, backup done/failed, low disk
- Disk dashboard with a low-space alert
Migrate, import & move
- Move the whole panel to another host with one command — servers, data,
settings and secrets travel together
- Import an app you already run — a WordPress site from an All-in-One
.wpress archive, or files plus a database dump
- Import rune definitions — a Pterodactyl egg, an XML definition, or a
Docker Compose file turned into a rune
- Public status page — an opt-in, per-server board (plus a Discord status embed)
that shows what's up without handing anyone a panel login
Anti-cheat & moderation
- Per-game anti-cheat surface (Paper anti-xray hints, BattlEye/EAC status,
recommended plugins)
- Centralized cross-server ban list — ban a player on one server or everywhere
at once, pushed via RCON/console
- Violation auto-actions — watch logs for a pattern and auto-kick/ban when it
recurs past a threshold
Security
- Passwords hashed with argon2id; RCON / backup / Steam credentials
encrypted at rest (AES-256-GCM) and never logged
- Login rate-limiting, secure headers, CSRF-safe token auth, path-traversal
guards on file access
Screenshots
| Dashboard | Server console & live stats |
|---|
| |
| Servers | Anti-cheat |
| |
| Schedules | Bans & auto-actions |
| |
| Runes (gameskills) | Settings |
| |
| Server settings (connect address, BattleMetrics, delegation) | Users & scoped access |
| |
| File manager | Backups & restore |
|
Light and dark themes; the UI is fully responsive and installable as a PWA.
Concepts
| Term | Meaning |
|---|
| Yggdrasil | the whole panel |
| Gameskill (UI: Rune) | a declarative game definition (YAML). You "carve new runes" to add games. |
| Realm | a group/category of servers. Default grouping is by game type; custom realms supported. |
Each game server runs as its own Docker container — giving per-server CPU/RAM
limits, clean isolation, and portable per-game runtimes (JRE, SteamCMD) without
polluting the host.
Run behind a reverse proxy (TLS)
Yggdrasil is a plain HTTP app on :8080 (configurable). Put any reverse proxy in
front for HTTPS. The console, live logs, and install output are WebSockets, so
your proxy must forward WebSocket upgrades — this is the #1 cause of "it loads
but the console is blank".
NGINX Proxy Manager (NPM)
In Proxy Hosts → Add/Edit:
| Field | Value |
|---|
| Domain Names | yggdrasil.example.com |
| Scheme | http (Yggdrasil terminates no TLS itself) |
| Forward Hostname / IP | the host running Yggdrasil, e.g. 192.168.1.50 |
| Forward Port | 8080 |
| Websockets Support | ✅ ON ← required for console / logs / install |
| Block Common Exploits | ON (fine) |
| SSL tab | request a Let's Encrypt cert, Force SSL + HTTP/2 ON (fine) |
Long-running consoles can sit idle longer than a proxy's default read timeout.
Yggdrasil now sends WebSocket keepalive pings every 30 s, but if you still see the
console drop after a while, paste this into the host's Advanced tab:
location / {
proxy_pass http://192.168.1.50:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400s; # don't time out idle consoles
proxy_buffering off; # stream install/log output immediately
}
Troubleshooting
- Page won't load at all through the proxy → the proxy can't reach Yggdrasil.
Make sure
server.host in the config is 0.0.0.0 (not 127.0.0.1), and from the
proxy host run curl http://192.168.1.50:8080 to confirm it answers.
- Page loads, login works, but console/logs are blank → Websockets Support is
OFF. Turn it ON (or add the Advanced snippet above).
- Console drops after ~1 minute → proxy read timeout; add
proxy_read_timeout
as above.
Verified: page load, login, and the WebSocket 101 Switching Protocols upgrade
all pass through an NGINX reverse proxy — no app-side changes are needed beyond
enabling WebSocket forwarding on the proxy.
Development
Requirements: Go 1.26+, and Node 20+ for the frontend.
The frontend (web/dist) is git-ignored and embedded at build time via
//go:embed all:web/dist, so build it before go build — otherwise the binary
serves an empty UI (it still compiles, thanks to a tracked .gitkeep). CI and
the release workflow build it automatically.
# Build the frontend once (rebuild after changing anything under web/src)
cd web && npm ci && npm run build && cd ..
# Run the backend against a local config (Docker optional; it degrades gracefully)
go run ./cmd/yggdrasil --config ./dev-config.yaml
# Tests
go test ./...
# Build a static binary (frontend must be built first, as above)
CGO_ENABLED=0 go build -o yggdrasil ./cmd/yggdrasil
A minimal dev-config.yaml:
server: { host: "127.0.0.1", port: 8080 }
database: { path: "./ygg.db" }
admin: { username: "admin", password: "changeme" }
See ARCHITECTURE.md for the design, the
rune schema for the gameskill format, and the
API reference for the HTTP API.
Disclaimer
Yggdrasil was built with Claude Code, Anthropic's
agentic coding tool. Much of the code, configuration, and documentation in this
repository was generated by an AI assistant.
This software is provided "AS IS", without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability, fitness
for a particular purpose, and non‑infringement.
There is absolutely no liability. In no event shall the authors, contributors,
or anyone associated with this project be held liable for any claim, damages, data
loss, downtime, security incident, or other liability — whether in an action of
contract, tort, or otherwise — arising from, out of, or in connection with the
software or its use.
You use Yggdrasil entirely at your own risk. It manages game servers, runs
containers, executes install scripts, opens network ports, and stores credentials;
operate it only on systems and data you are willing to lose, keep your own backups,
and review what it does before running it in production. By installing or using this
software you accept full responsibility for any consequences.
Community
Join the Discord — questions, help getting a rune working,
and news about releases.
Found a bug? Please open an issue rather
than reporting it in chat. Issues don't scroll away, and they can carry the logs and version
information a fix actually needs. Discord is the right place to ask whether something is a bug.
License
Yggdrasil Panel is free software under the
GNU Affero General Public License v3.0.
Run it, study it, share it, change it. The Affero part is the one that matters for a
panel: if you modify it and other people use your modified version over a network, they're
entitled to your modified source. Running it — modified or not — for yourself, your friends
or your community carries no obligation beyond keeping the notices intact. Selling hosting
built on a closed fork does.
The panel links to its own source from the sidebar, so anyone using an instance can find it.
If you fork it, point that link at your fork — that's what §13 asks for, and it's one
string.
Copyright © 2026 Kristian Wind.