Provided as-is, no warranty. CachePanel is licensed under the MIT License — use it at your own risk, the authors accept no liability for any damages arising from its use.
A modern, self-hosted web UI for LanCache setups running the tpill90 prefill tools (SteamPrefill, BattleNetPrefill, EpicPrefill).
Those tools are excellent, but selecting which games to pre-cache means running
an interactive terminal checklist (docker exec -it steam-prefill ... select-apps)
for each platform separately, and there's no unified view of what's actually
been cached. CachePanel replaces both of those with one dashboard:
- See what's happening — cache hit/miss ratio, traffic per service, recent download activity, parsed straight from your LanCache logs.
- Pick what to pre-cache, in a browser — no more terminal checklists.
- Steam: your real library, pulled from the official Steam Web API.
- Battle.net: the full list of Blizzard/Activision/Microsoft products available through Battle.net's Tact CDN.
- Epic Games: manual add-by-name (Epic has no public ownership API — see Known limitations).
- Trigger a download on demand — no need to wait for the nightly schedule, with live log streaming while it runs.
- Diagnose cache problems — DNS/reachability checks answer "why isn't this being cached?", plus a cache-integrity scan for corrupted (0-byte) files.
- See who's using it — per-client traffic stats, run history, a traffic graph over time.
- Prometheus metrics + a ready-made Grafana dashboard (see Monitoring below).
- Optional Discord notifications on prefill success/failure and cache disk warnings (see Discord notifications).
- Cover art (via SteamGridDB), 5 accent color themes plus dark mode, and a fully customizable dashboard — reorder or hide any tile, saved per browser.
- Its own login, encrypted secrets at rest, DE/EN UI, optional PostgreSQL storage instead of files (see below).
- Backend: Python, FastAPI
- Frontend: React, Vite, TypeScript, Tailwind CSS, shadcn/ui-style components
- Single Docker image — the backend serves the built frontend, one container, one port.
CachePanel expects to run alongside an existing LanCache + prefill-tools stack (see lancache.net and the tpill90 prefill tools for the base setup — CachePanel doesn't replace those, it drives them).
- Copy
.env.exampleto.env(no Steam credentials needed here — see step 4). - Adjust the volume paths in
docker-compose.ymlif your prefill tools' config directories or LanCache log directory don't live at the defaults (/opt/stacks/<tool>/config,/mnt/lancache/logs). - Build from source, or use the published image — both work with the same
docker-compose.yml:- From source: leave
build: .as-is,docker compose up -d --build. - Published image: every push to
mainpublishesghcr.io/syntaxlab-dev/cachepanel:latest(see.github/workflows/docker-publish.yml). Swap thebuild: .line forimage: ghcr.io/syntaxlab-dev/cachepanel:latestand rundocker compose up -d— no local build needed.
- From source: leave
- Open
http://<host>:8090and go to Settings to enter your own free Steam Web API key and SteamID64 — stored locally on your server, never baked into the image or shared with anyone else who might deploy CachePanel.
CachePanel needs read/write access to each prefill tool's config directory
(to manage the selectedAppsToPrefill.json selection file) and to the Docker
socket (to trigger an on-demand prefill run inside those containers).
CachePanel exposes a Prometheus-compatible /metrics endpoint — deliberately
unauthenticated (it sits outside /api/, see backend/app/routers/metrics.py
for why) so an external scraper can reach it without a session cookie. It
covers cache hit/miss bytes and requests per service, hit ratio, core-container
health, and the most recent prefill run per service.
Example Prometheus scrape config:
scrape_configs:
- job_name: cachepanel
static_configs:
- targets: ["<host>:8090"]A ready-made dashboard ships at
grafana/cachepanel-dashboard.json —
add a Prometheus datasource in Grafana pointed at your scraper, then
Dashboards → New → Import and pick that file.
Settings → Notifications accepts an optional Discord webhook URL (leave blank to disable). When set, CachePanel can post to it on a successful or failed prefill run, and on a cache disk usage warning (checked every 30 minutes, fires once when crossing 90% used and again after dropping back below and re-crossing). A "Send test" button lets you verify the URL immediately. A webhook failure or timeout only logs a warning — it never breaks a prefill run.
By default CachePanel stores everything (settings, run history, schedule,
panel login) as JSON files under ./data — zero config, works out of the
box. If you'd rather run a real database (e.g. you already run Postgres
for other services, or you're not comfortable with plain files holding an
encrypted settings blob), set DATABASE_URL in .env and start the
bundled optional Postgres service:
# .env
DATABASE_URL=postgresql://cachepanel:changeme@postgres:5432/cachepanel
docker compose --profile postgres up -dEvery store (settings, run history, schedule, panel login) switches to
Postgres automatically once DATABASE_URL is set — same encrypted-at-rest
settings blob as before (see Security notes), just kept
in a table instead of a file. The schema is created automatically on
startup if it doesn't exist yet.
No automatic migration: switching from file storage to Postgres (or
back) starts with empty tables/files — you'll re-enter your Settings-page
values and go through the panel's first-run login setup again. There's no
data-loss risk in the sense that your old ./data files stay exactly
where they are, untouched; you just won't see their contents from the
newly-active storage backend until you re-enter them.
- Unraid:
unraid/cachepanel.xmlis a Community Applications template, written against this project's owndocker-compose.yml. Not yet submitted to Community Applications — that requires a publishedghcr.ioimage, which doesn't exist until this project's GHCR publishing workflow ships. Usable manually today via Docker Compose Manager or by adding it as a custom template in the interim. - TrueNAS SCALE (25.04 "Fangtooth" and later): see
truenas/README.md— SCALE's Apps screen runs plain Docker Compose natively now, so this project's own compose file works as a Custom App with a few host-path adjustments. No official catalog entry (that's a separate submission process to iXsystems' own repo, out of scope here).
The backend's interactive API docs are available at /docs (Swagger UI) and
/openapi.json (machine-readable schema) on any running instance.
- Epic Games has no public API for a personal library, so v1 only
supports manually adding app names/IDs. Full catalog browsing (via an
OAuth device-code flow, similar to what the
legendaryproject does) is a natural follow-up. - Battle.net's catalog is a curated static list sourced from
BattleNetPrefill's own source,
since Blizzard doesn't expose a per-account ownership API either. Update
backend/app/services/battlenet_catalog.pyif new products are added upstream. - The panel has its own login (set up on first launch), independent of anything else running alongside it — still meant for a trusted home network rather than public exposure, but no longer wide open by default.
Your Steam credentials (and anything else stored via the Settings page) are
encrypted at rest in data/settings.json, with the key kept in a separate
file (data/.encryption_key). This protects the settings file if it leaks
in isolation (e.g. an accidental backup or copy) — it does not protect
against someone who already has root/filesystem access to the host
CachePanel runs on, since the key lives right next to the data it protects.
For a single-container self-hosted tool there's no realistic way to clear
that bar without an external secrets manager, which is out of scope here.
Bug reports, feature ideas, and PRs are welcome — see CONTRIBUTING.md for local dev setup, code style, and the DE/EN translation contract. Issues labeled
good first issue
are a reasonable place to start.
MIT — see LICENSE.
