A dashboard for your Valheim server.
I wanted one page my family and friends could pull up to see who's on our server, what day it is in-game, who's played the most and who keeps dying. Longhouse runs next to the dedicated server and builds that page from the server's logs.
Status: the dashboard, collector and Docker image work with a vanilla server today. The optional server plugin (tier 1 below) is in mods/ since 0.2.0; see mods/README.md to install them. The public preview runs on invented mock data.
Features
It works fine on a phone too.
There's a dark mode, the frontend is plain static files, and there's no tracking or analytics.
Quick start
Longhouse runs as one container on the same machine as your Valheim server. The deploy/ folder has a compose file that adds a read-only Docker proxy (so Longhouse can read the game's logs) and a Cloudflare Tunnel (so your group can reach it without opening any ports).
-
Copy the deploy folder to your game host
git clone https://github.com/MrGuato/valheim-game-dashboard.git cd valheim-game-dashboard/deploy cp .env.example .env
-
Fill in
.envThe defaults work for a container named
valheim. SetLONGHOUSE_TIMEZONE, and list the bosses you've beaten inLONGHOUSE_BOSSES(Valheim 1.0 stores the world in a format Longhouse can't read them from). -
Start it
docker compose up -d docker compose logs -f longhouse
The dashboard listens on
127.0.0.1:8080on the host. On first start it replays the game container's existing logs, so recent sessions and deaths show up straight away. -
Point a Cloudflare Tunnel at it
If
cloudflaredalready runs on the host, add a public hostname to that tunnel in Zero Trust (Networks → Tunnels), for examplevalheim.yourdomain.com, with servicehttp://localhost:8080. Otherwise create a Docker tunnel, put its token inTUNNEL_TOKEN, point the hostname athttp://longhouse:8080, and start withdocker compose --profile tunnel up -d. -
Lock it to your group
In Zero Trust, add an Access application for the same hostname with a policy that allows only your group's email addresses. See Keeping it private.
Not using Docker for the game server? Point VALHEIM_LOG_PATH at the server's log file instead and run npm start with Node 20 or newer.
How it works
Longhouse only uses what the server knows. Nothing is installed on players' PCs, so nobody's achievements are affected. There are two tiers: a vanilla server, and the same server with the optional Longhouse plugin.
Tier 0 · no mods Vanilla
The collector follows the game container's logs, polls the Steam query port (game port + 1, so 2457 by default) and reads the world save. That's enough for online status, who's connected, session lengths, deaths, raids, the activity heatmap, the world day and time, and boss progress. The log lines it matches look like this:
# a player spawned (or respawned) Got character ZDOID from Astrid : 2084737421:1 # the same line with 0:0 means that player just died Got character ZDOID from Leif : 0:0
Log formats can change between game updates, so patterns live in one file and are covered by tests.
Tier 1 · server plugin Optional
Available A small BepInEx plugin installed only on the server. It reads what the server already knows about connected players: which biome each player is in (or that they're in a dungeon or asleep), their appearance (body, skin, hair, beard), their visible equipment and their health, which is what the character figures are drawn from. It also counts kills per player and creature (Valheim marks every creature with the players who hit it) and how many building pieces each player has standing. It writes LH1 {json} lines to the server log, which the collector already follows. Players don't need to install anything, and it has no effect on their achievements.
Kills are counted from the day the plugin is installed. Skills, food buffs, crafting and lifetime totals live only in each player's character file on their own PC, so Longhouse doesn't show them. Reading those would need a mod in each player's game, and the Valheim mod loader turns achievements off.
What's tracked
| Data | Where it comes from | Needs |
|---|---|---|
| Online status, player count, version | Steam query | Vanilla |
| Who's online, session length, last seen | Server log | Vanilla |
| Deaths, raids, activity heatmap | Server log | Vanilla |
| World day, time of day | Server log (1.0) or world save | Vanilla |
| Boss progression | Pre-1.0 world save, or set by hand | Vanilla |
| Playtime, sessions, longest session | Server log | Vanilla |
| Current biome, in a dungeon, asleep, health | Server plugin | Server plugin |
| Character figure, equipped gear | Server plugin | Server plugin |
| Kills per creature, boss kills (since the plugin was installed) | Server plugin | Server plugin |
| Building pieces standing per player | Server plugin | Server plugin |
| Skills, food, stamina, crafting, lifetime totals | Only on each player's PC | Not shown |
Longhouse never stores chat, IP addresses or exact coordinates. Steam IDs are hashed before they're written to disk and are never sent to the browser.
Keeping it private
The code is public; your server isn't. Longhouse is built so you can publish (or fork) the repo without ever exposing your server address, passwords or your group's data.
What stays out of git
- Server address and secrets live in
.env, which is gitignored. The repo only has.env.examplewith placeholders. - Real data lives in
./data(gitignored). The repo only ships mock data. - The address on the page is hidden by default (
LONGHOUSE_HIDE_ADDRESS=true) and shows "Address shared privately" instead.
# confirm git is ignoring your secrets before you push
git check-ignore .env data/
Don't host real data on GitHub Pages. Pages sites are public to anyone with the link. Use Pages only for the mock-data preview, and serve your group's dashboard from your own machine.
Sharing with your group
| Option | Good for | Effort |
|---|---|---|
| Tailscale | Small groups who don't mind installing an app. Nothing is exposed to the internet. | Low |
| Cloudflare Tunnel + Access | A normal link that asks for an email code. Only addresses on your allow list get in. | Medium |
| Reverse proxy + password | You already run Caddy or nginx. Set LONGHOUSE_PASSWORD or use the proxy's basic auth. | Medium |
Players can also be hidden entirely with LONGHOUSE_HIDDEN_PLAYERS.
Configuration
Everything is set with environment variables in .env. With the deploy compose nothing is required; the defaults fit a container named valheim.
| Variable | Default | Description |
|---|---|---|
LONGHOUSE_SERVER_NAME | empty | Display name. Empty uses the name the server reports. |
LONGHOUSE_BIND | 127.0.0.1:8080 | Host address the deploy compose publishes the dashboard on. |
TUNNEL_TOKEN | empty | Only with --profile tunnel: token for a cloudflared container. |
VALHEIM_CONTAINER | valheim | Name of the game server container whose logs are read. |
DOCKER_HOST | unix:///var/run/docker.sock | Docker API endpoint. The deploy compose points this at the read-only proxy. |
VALHEIM_HOST | host.docker.internal | Host the Steam query is sent to. Never shown on the page. |
VALHEIM_QUERY_PORT | 2457 | Steam query port (game port + 1). |
VALHEIM_MAX_PLAYERS | 10 | Shown when the query port doesn't report a maximum. |
VALHEIM_WORLDS_DIR | /valheim/worlds_local | Where the world saves are mounted (read-only). |
VALHEIM_WORLD | newest .db | World to read. Defaults to the world named in the logs. |
VALHEIM_SAVE_INTERVAL | 1800 | Seconds between world saves, for the "next save" countdown. |
VALHEIM_LOG_PATH | empty | Read this log file instead of the Docker API. |
LONGHOUSE_LOG_TAIL | 300000 | Log lines replayed from the end of the game container's log on start. Lines already counted are skipped. |
LONGHOUSE_PORT | 8080 | Port the dashboard is served on. |
LONGHOUSE_PASSWORD | empty | If set, the browser asks for this password (any username). |
LONGHOUSE_HIDE_ADDRESS | true | Hide the join address on the page. |
LONGHOUSE_HIDDEN_PLAYERS | empty | Comma-separated names to leave off the dashboard entirely. |
LONGHOUSE_BOSSES | empty | Boss ids to mark defeated by hand: eikthyr,elder,bonemass,moder,yagluth,queen,fader. |
LONGHOUSE_REFRESH_SECONDS | 15 | How often the page refreshes and the server is polled. |
LONGHOUSE_TIMEZONE | UTC | IANA timezone for the heatmap and "today", e.g. America/New_York. |
Snapshot schema
The collector serves one JSON document at /api/snapshot.json, and the dashboard renders it. Writing your own collector (a Discord bot, a different log parser) only means producing this shape. The full example lives in assets/mock-data.js.
snapshot.json (abridged)
{
"schema": 1,
"generatedAt": "2026-10-21T19:40:00Z",
"server": {
"name": "Odin's Rest", "online": true, "version": "0.220.5",
"players": { "online": 4, "max": 10 },
"worldDay": 412, "timeOfDay": "Evening", "weather": "Mist",
"address": null
},
"bosses": [ { "id": "eikthyr", "defeated": true, "day": 9, "by": ["Astrid"] } ],
"players": [ {
"name": "Astrid", "online": true, "biome": "mistlands",
"vitals": { "hp": [198, 232] }, "topKill": "Greydwarf",
"equipment": { "weapon": { "name": "Mistwalker", "q": 3 } },
"stats": { "kills": 812, "deaths": 37, "piecesStanding": 2140 }
} ],
"mobs": [ ["Greydwarf", 3104, "blackforest"] ],
"heatmap": [ [0, 0, 1, …] ], // 7 days × 24 hours, avg players
"activity": [ { "m": 6, "type": "join", "text": "**Sigrid** joined the server" } ]
}
Fields that a tier doesn't provide are left out. Biome ids are meadows, blackforest, swamp, mountain, plains, mistlands, ashlands, deepnorth and ocean. Activity text supports **bold** only; everything else is escaped.
Customizing
Colors and theme
All colors are CSS custom properties at the top of assets/styles.css. Change --accent to re-skin buttons, bars and highlights; biome colors are the --b-* tokens. Light is the default. Visitors can switch to dark with the moon button, and the choice is remembered in their browser.
Panels
Every panel is rendered by its own function in assets/dashboard.js (renderBosses, renderLeaders, and so on) and hides itself when the snapshot has no data for it. Adding one means one function and one container in index.html.
Character figures
Profiles show a 3D voxel figure drawn by assets/voxel.js with plain WebGL (no libraries). It's built from a player's appearance (body type, skin, hair, beard) and equipment, shaded with baked ambient occlusion and a thin line along every voxel edge, which is what gives it the pixel-art look. Drag it or use the arrow keys to turn it, and double-click to reset. The small portraits, and browsers without WebGL, use the flat SVG figure from assets/figure.js. Both read gear the same way: armor colors are matched by keywords in the item name, so a new gear set only needs a line in the MATERIALS table in figure.js.
Local development
There's no build step and no npm dependencies. To work on the dashboard with mock data, serve the folder with any static server:
python -m http.server 5178
To run the real collector against a log file, use Node 20 or newer:
VALHEIM_LOG_PATH=./valheim.log LONGHOUSE_DATA_DIR=./data npm start
The page uses api/snapshot.json when the collector serves it and falls back to assets/mock-data.js everywhere else. Run the tests with npm test.
Repository layout
index.html dashboard docs.html this page assets/ styles, renderer, SVG and 3D voxel figures, mock snapshot server/ collector: logs, Steam query, world save, HTTP server/test/ log pattern and state tests deploy/ compose file with Docker proxy and Cloudflare Tunnel Dockerfile ghcr.io/mrguato/valheim-game-dashboard
Roadmap
- DoneDashboard with armory-style profiles, light and dark themes, docs
- DoneCollector: Docker log following, Steam query, world clock and boss keys, history
- DoneMulti-arch Docker image on GitHub Container Registry, Cloudflare Tunnel deploy
- DoneServer plugin (Tier 1) for biome, appearance and gear
- DoneKills per creature and pieces standing, counted on the server
- NextWeather per player
- IdeaDiscord webhook for boss kills, deaths and raids
FAQ
Do I need mods?
No. Without mods you get status, who's online, sessions, deaths, raids and the heatmap. The character figure, gear, biome, health, kills and pieces need the server plugin, which goes on the server only.
Why voxel figures instead of Valheim's real character models?
Valheim's character models and textures are game assets that can't be redistributed, and there's no public model viewer for them the way there is for some other games. So Longhouse builds its own pixel-art Vikings out of voxels from the same appearance and gear data. They look the part, and no game files are involved.
Does it work with crossplay servers?
Log-based features work either way. Crossplay servers may not answer Steam queries, in which case online status and player count fall back to the log.
Will it slow down my server?
It shouldn't. The collector reads the log as it's written and sends one small query every refresh interval. The server plugin only reads state the server already has.
Can players fake their stats?
Not through Longhouse. Everything it shows comes from the server's own log and what the server already knows; players don't send anything.
Does it run on Windows?
Yes, through Docker Desktop. A standalone build without Docker is on the roadmap.
Is this affiliated with Valheim?
No, it's just a side project. Not affiliated with Iron Gate or Coffee Stain.
Contributing
Issues and PRs are welcome. A couple of things to keep in mind:
- Never commit a real server address, password, token or player data. Use the mock snapshot for screenshots.
- Keep the frontend dependency-free so it can be served from anywhere.
- Log patterns and the snapshot schema are public contracts. Changes to either need a test and a note in the changelog.