Wheelhouse docs

The catalogue#

Thirty-eight entries: 36 container apps and two built-in feature modules. The tables below are generated from agent/catalog.json, not retyped, and every column is a field of the catalogue schema. The categories are in the order the catalogue declares them; within a category, entries sort case-insensitively by name.

Everything here is what the catalogue says, not what the agent tests. An entry is a recipe for a container the router runs: an image, a set of volumes, some environment, and a few things the web UI can honestly do with the result — link to it, probe whether it answers, and offer the configuration change that wires it into the router.

Check the version you are running:

bash
curl -sk "$R/api/apps/catalog" -H "Authorization: Bearer $T" | python3 -m json.tool | head -20

How to read the tables#

Each category has two tables. The first says what an entry is; the second says what installing it does.

ColumnMeaning
Portscontainer[/protocol][→ host N], from the entry's ports. Documentation, not configuration — nothing stages a port mapping.
Volumessource → destination, with (file) for a single-file bind mount and ro for read-only. Sources are defaults; the operator may override each one at install.
EnvironmentVariable names. A * marks one the entry declares required.
IntegrationWhat the UI does: the app's own web UI, the health probe, whether it appears on the Dashboard, and how many hints it offers.
NotesHost networking, privilege, Linux capabilities, a memory limit, and any platform gap the entry says it closes.

The entries#

DNS & filtering#

EntryidWhat it isImage
AdGuard HomeadguardNetwork-wide DNS filtering with per-client policies and a query log.docker.io/adguard/adguardhome:latest
Pi-holepiholeThe original DNS sinkhole, with a long-established blocklist ecosystem.docker.io/pihole/pihole:latest
dnsmasqdnsmasqWildcard DNS, split horizon, per-host DHCP options and an arch-conditional PXE matrix.docker.io/jpillora/dnsmasq:latest
UnboundunboundA validating, recursive resolver — no upstream provider sees your queries.docker.io/mvance/unbound:latest
BlockyblockyA small, fast DNS proxy with blocklists and per-client groups.docker.io/spx01/blocky:latest
idPortsVolumesEnvironmentIntegrationNotes
adguard53/tcp_udp, 3000/config/apps/adguard/work/opt/adguardhome/work, /config/apps/adguard/conf/opt/adguardhome/confweb UI :3000, probe :3000/, dashboard, 1 hint
pihole53/tcp_udp, 80/config/apps/pihole/etc/etc/pihole, /config/apps/pihole/dnsmasq.d/etc/dnsmasq.dTZ, WEBPASSWORD*web UI :80/admin, probe :80/admin/, dashboard, 1 hint
dnsmasq53/tcp_udp, 8080/config/apps/dnsmasq/dnsmasq.conf (file) → /etc/dnsmasq.confweb UI :8080, probe :53, 2 hintscaps: net-admin, net-bind-service; closes: Wildcard DNS · per-host DHCP options · PXE architecture matrix
unbound53/tcp_udp/config/apps/unbound/opt/unbound/etc/unboundprobe :53, 1 hint
blocky53/tcp_udp, 4000/config/apps/blocky/config.yml (file) → /app/config.ymlprobe :4000/, dashboard, 1 hint

VPN & remote access#

EntryidWhat it isImage
TailscaletailscaleMesh VPN. Advertise the LAN as a subnet route or make the router an exit node.docker.io/tailscale/tailscale:latest
NetBirdnetbirdOpen-source WireGuard mesh you can self-host end to end.docker.io/netbirdio/netbird:latest
HeadscaleheadscaleSelf-hosted control server for Tailscale clients.docker.io/headscale/headscale:latest
WG-Easywg-easyA web UI for WireGuard peers, with QR codes for phones.ghcr.io/wg-easy/wg-easy:latest
CloudflaredcloudflaredExpose an internal service without opening a port on the WAN.docker.io/cloudflare/cloudflared:latest
WireGuardwireguardThe router's own in-kernel WireGuard — tunnels and peers as native config.built-in feature
idPortsVolumesEnvironmentIntegrationNotes
tailscale/config/apps/tailscale/var/lib/tailscaleTS_AUTHKEY*, TS_HOSTNAME, TS_ROUTES, TS_STATE_DIR, TS_EXTRA_ARGSdashboard, 1 hinthost network; privileged; caps: net-admin, net-raw, sys-module
netbird/config/apps/netbird/etc/netbirdNB_SETUP_KEY*host network; caps: net-admin, sys-admin
headscale8080, 9090/config/apps/headscale/config/etc/headscale, /config/apps/headscale/data/var/lib/headscaleweb UI :8080, probe :8080/health
wg-easy51821, 51820/udp/config/apps/wg-easy/etc/wireguardWG_HOST, PASSWORD_HASHweb UI :51821, probe :51821/, 1 hintcaps: net-admin, sys-module
cloudflaredTUNNEL_TOKEN*1 hint
wireguard1 hint

Monitoring#

EntryidWhat it isImage
ntopngntopngPer-host flow analytics and historical top talkers.docker.io/ntop/ntopng:latest
NetdatanetdataPer-second system metrics with no configuration.docker.io/netdata/netdata:latest
PrometheusprometheusScrape and retain metrics — including Wheelhouse's own.docker.io/prom/prometheus:latest
GrafanagrafanaDashboards over Prometheus, Loki or anything else you point it at.docker.io/grafana/grafana:latest
Node Exporternode-exporterHost metrics for Prometheus, straight off the router.docker.io/prom/node-exporter:latest
Uptime Kumauptime-kumaUptime monitoring with notifications, run from inside the network.docker.io/louislam/uptime-kuma:latest
Speedtest Trackerspeedtest-trackerRuns a speed test on a schedule and keeps the history.ghcr.io/linuxserver/speedtest-tracker:latest
OpenSpeedTestopenspeedtestA LAN-side speed test target — measures the network, not the internet.docker.io/openspeedtest/latest:latest
LibreSpeedlibrespeedA lighter LAN speed test with no Flash, Java or dependencies.docker.io/linuxserver/librespeed:latest
idPortsVolumesEnvironmentIntegrationNotes
ntopng3000/config/apps/ntopng/var/lib/ntopngweb UI :3000, probe :3000/, dashboard, 1 hinthost network; caps: net-admin, net-raw
netdata19999/config/apps/netdata/config/etc/netdata, /config/apps/netdata/lib/var/lib/netdata, /proc/host/proc ro, /sys/host/sys roweb UI :19999, probe :19999/api/v1/info, dashboardhost network; privileged; caps: sys-admin
prometheus9090/config/apps/prometheus/prometheus.yml (file) → /etc/prometheus/prometheus.yml, /config/apps/prometheus/data/prometheusweb UI :9090, probe :9090/-/healthy, dashboard, 1 hint
grafana3000/config/apps/grafana/var/lib/grafanaGF_SECURITY_ADMIN_PASSWORD*web UI :3000, probe :3000/api/health, dashboard
node-exporter9100/proc/host/proc ro, /sys/host/sys roprobe :9100/metricshost network
uptime-kuma3001/config/apps/uptime-kuma/app/dataweb UI :3001, probe :3001/, dashboard
speedtest-tracker80/config/apps/speedtest-tracker/configAPP_KEY*, DB_CONNECTIONweb UI :80, probe :80/, 1 hint
openspeedtest3000, 3001web UI :3000, probe :3000/
librespeed80web UI :80, probe :80/

Security#

EntryidWhat it isImage
CrowdSeccrowdsecParses logs, detects attacks, and shares signals with a community blocklist.docker.io/crowdsecurity/crowdsec:latest
ZeekzeekNetwork security monitor that turns traffic into structured logs.docker.io/zeek/zeek:latest
VaultwardenvaultwardenA Bitwarden-compatible password server, kept on your own hardware.docker.io/vaultwarden/server:latest
Suricata IDSsuricataThe IDS VyOS ships: Suricata watching interfaces you name, driven from the config tree.built-in feature
idPortsVolumesEnvironmentIntegrationNotes
crowdsec8080/config/apps/crowdsec/config/etc/crowdsec, /config/apps/crowdsec/data/var/lib/crowdsec/data, /var/log/var/log/host roprobe :8080/health, 1 hint
zeek/config/apps/zeek/logs/usr/local/zeek/logs, /config/apps/zeek/etc/usr/local/zeek/etchost network; caps: net-admin, net-raw
vaultwarden80/config/apps/vaultwarden/dataADMIN_TOKEN, SIGNUPS_ALLOWEDweb UI :80, probe :80/alive
suricata1 hint

Network services#

EntryidWhat it isImage
Nginx Proxy ManagernpmReverse proxy with a UI and automatic Let's Encrypt certificates.docker.io/jc21/nginx-proxy-manager:latest
CaddycaddyA reverse proxy that gets its own certificates with no configuration.docker.io/library/caddy:latest
TraefiktraefikDynamic reverse proxy that discovers containers as they appear.docker.io/library/traefik:latest
netboot.xyznetbootxyzNetwork-boot installers for anything, without building a PXE tree.ghcr.io/linuxserver/netbootxyz:latest
MosquittomosquittoAn MQTT broker for the IoT half of the network.docker.io/library/eclipse-mosquitto:latest
idPortsVolumesEnvironmentIntegrationNotes
npm80, 443, 81/config/apps/npm/data/data, /config/apps/npm/letsencrypt/etc/letsencryptweb UI :81, probe :81/, dashboard, 2 hints
caddy80, 443/config/apps/caddy/Caddyfile (file) → /etc/caddy/Caddyfile, /config/apps/caddy/data/dataprobe :80/
traefik80, 443, 8080/config/apps/traefik/etc/traefikweb UI :8080, probe :8080/ping
netbootxyz3000, 69/udp, 80/config/apps/netbootxyz/config/config, /config/apps/netbootxyz/assets/assetsweb UI :3000, probe :3000/, 1 hintcloses: PXE architecture matrix (with dnsmasq)
mosquitto1883, 9001/config/apps/mosquitto/config/mosquitto/config, /config/apps/mosquitto/data/mosquitto/dataprobe :1883

Management#

EntryidWhat it isImage
PortainerportainerContainer management for everything running on the router.docker.io/portainer/portainer-ce:latest
HomepagehomepageA start page that links every service on the network.ghcr.io/gethomepage/homepage:latest
HomerhomerA static start page — one YAML file, no runtime.docker.io/b4bz/homer:latest
ntfyntfyPush notifications from scripts, over HTTP.docker.io/binwiederhier/ntfy:latest
GotifygotifySelf-hosted push notifications with an Android client.docker.io/gotify/server:latest
WatchYourLANwatchyourlanNotices when a device appears on the network that has not been seen before.docker.io/aceberg/watchyourlan:latest
DozzledozzleLive container logs in a browser, no storage, no setup.docker.io/amir20/dozzle:latest
IT Toolsit-toolsA pile of offline network and encoding tools in one page.ghcr.io/corentinth/it-tools:latest
Wiki.jswikijsSomewhere to write down why the config is the way it is.ghcr.io/requarks/wiki:latest
idPortsVolumesEnvironmentIntegrationNotes
portainer9443, 9000/config/apps/portainer/data, /run/podman/podman.sock/var/run/docker.sockweb UI :9000, probe :9000/, 1 hint
homepage3000/config/apps/homepage/app/configweb UI :3000, probe :3000/
homer8080/config/apps/homer/www/assetsweb UI :8080, probe :8080/
ntfy80/config/apps/ntfy/cache/var/cache/ntfy, /config/apps/ntfy/etc/etc/ntfyweb UI :80, probe :80/v1/health
gotify80/config/apps/gotify/app/dataGOTIFY_DEFAULTUSER_PASS*web UI :80, probe :80/health
watchyourlan8840/config/apps/watchyourlan/dataIFACESweb UI :8840, probe :8840/, 1 hinthost network; caps: net-admin, net-raw
dozzle8080/run/podman/podman.sock/var/run/docker.sock roweb UI :8080, probe :8080/
it-tools80web UI :80, probe :80/
wikijs3000/config/apps/wikijs/wiki/dataDB_TYPE, DB_FILEPATHweb UI :3000, probe :3000/

The two built-in entries#

wireguard and suricata are kind: "builtin". They are not containers: they gate a native page in the web UI, and "installing" one stages nothing at all. The agent says so rather than pretending:

This is a built-in feature, not a container — installing it enables its page instantly
and stages nothing.

Each declares a config_path. If configuration exists under that path, the page stays visible whether or not the feature is enabled — a page must never be hidden while the configuration it edits is live on the router.

What is deliberately absent#

Installing one#

Three things have to be true before the router will commit a container: the image must be on the router, every volume source must exist, and the configuration must be valid. That is why installing is four calls rather than one.

bash
# 1. What would be staged, and is the image here yet?
curl -sk -X POST "$R/api/apps/plan" -H "Authorization: Bearer $T" \
  -H 'Content-Type: application/json' \
  -d '{"app_id":"adguard","name":"adguard","address":"10.99.0.10"}'

# 2. Pull the image onto the router.
curl -sk -X POST "$R/api/apps/pull" -H "Authorization: Bearer $T" \
  -H 'Content-Type: application/json' -d '{"app_id":"adguard"}'

# 3. Create the volume sources.
curl -sk -X POST "$R/api/apps/prepare" -H "Authorization: Bearer $T" \
  -H 'Content-Type: application/json' \
  -d '{"app_id":"adguard","name":"adguard"}'

# 4. Stage the operations from step 1, then commit them in the Commit Bar.

Lifecycle is configuration too#

There is no control socket. Stopping an app stages set container name <n> disable; starting it deletes that node; restarting is a two-commit bounce, and if the second commit fails the app is left down and the API says so in those words.

See also#

Checked against#

agent/catalog.json, agent/catalog.go (loadCatalog, planInstall, probeApp), agent/apps.go (handleAppPlan, handleAppPull, handleAppPrepare, handleAppRestart, handleAppFeature, collectInstalled), agent/catalog_test.go, scripts/check-images.py, docs/apps.md, PLAN.md §8.

Updated 2026-09-02 catalogue apps containers