Centerpoint Home Lab Description Specifically for Centerpoint set up Server Introduction Hardware specifications, storage layout, network topology, and the base Docker environment that underpins the entire Centerpoint stack. Overview & Hardware Overview & Hardware tags: [hardware, centerpoint, specs] Overview Centerpoint is the primary homelab server — a Mini PC form factor running a full self-hosted Docker stack. It acts as the central compute node for all containerised services, reverse proxying, media, AI inference, and automation in the homelab. Property Value Hostname centerpoint Role Primary Docker host Form Factor Mini PC (NUC-style) IP Address 192.168.1.85 Tailscale IP 100.117.158.101 OS Ubuntu 24.04.4 LTS (Noble Numbat) Kernel 6.17.0-35-generic CPU Property Value Model Intel Core Ultra 9 285H Core Architecture P-cores only (no hyperthreading) Physical Cores 16 Sockets 1 Threads 16 (1 per core) The 285H is an Intel Meteor Lake H-series mobile processor with a dedicated Neural Processing Unit (NPU). The absence of E-cores or hyperthreading means all 16 logical CPUs are full performance cores, which benefits parallel container workloads. Memory Property Value Total RAM 96 GB At time of writing, approximately 33 GB is actively in use with ~9 GB free and ~49 GB used as page cache — normal for a long-running Linux system running a large Docker stack. Notes Uptime is typically measured in weeks; the system runs continuously. The NPU in the 285H is not currently leveraged by any container workload — it is available for future OpenVINO or AI inference optimisation. Last Updated: 2026-06-16 Storage Layout Overview Centerpoint uses a three-tier storage strategy: Local NVMe — fast system and application data storage Ceph OSD block devices — two NVMe drives contributing to a distributed Ceph cluster for object/block storage across the homelab NFS mounts from UnRAID — bulk media and data storage from the NAS server at 192.168.1.119 Local NVMe Drives System Drive — nvme1n1 (1.8 TB) The primary system disk, GPT-partitioned with LVM. Partition Size Mount Point Purpose nvme1n1p1 1 GB /boot/efi EFI System Partition nvme1n1p2 2 GB /boot Boot partition nvme1n1p3 (LVM PV) 1.8 TB — LVM physical volume ubuntu-vg/ubuntu-lv 1.8 TB / Root filesystem Current usage: 520 GB used / 1.3 TB free This drive holds the OS, all Docker image layers ( /var/lib/docker), container volumes, and the compose project files under /home/jeeves/docker/. Ceph OSD — nvme0n1 (931.5 GB) Configured as a Ceph OSD block device under LVM management. Mounted at /media/jeeves/1TB_Vol2. The volume appears nearly empty at the filesystem level because Ceph manages the block device directly — actual utilised capacity is tracked by the Ceph cluster, not the OS mount point. Ceph OSD — nvme2n1 (931.5 GB) Second Ceph OSD block device, LVM-managed. Mounted at /media/jeeves/1TB_Vol1. Shows ~115 GB used at the OS level; the remainder is managed by Ceph. Both Ceph OSD drives contribute to a distributed storage pool shared across the homelab. Ceph provides replication and data protection at the cluster level rather than at the individual host level. NFS Mounts — UnRAID ( 192.168.1.119) UnRAID at 192.168.1.119 hosts a 38 TB storage pool (~32 TB in use) and exports three NFS shares, auto-mounted on Centerpoint at boot. Mount Point NFS Source Consumer Services /mnt/Photos 192.168.1.119:/mnt/user/Photos Immich /mnt/data 192.168.1.119:/mnt/user/Data General / miscellaneous /mnt/Multimedia 192.168.1.119:/mnt/user/Multimedia Plex, Jellyfin, Stash, Audiobookshelf These paths are bind-mounted into media containers — the media files themselves are never stored locally on Centerpoint. Notes / Gotchas If UnRAID is offline or rebooting, NFS mounts will stall and any container with a bind mount into /mnt/* will hang or fail to start until the mount recovers. Docker named volumes (databases, config state, etc.) all reside on the system NVMe under /var/lib/docker/volumes/ — these are not automatically backed up to UnRAID. The Ceph cluster should be monitored independently; degraded OSD status does not surface through Docker or the OS mounts in any obvious way. Last Updated: 2026-06-16 Networking Overview Centerpoint is reachable via four distinct paths depending on the use case. Path Address / Domain Use Case LAN (physical) 192.168.1.85 Direct IP access, management Tailscale VPN 100.117.158.101 Secure remote access Traefik (internal) *.home.local Named HTTPS on LAN Traefik (external) *.jeeves5454.ddns.net / *.jeevesconsults.ca Internet-facing HTTPS Physical Network Property Value Interface enp47s0 IP Address 192.168.1.85/24 Default Gateway 192.168.1.1 (Unifi router) Subnet 192.168.1.0/24 The IP is assigned via DHCP with a static lease on the Unifi gateway, making it functionally static. Tailscale Mesh VPN Centerpoint runs as a Tailscale node and exit node, allowing remote devices to route all traffic through the home network. Peer Tailscale IP Platform Notes centerpoint 100.117.158.101 Linux This host — exit node corsec 100.101.27.107 Linux HA OS server — also exit node halcyon 100.81.166.2 Windows Active peer (direct connection) Mobile varies iOS Occasional peers Docker Network Architecture Docker maintains approximately 25 bridge networks on the host. Each application stack follows a consistent isolation pattern: Network Purpose traefik-net Shared bridge — all Traefik-fronted containers attach here *-internal Per-stack isolated networks for app-to-database communication media-network Shared bridge for media stack containers bridge Docker default (not used for production workloads) Typical stack pattern: Application container → attached to both traefik-net and -internal Database / cache sidecar → attached to -internal only (never on Traefik) DNS Internal ( *.home.local): Resolved by AdGuard Home on the LAN. A wildcard DNS record points *.home.local → 192.168.1.85 so Traefik receives all requests and routes by hostname. External ( *.jeeves5454.ddns.net): DDNS via No-IP, keeps the external hostname updated with the home WAN IP. External ( *.jeevesconsults.ca): Managed via DNS provider with appropriate A / CNAME records pointing to the home WAN. Notes / Gotchas If AdGuard Home is down, all *.home.local DNS resolution fails. Services remain accessible via direct IP but HTTPS by hostname will not work. Tailscale must be running ( tailscaled) for the 100.x address to be reachable. The large number of br-* interfaces visible on ip addr is normal — Docker creates one per network. Traefik handles all TLS termination; individual containers do not need to manage certificates. Last Updated: 2026-06-16 Docker Environment Overview All services on Centerpoint run as Docker containers, managed through Docker Compose project files and monitored via Portainer EE. Traefik v3 serves as the reverse proxy and TLS termination point for every service. Docker Engine Property Value Docker Version 29.5.3 Docker Compose v5.1.4 Storage Driver overlayfs Docker Root /var/lib/docker Total Containers 112 Running 97 Stopped 15 Images 108 Project Structure All compose projects live under /home/jeeves/docker/, with one subdirectory per logical stack: /home/jeeves/docker/ ├── adguard/ ├── ai-stack/ ← Ollama, Open Web UI, Faster-Whisper, Kokoro ├── arr/ ← Sonarr, Radarr, Prowlarr, Bazarr, NZBGet, etc. ├── authentik/ ├── bookstack/ ├── crowdsec/ ├── homepage/ ├── immich/ ├── paperless/ ├── traefik/ └── ... (one directory per stack) Each directory contains a docker-compose.yml and any local config files or bind-mount targets specific to that stack. Container Management — Portainer EE Portainer Enterprise Edition provides the web UI for container lifecycle management, log viewing, stack deployment, and environment monitoring. Portainer connects to the Docker daemon via a dockerproxy sidecar container (Tecnativa Docker Socket Proxy) rather than mounting the Docker socket directly. This limits the API surface exposed to Portainer and reduces the blast radius of any container compromise. Traefik v3 — Reverse Proxy Traefik is the single ingress point for all named HTTP/HTTPS traffic. It runs permanently on traefik-net and discovers routes automatically from Docker container labels — no manual reload required when stacks are added or removed. Property Value HTTP port 80 — auto-redirects all traffic to HTTPS HTTPS port 443 Dashboard port 8080 — internal only ( traefik.home.local) Static config /home/jeeves/docker/traefik/traefik.yml Dynamic config /home/jeeves/docker/traefik/dynamic.yml (file-watched) Access logs /var/log/traefik/access.log (JSON, buffered) Certificate Resolvers Resolver Scope Method Certificate Authority letsencrypt External domains HTTP challenge Let's Encrypt step-ca *.home.local ACME Internal Step-CA ( ca.home.local) Internal certificates have a 720-hour (30-day) duration and auto-renew via Traefik's built-in ACME client against the Step-CA instance. Active Plugins Plugin Version Purpose PascalMinder/geoblock v0.3.6 Country-level block on external-facing routes maxlerebourg/crowdsec-bouncer-traefik-plugin v1.3.0 Blocks IPs flagged by the local CrowdSec LAPI Standard Routing Pattern Each service defines Traefik labels in its own docker-compose.yml. The typical pattern for a dual-route service (internal + external) is: labels: - "traefik.enable=true" # External route — Let's Encrypt TLS + security middleware - "traefik.http.routers.-ext.rule=Host(`.jeeves5454.ddns.net`)" - "traefik.http.routers.-ext.entrypoints=websecure" - "traefik.http.routers.-ext.tls.certresolver=letsencrypt" - "traefik.http.routers.-ext.middlewares=authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file" # Internal route — Step-CA TLS, no extra middleware - "traefik.http.routers.-int.rule=Host(`.home.local`)" - "traefik.http.routers.-int.entrypoints=websecure" - "traefik.http.routers.-int.tls.certresolver=step-ca" # Backend service port - "traefik.http.services.-svc.loadbalancer.server.port=" Services that are internal-only omit the -ext router entirely. Services that require OAuth2 authentication on external routes add authentik@file middleware. Notes / Gotchas traefik-net is an externally created network and must exist before any Traefik-fronted stack is started: docker network create traefik-net Never restart dockerproxy while Portainer is actively being used — it will lose its Docker connection until the proxy is back up. Compose files use the stack subdirectory as their working directory — relative bind mount paths resolve from there. overlayfs can accumulate orphaned image layers over time. Prune periodically: docker image prune docker volume prune # caution — only remove truly unused volumes Last Updated: 2026-06-16 Infrastructure & Networking Chapter Introduction Overview This chapter documents the foundational infrastructure layer that all other services depend on. These nine services form the backbone of the Centerpoint stack — handling reverse proxying, TLS, identity, DNS, threat detection, logging, and observability. Services in This Chapter Service Container(s) Purpose Traefik v3 traefik Reverse proxy and TLS termination Step-CA step-ca Internal ACME certificate authority Portainer EE portainer Docker container management UI Authentik authentik-server, authentik-worker, authentik-postgresql, authentik-geoip SSO / identity provider AdGuard Home adguardhome LAN DNS resolver and ad blocking CrowdSec crowdsec, crowdsec-bouncer-traefik Collaborative threat detection and blocking Dozzle dozzle Real-time container log viewer Uptime-Kuma uptime-kuma Service uptime and endpoint monitoring Homepage homepage Homelab dashboard Dependency Order When starting from scratch, services must come up in this order: AdGuard Home — *.home.local DNS must resolve before anything can find its neighbours Step-CA — required for Traefik to issue internal certificates on first boot Traefik — all named HTTPS routes depend on it Authentik (PostgreSQL → Server → Worker) — required before any externally-accessible service that enforces SSO CrowdSec → CrowdSec Bouncer — bouncer cannot connect to LAPI until CrowdSec is healthy All other infrastructure services (Portainer, Dozzle, Uptime-Kuma, Homepage) can start in any order Last Updated: 2026-06-16 Traefik v3 Overview Traefik is the single ingress point for all named HTTP/HTTPS traffic on Centerpoint. It runs on the traefik-net Docker bridge network and discovers routes automatically from container labels — no manual reload is needed when stacks are added or removed. All TLS termination happens at Traefik; individual application containers serve plain HTTP internally. Access Type URL / Endpoint Notes Dashboard https://traefik.home.local Internal only — LAN access required The dashboard is exposed only on the internal Step-CA route. There is no external (internet-facing) route for the Traefik dashboard. Configuration Image: traefik:v3.6.13 Compose project: traefik (same project as step-ca) Ports Port Protocol Purpose 80 TCP HTTP — auto-redirects all traffic to HTTPS 443 TCP HTTPS — primary entrypoint 8080 TCP Traefik API / Dashboard (internal) Static Configuration — traefik.yml Location: /home/jeeves/docker/traefik/traefik.yml Key settings: api: dashboard: true insecure: false entryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https websecure: address: ":443" providers: docker: network: traefik-net exposedByDefault: false file: directory: /config watch: true certificatesResolvers: letsencrypt: acme: email: storage: /letsencrypt/acme.json httpChallenge: entryPoint: web step-ca: acme: email: storage: /letsencrypt/step-ca.json caServer: https://ca.home.local:9000/acme/acme/directory certificatesDuration: 720 experimental: plugins: geoblock: moduleName: github.com/PascalMinder/geoblock version: v0.3.6 crowdsec-bouncer-traefik-plugin: moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin version: v1.3.0 Certificate Resolvers Resolver Scope Method CA Duration letsencrypt External ( *.jeeves5454.ddns.net) HTTP challenge Let's Encrypt 90 days step-ca Internal ( *.home.local) ACME Internal Step-CA at ca.home.local:9000 30 days Plugins Plugin Version Purpose PascalMinder/geoblock v0.3.6 Blocks requests from countries not in allowlist maxlerebourg/crowdsec-bouncer-traefik-plugin v1.3.0 Forwards requests to CrowdSec bouncer for IP checks Geoblock is configured to allow CA, US, and IN only. The middleware is named plex-geoblock@file and is defined in /home/jeeves/docker/traefik/config/crowdsec.yml. Dynamic Configuration Location: /home/jeeves/docker/traefik/config/ (file-watched) File Contents crowdsec.yml plex-geoblock and crowdsec-bouncer middleware defs dynamic.yml Static file-provider routes for Plex (no Docker labels) middlewares.yml Additional shared middleware definitions Standard Dual-Route Label Pattern Each service that requires both internal and external access uses this label pattern in its docker-compose.yml: labels: - "traefik.enable=true" # External route — Let's Encrypt TLS + security middleware - "traefik.http.routers.-ext.rule=Host(`.jeeves5454.ddns.net`)" - "traefik.http.routers.-ext.entrypoints=websecure" - "traefik.http.routers.-ext.tls.certresolver=letsencrypt" - "traefik.http.routers.-ext.middlewares=authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file" # Internal route — Step-CA TLS, no SSO middleware - "traefik.http.routers.-int.rule=Host(`.home.local`)" - "traefik.http.routers.-int.entrypoints=websecure" - "traefik.http.routers.-int.tls.certresolver=step-ca" # Backend - "traefik.http.services.-svc.loadbalancer.server.port=" Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/traefik/traefik.yml /traefik.yml Static config /home/jeeves/docker/traefik/config /config Dynamic config (file-watched) /home/jeeves/docker/traefik/letsencrypt /letsencrypt ACME certificate storage /var/log/traefik /var/log/traefik Access logs (JSON) /var/run/docker.sock /var/run/docker.sock Docker label discovery Dependencies traefik-net Docker network (must be created manually before first start) step-ca container (must be healthy for internal certs to renew) CrowdSec LAPI at crowdsec:8080 (bouncer requires it; Traefik starts without it but will error on blocked requests) Notes / Gotchas traefik-net is externally managed — always create it first: docker network create traefik-net Traefik discovers routes from container labels only on containers attached to traefik-net. If a container is not on this network it will be invisible to Traefik even with traefik.enable=true. exposedByDefault: false means every service must explicitly opt in with traefik.enable=true. The step-ca certificate resolver stores its ACME account and certs in /letsencrypt/step-ca.json (separate from the Let's Encrypt storage). Do not delete this file or certificates will be re-issued from scratch. Access logs are written to /var/log/traefik/access.log in JSON format and are also bind-mounted into the CrowdSec container for log ingestion. Last Updated: 2026-06-16 Step-CA (Internal Certificate Authority) Overview Step-CA is an internal ACME-compatible certificate authority that issues TLS certificates for all *.home.local domains. It allows Traefik to provision and auto-renew trusted certificates for internal services without relying on Let's Encrypt or exposing any ports to the internet. Clients and browsers on the LAN trust *.home.local certificates because the Step-CA root certificate has been manually installed as a trusted CA on each device. Access Type URL / Endpoint Notes ACME API https://ca.home.local:9000/acme/acme/directory Used by Traefik only Step-CA UI N/A No web UI — CLI managed only Step-CA does not have a browser-accessible dashboard. Management is via the step CLI tool. Configuration Image: smallstep/step-ca:latest Compose project: traefik (same project as traefik container) Ports Port Protocol Purpose 9000 TCP ACME API (host-bound: 0.0.0.0:9000) Port 9000 is bound directly to the host so that Traefik (and any other ACME client on the LAN) can reach it at ca.home.local:9000. CA Configuration — ca.json Location: /home/jeeves/docker/step-ca/config/config/ca.json Key settings (secrets redacted): { "root": "/home/step/certs/root_ca.crt", "crt": "/home/step/certs/intermediate_ca.crt", "key": "/home/step/secrets/intermediate_ca_key", "address": "0.0.0.0:9000", "dnsNames": ["ca.home.local"], "db": { "type": "badger", "dataSource": "/home/step/db" }, "authority": { "provisioners": [ { "type": "ACME", "name": "acme" } ] } } Certificate duration for ACME-issued certs is set to 720 hours (30 days) in Traefik's step-ca resolver config. Traefik renews automatically before expiry. Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/step-ca/config /home/step CA configuration, certificates, database Directory Layout Inside the Volume /home/jeeves/docker/step-ca/config/ ├── config/ │ ├── ca.json ← Main CA configuration │ └── defaults.json ← Step CLI defaults ├── certs/ │ ├── root_ca.crt ← Root CA certificate (install this on client devices) │ └── intermediate_ca.crt ├── secrets/ ← Private keys — never expose these │ ├── root_ca_key │ ├── intermediate_ca_key │ └── password ← Key encryption password (REDACTED) └── db/ ← BadgerDB certificate issuance database Dependencies AdGuard Home — ca.home.local must resolve on the LAN for Traefik to reach the ACME endpoint No other service dependencies Trusting the Root CA on Client Devices Every device that accesses *.home.local URLs in a browser must trust the Step-CA root certificate. The root cert is at: /home/jeeves/docker/step-ca/config/certs/root_ca.crt Also available at /home/jeeves/docker/traefik/step-ca-root.crt (Traefik keeps a copy for its own ACME client trust store). macOS sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root_ca.crt Windows Import-Certificate -FilePath root_ca.crt -CertStoreLocation Cert:\LocalMachine\Root Ubuntu / Debian sudo cp root_ca.crt /usr/local/share/ca-certificates/step-ca.crt sudo update-ca-certificates Notes / Gotchas The Step-CA password file ( /home/step/secrets/password) is read on startup. If the volume is lost, the CA must be re-initialized and all client devices must re-import the new root certificate. Do not delete the db/ directory — it contains the certificate issuance history. Loss means Step-CA cannot check for revoked certificates. Step-CA is in the same Portainer compose project as Traefik. Restarting the Traefik stack will also restart Step-CA briefly. Schedule this during off-hours. Traefik caches issued certs in its ACME storage file. If Step-CA is temporarily down, Traefik will continue serving existing certs until they near expiry. Last Updated: 2026-06-16 Portainer EE Overview Portainer Enterprise Edition is the primary container management interface for Centerpoint. It provides a web UI for deploying stacks, viewing container logs, managing volumes and networks, and monitoring resource usage across the Docker environment. All compose stacks are deployed and managed through Portainer rather than by running docker compose directly on the host. Portainer stores stack definitions internally (under /data/compose/) and rebuilds containers from those definitions when updated. Access Type URL Notes Internal https://portainer.home.local LAN access via Step-CA TLS HTTPS UI https://192.168.1.85:9443 Direct IP fallback Tunnel Port 8001 (agent) For Edge Agent / remote access Portainer does not have an external (internet-facing) Traefik route. Access is LAN-only or via Tailscale. Configuration Image: portainer/portainer-ee:latest Compose project: portainer Ports Port Protocol Purpose 8001 TCP Edge Agent tunnel port 9443 TCP HTTPS management UI (host-bound) Traefik Labels traefik.enable: "true" traefik.http.routers.portainer.rule: Host(`portainer.home.local`) traefik.http.routers.portainer.entrypoints: websecure traefik.http.routers.portainer.tls.certresolver: step-ca traefik.http.services.portainer.loadbalancer.server.port: 9443 Internal-only route — no external Traefik router. Volumes / Bind Mounts Host Path / Volume Container Path Purpose portainer_portainer_data /data Portainer state and stack data (named volume, external) /var/run/docker.sock /var/run/docker.sock Direct Docker socket access The portainer_portainer_data volume is declared external: true — it must exist before the stack is started. Note: Portainer mounts the Docker socket directly. This is intentional for Portainer EE; it is the only service with direct socket access. Dependencies Docker socket ( /var/run/docker.sock) — no other service dependencies Traefik on traefik-net for the portainer.home.local route Step-CA for the internal TLS certificate Notes / Gotchas Portainer stores all deployed stack definitions under /data/compose//v/. These are the authoritative copies of each stack's docker-compose.yml while managed through Portainer. Files under /home/jeeves/docker/ may be out of date if a stack was edited directly in the Portainer UI. Portainer EE licence key is stored in the Portainer UI and must be re-entered if the portainer_portainer_data volume is lost. The portainer-ee:latest tag follows the latest stable EE release. Pin to a specific version (e.g. portainer-ee:2.22.0) before any planned maintenance to avoid unintended upgrades. Portainer agent is not separately deployed on Centerpoint — Portainer connects to its local Docker daemon directly via the socket. Portainer does not work over Traefik - I have not been troubleshooting this, and is a future action or Task to manage FUTURE WORK Identify issue with Portainer and Traefik interaction Migrate Portainer cert with Step-CA cert for consistent cert management Last Updated: 2026-06-16 Authentik (SSO / Identity Provider) Overview Authentik is the identity provider and single sign-on (SSO) gateway for all externally-accessible services on Centerpoint. It implements ForwardAuth middleware for Traefik, meaning that any request arriving at an external route tagged with authentik-auth@docker is intercepted and validated by Authentik before being forwarded to the backend service. Authentik also serves as an OAuth2/OIDC provider — services like BookStack use OIDC for native user login rather than ForwardAuth. Access Type URL Notes Internal https://auth.home.local LAN access via Step-CA TLS External https://auth.jeevesconsults.ca Internet-facing — OIDC redirect target The external URL ( auth.jeevesconsults.ca) is the one registered as the OIDC issuer with downstream services. It must be reachable by browsers completing OAuth2 flows. Containers in This Stack Container Image Role authentik-server ghcr.io/goauthentik/server:2026.5.x HTTP server + ForwardAuth endpoint authentik-worker ghcr.io/goauthentik/server:2026.5.x Background task worker (Rust entry) authentik-postgresql postgres:16-alpine Primary database authentik-geoip ghcr.io/maxmind/geoipupdate:v7.x GeoIP database updater (MaxMind) Startup Order authentik-postgresql must reach a healthy state before authentik-server and authentik-worker start. The authentik-geoip sidecar runs independently. Configuration Compose project: authentik Key Environment Variables Variable Value / Notes AUTHENTIK_SECRET_KEY REDACTED — long random string, must stay constant AUTHENTIK_POSTGRESQL__HOST authentik-postgresql AUTHENTIK_POSTGRESQL__NAME authentik AUTHENTIK_POSTGRESQL__USER authentik AUTHENTIK_POSTGRESQL__PASSWORD REDACTED AUTHENTIK_REDIS__HOST redis (internal sidecar or external Redis) AUTHENTIK_LISTEN__HTTP 0.0.0.0:9000 — explicit bind required for Docker bridge AUTHENTIK_ERROR_REPORTING__ENABLED false Important: AUTHENTIK_LISTEN__HTTP: "0.0.0.0:9000" is required in Authentik 2026.5+. Newer versions default to [::] (IPv6 wildcard) which Docker bridge networks cannot reach via IPv4. Without this override, ForwardAuth requests from Traefik fail silently. ForwardAuth Middleware The Authentik ForwardAuth middleware is defined on the authentik-server container labels and is available to all services on traefik-net as: authentik-auth@docker Example usage on a protected external route: labels: - "traefik.http.routers.-ext.middlewares=authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file" OIDC Configuration (for native OIDC apps) Applications using OIDC (e.g. BookStack) configure Authentik as their provider with the following settings: Setting Value Issuer URL https://auth.jeevesconsults.ca/application/o// JWKS URL /.well-known/jwks.json Client ID Per-application (set in Authentik admin UI) Client Secret REDACTED — set per-application email_verified Requires custom Scope Mapping returning true (2026.5+ change) Volumes / Bind Mounts Host Path / Volume Container Path Purpose authentik_media /media Uploaded assets (logos, avatars) authentik_custom-templates /templates Custom email / flow templates authentik_postgresql_data /var/lib/postgresql/data PostgreSQL data directory authentik_geoip_data /usr/share/GeoIP MaxMind GeoIP databases All volumes are named Docker volumes managed by Portainer. Sub-section: PostgreSQL The authentik-postgresql container is a dedicated Postgres 16 sidecar that stores all Authentik state: users, groups, policies, flows, tokens, and audit logs. It is not shared with any other service. Database credentials are passed to the server via AUTHENTIK_POSTGRESQL__* environment variables. The container is on the authentik-internal network only — it is never exposed to traefik-net or the host. Sub-section: GeoIP Updater authentik-geoip runs the MaxMind GeoIPUpdate daemon, which downloads and refreshes the GeoLite2-City and GeoLite2-ASN databases on a schedule. The databases are shared into authentik-server via the authentik_geoip_data volume. A MaxMind account and licence key (REDACTED) are required for the GeoIP databases. Sub-section: Worker authentik-worker runs as the same container image as authentik-server but with the worker entrypoint. It handles background tasks: email delivery, outpost health checks, blueprint application, and event cleanup. Since Authentik 2025.10+, the worker uses a Rust-based entrypoint for improved performance. Dependencies authentik-postgresql (must be healthy before server/worker start) traefik-net (server must be on this network for ForwardAuth to reach it) MaxMind account for GeoIP updates (not strictly required, but GeoIP features will be unavailable without it) Notes / Gotchas If the AUTHENTIK_SECRET_KEY changes, all tokens, sessions, and cookies are immediately invalidated — all users will be logged out across all services. Authentik's admin interface is at /if/admin/. Initial admin credentials are set via the AUTHENTIK_BOOTSTRAP_PASSWORD env var on first start. For OIDC apps: email_verified defaults to false since Authentik 2025.10. Create a custom Scope Mapping that hard-codes "email_verified": True and attach it to the OAuth2 provider. Existing local users who also log in via OIDC must have their External Auth ID set in Admin → Users → Edit → External Auth ID to match the sub claim from the OIDC token, otherwise two separate accounts will be created. DOZZLE_ENABLE_SHELL=true on Dozzle requires that this admin account is separate from the Authentik service account. Last Updated: 2026-06-16 AdGuard Home Overview AdGuard Home serves as the primary LAN-wide DNS resolver for the entire homelab network. All devices on the 192.168.1.0/24 subnet use it as their primary DNS server ( 192.168.1.85:53). It provides: DNS-based ad and tracker blocking Wildcard DNS entry *.home.local → 192.168.1.85 enabling all Traefik internal routes DNS rewrites for custom local hostnames Query logging and per-client statistics Upstream DNS forwarding (via DoH to external resolvers) A secondary AdGuard Home instance runs on the CorSec server (Home Assistant machine at 192.168.1.64). DNS rewrites are also maintained there so that name resolution continues during Centerpoint maintenance or restarts. Client devices should have both IPs configured as DNS resolvers. Access Type URL Notes Internal https://adguard.home.local LAN access via Step-CA TLS Direct http://192.168.1.85:80 Plain HTTP UI (AdGuard internal port) DNS service itself runs on port 53 (TCP + UDP), bound to the host IP. Configuration Image: adguard/adguardhome:v0.107.71 Compose project: adguardhome (managed via Portainer) Ports Port Protocol Purpose 53 TCP+UDP DNS resolver (host-bound) 80 TCP AdGuard web UI (HTTP, internal) 443 TCP DNS-over-HTTPS 853 TCP DNS-over-TLS 3000 TCP Initial setup port Only port 53 is bound to the host. Other ports are exposed only within traefik-net. Traefik Labels traefik.enable: "true" traefik.http.routers.adguard.rule: Host(`adguard.home.local`) traefik.http.routers.adguard.entrypoints: websecure traefik.http.routers.adguard.tls.certresolver: step-ca traefik.http.services.adguard.loadbalancer.server.port: 80 Internal-only route — no external Traefik router. Critical Wildcard DNS Entry AdGuard Home (both primary and secondary) must have a DNS Rewrite configured as: *.home.local → 192.168.1.85 This single wildcard record means Traefik receives all *.home.local HTTP requests and routes them by hostname. Without it, no internal service domain resolves. All custom DNS rewrites (for individual hostnames outside the wildcard) must be kept in sync between the Centerpoint and CorSec instances. Upstream DNS Resolvers Upstream DNS (for forwarding public queries) is configured in AdGuard's settings UI. Common configuration: Primary: https://dns.cloudflare.com/dns-query (DoH) Secondary: https://dns.google/dns-query (DoH) Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/adguard/conf /opt/adguardhome/conf Configuration ( AdGuardHome.yaml) /home/jeeves/docker/adguard/work /opt/adguardhome/work Query logs and statistics database Networks AdGuard Home is on traefik-net for its web UI route and has port 53 bound directly to the host for DNS service. Dependencies None (AdGuard must start first; it has no upstream service dependencies) AdGuard's own DNS resolution bootstraps via the upstream DoH resolvers using hardcoded IPs if DNS is not yet available High Availability Instance Host IP Role Primary Centerpoint 192.168.1.85 Primary — full config, rewrites, logging Secondary CorSec (Home Assistant) 192.168.1.64 Backup — same rewrites, ad-block lists Client devices should configure both DNS servers in priority order. The secondary takes over automatically if the primary becomes unreachable. DNS rewrites must be manually kept in sync between the two instances — there is no automated synchronisation. Notes / Gotchas The work/data directory requires root-level permissions — the container runs as root. This is expected behaviour. If port 53 is already in use on the host (e.g. systemd-resolved), AdGuard will fail to start. On Ubuntu 24.04, disable the stub listener: sudo systemctl disable --now systemd-resolved Client-specific rules (blocking, bypass, custom upstream) are stored in AdGuardHome.yaml and are preserved across container upgrades as long as the conf/ bind mount is intact. AdGuard Home should be pinned to a specific version tag rather than latest to avoid schema migrations breaking the config on unexpected upgrades. When adding a new *.home.local service, no DNS change is needed (the wildcard covers it). New custom hostnames outside the wildcard pattern must be added to both the primary and secondary AdGuard instances. FUTURE WORK Update the DNS blocklist to include more sites Provide a summary of sites blocked via AI for review and/or include in Grafana for trend analysis to know if the numbers go up or down over time- find rogue machines Last Updated: 2026-06-16 CrowdSec Overview CrowdSec is a collaborative intrusion detection and prevention system integrated directly into Traefik. It analyses Traefik access logs in real time, detects attack patterns (brute force, CVE exploitation, bad bots, etc.), and instructs Traefik to block flagged IPs before they reach any backend service. The stack consists of two containers: crowdsec — the Security Engine (LAPI + agent). Ingests logs, applies detection scenarios, maintains a decision list, and shares signals with the CrowdSec community. crowdsec-bouncer-traefik — the Traefik bouncer. Traefik forwards every incoming request to it via ForwardAuth; the bouncer checks the IP against CrowdSec's local decision database and returns allow/deny. Access Neither CrowdSec container has a web UI. Management is via the cscli CLI inside the crowdsec container: docker exec -it crowdsec cscli decisions list docker exec -it crowdsec cscli alerts list docker exec -it crowdsec cscli metrics Configuration Compose project: crowdsec (managed via Portainer) Containers crowdsec — Security Engine Image: crowdsecurity/crowdsec:latest Property Value Network traefik-net Restart policy unless-stopped Key environment variables: Variable Value / Notes GID 1000 COLLECTIONS crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/whitelist-good-actors BOUNCER_KEY_TRAEFIK REDACTED — shared secret used by the bouncer to authenticate with LAPI Collections installed: Collection Purpose crowdsecurity/traefik Detects attack patterns in Traefik access logs crowdsecurity/http-cve Detects exploitation of known HTTP CVEs crowdsecurity/whitelist-good-actors Whitelists known-good crawlers and services crowdsec-bouncer-traefik — Traefik Bouncer Image: fbonalair/traefik-crowdsec-bouncer:latest Property Value Network traefik-net Internal port 8080 (ForwardAuth endpoint) Key environment variables: Variable Value / Notes GIN_MODE release CROWDSEC_AGENT_HOST crowdsec:8080 — CrowdSec LAPI endpoint CROWDSEC_BOUNCER_API_KEY REDACTED — must match BOUNCER_KEY_TRAEFIK How the Bouncer Integrates with Traefik The middleware is defined in /home/jeeves/docker/traefik/config/crowdsec.yml: http: middlewares: crowdsec-bouncer: forwardAuth: address: http://crowdsec-bouncer-traefik:8080/api/v1/forwardAuth trustForwardHeader: true This middleware is referenced on external Traefik routes as crowdsec-bouncer@file. Log Ingestion CrowdSec reads Traefik access logs from two bind-mounted paths: Host Path Container Path Notes /var/log/traefik /var/log/traefik:ro Primary log location /home/jeeves/docker/traefik/logs /logs/traefik:ro Secondary / rotated logs The acquis.yaml config file at /home/jeeves/docker/crowdsec/config/acquis.yaml defines which log files to tail and in what format. Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/crowdsec/data /var/lib/crowdsec/data Decision database and GeoIP data /home/jeeves/docker/crowdsec/config /etc/crowdsec Scenarios, parsers, config /var/log/traefik /var/log/traefik:ro Traefik access log (read-only) /home/jeeves/docker/traefik/logs /logs/traefik:ro Traefik rotated logs (read-only) Dependencies Traefik (must be running and writing access logs for CrowdSec to process) crowdsec LAPI must be healthy before crowdsec-bouncer-traefik starts Internet access required for CrowdSec community hub sync (pulling updated block lists) Notes / Gotchas The BOUNCER_KEY_TRAEFIK / CROWDSEC_BOUNCER_API_KEY pair must match exactly. If mismatched, the bouncer will fail to authenticate with LAPI and Traefik will receive a 200 (passthrough) response from ForwardAuth — effectively disabling the bouncer silently. CrowdSec community sharing ( online_api_credentials.yaml) requires an account at app.crowdsec.net. The credentials file is stored at /home/jeeves/docker/crowdsec/config/online_api_credentials.yaml. crowdsec-bouncer@file middleware must be applied to external routes only. Applying it to internal ( *.home.local) routes will check LAN IPs against the community block list, which may block legitimate traffic unexpectedly. Manually ban an IP with: docker exec -it crowdsec cscli decisions add --ip --duration 24h --reason "manual" Remove a ban: docker exec -it crowdsec cscli decisions delete --ip Last Updated: 2026-06-16 Dozzle Overview Dozzle is a lightweight, real-time log viewer for Docker containers. It provides a web UI to stream, search, and follow container logs without needing to SSH into the host and run docker logs. It is configured to monitor containers on both Centerpoint and Lusankya (the second homelab server at 192.168.1.114). Access Type URL Notes Internal https://dozzle.home.local LAN access via Step-CA TLS No external (internet-facing) Traefik route — LAN and Tailscale access only. Configuration Image: amir20/dozzle:latest (v10.6.0 at time of writing) Compose project: dozzle (managed via Portainer) Environment Variables Variable Value Purpose DOZZLE_HOSTNAME Centerpoint Display name for the local host DOZZLE_REMOTE_HOST tcp://192.168.1.114:2375|Lusankya Adds Lusankya as a remote Docker host DOZZLE_ENABLE_ACTIONS false Disables container start/stop from UI DOZZLE_ENABLE_SHELL true Enables shell access to containers DOZZLE_ENABLE_SHELL=true allows executing shell commands inside any container from the Dozzle UI. This is a powerful capability — ensure Dozzle is not accessible externally. Traefik Labels traefik.enable: "true" traefik.http.routers.dozzle.rule: Host(`dozzle.home.local`) traefik.http.routers.dozzle.entrypoints: websecure traefik.http.routers.dozzle.tls.certresolver: step-ca traefik.http.services.dozzle.loadbalancer.server.port: 8080 Internal-only route. No Authentik ForwardAuth on this route — access is controlled at the network level (LAN + Tailscale only). Volumes / Bind Mounts Host Path Container Path Purpose /var/run/docker.sock /var/run/docker.sock:ro Read-only Docker socket access Dozzle mounts the Docker socket read-only. No persistent data volume is required — Dozzle streams logs directly from the Docker daemon and does not store them. Networks Network Purpose traefik-net Exposes the Dozzle web UI Remote Hosts Dozzle connects to Lusankya's Docker daemon at tcp://192.168.1.114:2375. This requires Lusankya's Docker daemon to have TCP exposure enabled. Logs from both Centerpoint and Lusankya containers are visible in a single Dozzle instance. Dependencies Docker socket on Centerpoint ( /var/run/docker.sock) Lusankya Docker daemon accessible at tcp://192.168.1.114:2375 Traefik on traefik-net for the dozzle.home.local route Notes / Gotchas DOZZLE_ENABLE_SHELL=true gives the ability to exec into any container from the browser. Treat this with the same care as SSH access to the host. Dozzle does not persist logs. Once a container is removed, its historical logs are no longer accessible from Dozzle (use docker logs or a log aggregation stack for archival). The latest image tag tracks the latest release. Dozzle updates frequently — check the changelog before pulling, especially for breaking UI changes. Lusankya's Docker TCP port ( 2375) is unencrypted. This is acceptable on the private LAN but should never be exposed externally. Last Updated: 2026-06-16 Uptime-Kuma Overview Uptime-Kuma is a self-hosted uptime monitoring tool that tracks the availability of services and endpoints, sends alerts on downtime, and displays historical uptime statistics. It serves as the primary observability dashboard for the homelab — monitoring both internal services and external URLs. Access Type URL Notes Internal (via Tailscale or LAN direct) Port 3001 on 192.168.1.85 External https://uptime.jeeves5454.ddns.net Internet-facing, protected by Authentik + GeoBlock + CrowdSec Configuration Image: louislam/uptime-kuma:2 Compose project: uptime-kuma (managed via Portainer; local file at /home/jeeves/docker/uptime_kuma/docker-compose.yml) Ports Port Protocol Purpose 3001 TCP Web UI (also bound to host for direct access) Traefik Labels traefik.enable: "true" traefik.http.routers.uptime.rule: Host(`uptime.jeeves5454.ddns.net`) traefik.http.routers.uptime.entrypoints: websecure traefik.http.routers.uptime.tls.certresolver: letsencrypt traefik.http.routers.uptime.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file,uptime-headers traefik.http.middlewares.uptime-headers.headers.customrequestheaders.X-Forwarded-Proto: https traefik.http.services.uptime.loadbalancer.server.port: 3001 The uptime-headers middleware injects X-Forwarded-Proto: https — required because Uptime-Kuma needs to know the original scheme for correct redirect handling. External access is gated behind Authentik SSO, GeoBlock (CA/US/IN), and CrowdSec. Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/uptime_kuma/uptime-kuma-data /app/data SQLite DB, config, logs The data directory stores: kuma.db — SQLite database with monitor config, history, and incidents error.log — Uptime-Kuma application error log Networks Network Purpose traefik-net External Traefik route uptime-kuma_monitoring Internal per-stack network Dependencies Traefik on traefik-net for the external HTTPS route Authentik at auth.jeevesconsults.ca for SSO on the external route Network connectivity to all monitored endpoints Notes / Gotchas Uptime-Kuma v2 uses SQLite. The kuma.db file is the entire state of the service — back it up before any upgrade. The X-Forwarded-Proto header middleware ( uptime-headers) is specifically required for Uptime-Kuma to generate correct URLs in status page embeds and notifications. Monitor configurations (which services to check, intervals, notifications) are all stored in the UI and persisted in the SQLite database. There is no YAML-based config file. If Uptime-Kuma reports false positives for internal services, verify that DNS resolution works correctly from within the Uptime-Kuma container on traefik-net. Notification integrations (email, Telegram, Slack, etc.) are configured in the UI under Settings → Notifications. Last Updated: 2026-06-16 Homepage.Dev Overview Homepage (gethomepage.dev) is the self-hosted service dashboard for Centerpoint. It provides a single-pane-of-glass view of all running services, organised into three tabs that reflect the different access methods available for each service. It also displays live Docker container status by connecting directly to the Docker socket. Access Type URL Notes Internal https://centerpoint.home.local LAN access via Step-CA TLS Direct http://192.168.1.85:3003 Plain HTTP fallback No external (internet-facing) Traefik route. Configuration Image: ghcr.io/gethomepage/homepage:latest (v1.13.2 at time of writing) Compose project: homepage (Portainer-managed; local file at /home/jeeves/docker/homepage/docker-compose.yml) Ports Port Protocol Purpose 3003 TCP Web UI (mapped from internal 3000) Traefik Labels traefik.enable: "true" traefik.http.routers.homepage.rule: Host(`centerpoint.home.local`) traefik.http.routers.homepage.entrypoints: websecure traefik.http.routers.homepage.tls.certresolver: step-ca traefik.http.services.homepage.loadbalancer.server.port: 3000 Internal-only route on centerpoint.home.local. Environment Variables Variable Value PUID 1000 PGID 1000 LOG_TARGETS stdout HOMEPAGE_ALLOWED_HOSTS gethomepage.dev,192.168.1.64:3003,localhost:3003 Dashboard Structure Homepage is configured with three tabs: Tab Service suffix Description External Secure (plain names) Services via *.jeeves5454.ddns.net or *.jeevesconsults.ca Internal Secure (LAN) Services via *.home.local through Traefik Internal Unsecured (IP) Services via direct http://IP:port The default tab is set to Internal Secure. Services can appear in multiple tabs if they have multiple access routes. Docker container status widgets ( server: centerpoint, container: ) are applied to services where applicable. Configuration Files All config files live under /home/jeeves/docker/homepage/config/: File Purpose settings.yaml Dashboard title, theme, background, tab layout services.yaml All service entries grouped by tab and category widgets.yaml Top-bar info widgets (system stats, weather, etc.) bookmarks.yaml Bookmark groups (if used) docker.yaml Docker socket connection config for container status Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/homepage/config /app/config All dashboard config files /var/run/docker.sock /var/run/docker.sock:ro Read-only Docker socket for container status Dependencies Docker socket for live container status display Traefik on traefik-net for the centerpoint.home.local route API keys for service widgets (stored in services.yaml — secrets should be kept in a secrets.yaml file or environment variables, not committed to version control) Notes / Gotchas Homepage requires group names to be globally unique across all tabs. The (LAN) and (IP) suffixes on group names solve this constraint for the multi-tab layout. HOMEPAGE_ALLOWED_HOSTS must include any hostname or IP:port used to access the dashboard; otherwise Homepage returns a 403. The Docker socket mount allows Homepage to show live container state. Homepage accesses it read-only and does not have the ability to start or stop containers. Config changes take effect immediately on file save — no container restart required. Quick Launch search only finds services listed in services.yaml. To enable search indexing for descriptions, set searchDescriptions: true in settings.yaml. Last Updated: 2026-06-16 AI & Automation Chapter Introduction Overview This chapter documents the AI inference, voice processing, and workflow automation services running on Centerpoint. All GPU-accelerated workloads leverage an NVIDIA GeForce RTX 5080 connected to the Mini PC via OcuLink, providing 16 GB of GDDR7 VRAM on the Blackwell architecture (Compute Capability 12.0). GPU Hardware Property Value GPU NVIDIA GeForce RTX 5080 VRAM 16 GB GDDR7 (16,303 MiB) Architecture Blackwell (Compute Capability 12.0) Connection OcuLink (external GPU enclosure) NVIDIA Driver 580.159.03 CUDA Version 12.9 Container Runtime nvidia (all GPU containers) All containers that use the GPU are launched with runtime: nvidia and NVIDIA_VISIBLE_DEVICES=all. No device passthrough via --device /dev/dri is used — the Intel Arc / IPEX-LLM path has been retired. Services in This Chapter Service Container(s) GPU Purpose Ollama ollama Yes Local LLM inference backend Open Web UI open-webui No Chat interface for Ollama and OpenAI APIs Faster-Whisper faster-whisper Yes Speech-to-text (Whisper large-v3-turbo) Kokoro kokoro Yes Text-to-speech (TTS) API Riffado riffado, riffado-db No AI audio podcast app (formerly OpenPlaud) PaperlessAI paperless-ai No Autonomous document classification via Ollama N8N n8n No Workflow automation platform Open Notebook open-notebook, open-notebook-db No AI research notebook (SurrealDB backend) MCP GitHub mcp-github-proxy, github-mcp-server No GitHub MCP server with OAuth 2.1 Compose Project Most AI stack services (Ollama, Open Web UI, Kokoro, Faster-Whisper, Open Notebook, Riffado) are managed as a single Portainer compose project called ai-stack on a shared ai-internal bridge network plus traefik-net. N8N, PaperlessAI, and MCP GitHub are separate Portainer stacks. Last Updated: 2026-06-16 Ollama Overview Ollama is the local large language model (LLM) inference backend for the homelab. It serves models via an OpenAI-compatible REST API and is consumed by Open Web UI, PaperlessAI, and any other service that needs LLM inference without sending data to external providers. Ollama runs with full NVIDIA RTX 5080 acceleration via the nvidia container runtime. All model weights are stored on the local NVMe system drive. Access Type URL Notes Internal https://ollama.home.local Traefik-proxied HTTPS Direct http://192.168.1.85:11434 Raw API (no TLS) No external (internet-facing) route — LAN and Tailscale access only. Configuration Image: ollama/ollama:latest Compose project: ai-stack Runtime: nvidia Ports Port Protocol Purpose 11434 TCP Ollama REST API (host-bound) Traefik Labels traefik.enable: "true" traefik.docker.network: traefik-net traefik.http.routers.ollama.rule: Host(`ollama.home.local`) traefik.http.routers.ollama.entrypoints: websecure traefik.http.routers.ollama.tls.certresolver: step-ca traefik.http.services.ollama.loadbalancer.server.port: 11434 Internal-only route, no authentication middleware — API access is unrestricted on the LAN. Callers must be on the LAN or Tailscale. Environment Variables Variable Value Purpose OLLAMA_HOST 0.0.0.0 Listen on all interfaces NVIDIA_VISIBLE_DEVICES all Expose all NVIDIA GPUs to container NVIDIA_DRIVER_CAPABILITIES compute,utility Required NVIDIA driver caps OLLAMA_NUM_GPU 999 Use all available GPU layers no_proxy localhost,127.0.0.1 Bypass proxy for local calls GPU Acceleration Ollama uses the nvidia container runtime. The RTX 5080 provides 16 GB of VRAM, allowing large models (7B–27B parameter range) to run fully in VRAM without CPU offloading. runtime: nvidia environment: - NVIDIA_VISIBLE_DEVICES=all - NVIDIA_DRIVER_CAPABILITIES=compute,utility Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/ollama/ /root/.ollama Model weights and config Model files are stored under /home/jeeves/docker/ollama/models/ on the local NVMe system drive (1.8 TB). Large models can consume significant space. Networks Network Purpose ai-stack_ai-internal Internal communication with Open Web UI, PaperlessAI traefik-net Exposes Ollama API via Traefik Dependencies NVIDIA container runtime ( nvidia) on the Docker daemon RTX 5080 connected via OcuLink (must be recognised as a CUDA device) Notes / Gotchas OLLAMA_NUM_GPU=999 is the conventional way to tell Ollama to use as many GPU layers as possible. It does not literally use 999 GPUs. Models are downloaded via ollama pull or via the Open Web UI admin panel. Downloaded models persist in the bind-mounted /root/.ollama directory. If the OcuLink connection drops or the GPU is not recognised, Ollama falls back to CPU inference — responses will be significantly slower. Check with: docker exec -it ollama ollama ps The local file at /home/jeeves/docker/ai-stack/ollama-intel-arc/docker-compose.yml is the legacy IPEX-LLM config and is no longer in use. The current stack is managed via Portainer and uses ollama/ollama:latest with CUDA. Last Updated: 2026-06-16 Open-WebUI Overview Open Web UI is the primary chat and AI management interface for the homelab. It provides a ChatGPT-style web frontend connected to the local Ollama backend, with support for conversation history, model selection, RAG (document chat), image generation, and tool use. It can also proxy to external OpenAI-compatible APIs. Access Type URL Notes Internal https://ai.home.local LAN access via Step-CA TLS External https://ai.jeeves5454.ddns.net Internet-facing — Authentik SSO + GeoBlock + CrowdSec Configuration Image: ghcr.io/open-webui/open-webui:main Compose project: ai-stack Ports Port Protocol Purpose 3015 TCP Web UI (mapped from internal 8080) Traefik Labels # Internal route traefik.http.routers.openwebui-internal.rule: Host(`ai.home.local`) traefik.http.routers.openwebui-internal.entrypoints: websecure traefik.http.routers.openwebui-internal.tls.certresolver: step-ca traefik.http.routers.openwebui-internal.service: openwebui-svc # External route traefik.http.routers.openwebui-external.rule: Host(`ai.jeeves5454.ddns.net`) traefik.http.routers.openwebui-external.entrypoints: websecure traefik.http.routers.openwebui-external.tls.certresolver: letsencrypt traefik.http.routers.openwebui-external.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.openwebui-external.service: openwebui-svc traefik.http.services.openwebui-svc.loadbalancer.server.port: 8080 Environment Variables Variable Value / Notes WEBUI_AUTH False — authentication handled by Authentik ENABLE_OLLAMA_API True ENABLE_OPENAI_API True ENABLE_IMAGE_GENERATION True IMAGE_GENERATION_ENGINE automatic1111 IMAGE_GENERATION_MODEL dreamshaper_8 IMAGE_SIZE 400x400 IMAGE_STEPS 8 AUTOMATIC1111_BASE_URL http://stable-diffusion:7860/ AUTOMATIC1111_CFG_SCALE 2 AUTOMATIC1111_SAMPLER DPM++ SDE AUTOMATIC1111_SCHEDULER Karras WEBUI_AUTH=False disables Open Web UI's own login page. Authentication is delegated entirely to Authentik ForwardAuth on the external route. On the internal LAN route, the interface is open — access is controlled by network boundary only. Volumes / Bind Mounts Host Path / Volume Container Path Purpose open_webui_open-webui-data /app/backend/data Conversation history, settings, uploaded docs (named volume, external) Networks Network Purpose ai-stack_ai-internal Reaches Ollama backend on ai-internal network traefik-net Exposes the web UI via Traefik Dependencies ollama — must be running for model inference; Open Web UI will start without it but model requests will fail Authentik — required for external route SSO; LAN route is unaffected if Authentik is down Notes / Gotchas The main image tag tracks the latest development build. For stability, consider pinning to a tagged release (e.g. v0.6.x). WEBUI_AUTH=False means anyone on the LAN can access the internal URL without credentials. If untrusted devices are on the LAN, consider enabling WEBUI_AUTH and creating user accounts, or adding Authentik middleware to the internal route as well. Conversation history and user settings are stored in the named Docker volume. Back this up before upgrades. Open Web UI admin panel is at https://ai.home.local/admin/ — first user to register (if auth is enabled) becomes the admin. Image generation requires the stable-diffusion container to be running (separate service, also on ai-internal network). Last Updated: 2026-06-16 Faster Whisper Overview Faster-Whisper is the speech-to-text transcription service for the homelab. It runs OpenAI's Whisper model via the faster-whisper library (CTranslate2 backend), which offers significantly faster inference than the original Whisper implementation at equivalent or lower VRAM usage. It is used by N8N automation workflows for audio transcription tasks (e.g. podcast processing in the Minuspod pipeline). Access Type URL Notes Internal https://whisper.home.local LAN access via Step-CA TLS No external route — internal automation use only. Configuration Image: hwdsl2/whisper-server:cuda Compose project: ai-stack Runtime: nvidia CUDA Version: 12.9 Ports Port Protocol Purpose 9015 TCP HTTP API (mapped from internal 9000) Traefik Labels traefik.enable: "true" traefik.docker.network: traefik-net traefik.http.routers.whisper.rule: Host(`whisper.home.local`) traefik.http.routers.whisper.entrypoints: websecure traefik.http.routers.whisper.tls.certresolver: step-ca traefik.http.services.whisper.loadbalancer.server.port: 9000 Environment Variables Variable Value Purpose WHISPER_MODEL large-v3-turbo Whisper model variant to load WHISPER_DEVICE cuda Run inference on GPU WHISPER_COMPUTE_TYPE float16 FP16 precision (optimal for CUDA) WHISPER_LANGUAGE en Default transcription language NVIDIA_VISIBLE_DEVICES all Expose all NVIDIA GPUs NVIDIA_DRIVER_CAPABILITIES compute,utility Required NVIDIA driver capabilities Model: large-v3-turbo — the distilled variant of Whisper large-v3, offering near-large accuracy at roughly 3× the speed and reduced VRAM usage. GPU Acceleration runtime: nvidia environment: - NVIDIA_VISIBLE_DEVICES=all - WHISPER_DEVICE=cuda - WHISPER_COMPUTE_TYPE=float16 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/ai-stack/whisper/files /var/lib/whisper Transcription input/output files Model weights are downloaded to a temp directory inside the container on first start and cached within the container layer (not persisted in a named volume). Networks Network Purpose ai-stack_ai-internal Internal access from N8N workflows traefik-net Exposes API via Traefik API Usage The whisper-server exposes a simple HTTP POST endpoint: curl -X POST https://whisper.home.local/inference \ -F file=@audio.mp3 \ -F response_format=json Response: {"text": "Transcribed text here..."} Dependencies NVIDIA container runtime with RTX 5080 access N8N (primary consumer via HTTP calls) Notes / Gotchas The large-v3-turbo model is downloaded on first container start. This can take several minutes and the container will appear unresponsive until the download completes. float16 compute type requires a GPU with FP16 support. The RTX 5080 (Blackwell) supports this natively. On CPU-only fallback, use int8 instead. The files bind mount ( /var/lib/whisper) can be used to pre-stage audio files for batch transcription if needed. Last Updated: 2026-06-16 Kokoro Overview Kokoro is a high-quality, locally-hosted text-to-speech (TTS) service. It exposes an OpenAI-compatible TTS API, making it a drop-in replacement for external TTS services in any application that supports the /v1/audio/speech endpoint. It runs the Kokoro TTS model via the kokoro-fastapi server with full RTX 5080 GPU acceleration. Access Type URL Notes Internal https://kokoro.home.local LAN access via Step-CA TLS No external route — internal service only. Configuration Image: ghcr.io/remsky/kokoro-fastapi-gpu:latest-cu128 Compose project: ai-stack Runtime: nvidia CUDA Version: 12.8 (image) / 12.9 (host driver, backward compatible) Ports Port Protocol Purpose 8880 TCP Kokoro FastAPI TTS API Traefik Labels traefik.enable: "true" traefik.docker.network: traefik-net traefik.http.routers.kokoro.rule: Host(`kokoro.home.local`) traefik.http.routers.kokoro.entrypoints: websecure traefik.http.routers.kokoro.tls.certresolver: step-ca traefik.http.services.kokoro.loadbalancer.server.port: 8880 GPU Acceleration runtime: nvidia environment: - NVIDIA_VISIBLE_DEVICES=all - DEVICE=gpu - USE_GPU=true The image tag latest-cu128 targets CUDA 12.8. The host driver (580.159.03) is fully forward-compatible with this image. Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/kokoro /app/data Voice models and cached data Networks Network Purpose ai-stack_ai-internal Internal access from Open Web UI and N8N traefik-net Exposes API via Traefik API Usage Kokoro exposes an OpenAI-compatible TTS endpoint: curl -X POST https://kokoro.home.local/v1/audio/speech \ -H "Content-Type: application/json" \ -d '{ "model": "kokoro", "input": "Hello from the homelab.", "voice": "af_sky", "response_format": "mp3" }' --output speech.mp3 Available voices and model details are listed at https://kokoro.home.local/docs (Swagger UI). Dependencies NVIDIA container runtime with RTX 5080 access Notes / Gotchas The latest-cu128 tag pulls the latest build compiled against CUDA 12.8. NVIDIA driver 580.x supports CUDA 12.9 on the host, which is backward-compatible. Voice model files are cached in the /app/data bind mount on first use — initial synthesis requests may be slower while models are downloaded. Kokoro is OpenAI API-compatible, meaning Open Web UI can be configured to use it as its TTS provider via the admin settings. Last Updated: 2026-06-16 Riffado (OpenPlaud) Overview Riffado (formerly known as OpenPlaud) is a self-hosted AI audio and podcast management application. It handles audio file storage, playback, and AI-assisted processing within the homelab ecosystem. Audio files are stored on the Ceph OSD volume ( 1TB_Vol1) rather than the system NVMe, keeping large media off the primary drive. Access Type URL Notes Internal https://riffado.home.local LAN access via Step-CA TLS External https://riffado.jeeves5454.ddns.net Internet-facing — Authentik SSO + GeoBlock + CrowdSec Containers in This Stack Container Image Role riffado ghcr.io/riffado/riffado:latest Application server riffado-db postgres:16-alpine PostgreSQL database backend Configuration Compose project: ai-stack Environment Variables Variable Value / Notes APP_URL https://riffado.jeeves5454.ddns.net HOSTNAME 0.0.0.0 DATABASE_URL postgresql://postgres:**REDACTED**@riffado-db:5432/riffado DEFAULT_STORAGE_TYPE local LOCAL_STORAGE_PATH /app/storage DISABLE_REGISTRATION true BETTER_AUTH_SECRET REDACTED ENCRYPTION_KEY REDACTED NODE_ENV production DISABLE_REGISTRATION=true prevents new accounts from being created — access is limited to pre-provisioned users and gated by Authentik on the external route. Traefik Labels # External route traefik.http.routers.riffado-ext.rule: Host(`riffado.jeeves5454.ddns.net`) traefik.http.routers.riffado-ext.entrypoints: websecure traefik.http.routers.riffado-ext.tls.certresolver: letsencrypt traefik.http.routers.riffado-ext.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.riffado-ext.service: riffado-svc # Internal route traefik.http.routers.riffado-int.rule: Host(`riffado.home.local`) traefik.http.routers.riffado-int.entrypoints: websecure traefik.http.routers.riffado-int.tls.certresolver: step-ca traefik.http.routers.riffado-int.service: riffado-svc traefik.http.services.riffado-svc.loadbalancer.server.port: 3000 Volumes / Bind Mounts Host Path Container Path Purpose /media/jeeves/1TB_Vol1/docker/riffado/audio /app/storage Audio file storage (Ceph OSD) Audio files are stored on the Ceph-managed OSD volume ( nvme2n1, mounted at /media/jeeves/1TB_Vol1). This keeps large audio files off the system NVMe and on the dedicated storage volume. Sub-section: PostgreSQL Database riffado-db is a dedicated Postgres 16-alpine sidecar managing all Riffado application state: user accounts, playlists, audio metadata, and processing history. It is not shared with any other service. Host Path / Volume Container Path Purpose /home/jeeves/docker/riffado/db /var/lib/postgresql/data PostgreSQL data files The database container is on the riffado-internal network only — it is never exposed to traefik-net. Networks Network Purpose traefik-net Exposes the Riffado web UI riffado-internal riffado ↔ riffado-db communication Dependencies riffado-db (must be healthy before riffado starts) Authentik for SSO on external route Ceph OSD volume must be mounted at /media/jeeves/1TB_Vol1 before the container starts, otherwise the audio storage path is unavailable Notes / Gotchas If the Ceph OSD volume is unmounted or degraded, audio file operations will fail even though the container itself runs normally. APP_URL must match the externally accessible URL. Changing this after initial setup requires updating any stored links. The BETTER_AUTH_SECRET and ENCRYPTION_KEY must remain constant — changing them invalidates all existing sessions and encrypted data. Last Updated: 2026-06-16 Paperless-AI Overview PaperlessAI is an AI companion service for Paperless-NGX that automates document classification. It monitors the Paperless document inbox and uses a local Ollama model to automatically assign tags, document types, correspondents, and titles to newly ingested documents — eliminating the need for manual categorisation. It communicates with Ollama over the internal ai-internal network and with Paperless-NGX via its API over the external HTTPS URL. Access Type URL Notes Internal https://paperlessai.home.local Authentik SSO required External https://paperlessai.jeeves5454.ddns.net Authentik SSO + GeoBlock + CrowdSec Configuration Image: clusterzx/paperless-ai:latest Compose project: Paperless stack (managed via Portainer, separate from ai-stack) Environment Variables Variable Value / Notes LLM_PROVIDER ollama OLLAMA_URL http://ollama.home.local:11434 OLLAMA_MODEL gemma4:12b PAPERLESS_URL https://paperless.jeeves5454.ddns.net PAPERLESS_API_KEY REDACTED AUTO_MODE true — processes documents automatically AUTO_TAG ai-processed — applied to every AI-classified document NODE_ENV production AUTO_MODE=true means PaperlessAI polls the Paperless inbox on a schedule and processes new documents without any manual trigger. The ai-processed tag is applied to documents after classification, allowing easy filtering in Paperless. Traefik Labels # External route traefik.http.routers.paperlessai-ext.rule: Host(`paperlessai.jeeves5454.ddns.net`) traefik.http.routers.paperlessai-ext.entrypoints: websecure traefik.http.routers.paperlessai-ext.tls.certresolver: letsencrypt traefik.http.routers.paperlessai-ext.middlewares: plex-geoblock@file,crowdsec-bouncer@file,authentik-auth@docker traefik.http.routers.paperlessai-ext.service: paperlessai-svc # Internal route traefik.http.routers.paperlessai-int.rule: Host(`paperlessai.home.local`) traefik.http.routers.paperlessai-int.entrypoints: websecure traefik.http.routers.paperlessai-int.tls.certresolver: step-ca traefik.http.routers.paperlessai-int.middlewares: authentik-auth@docker traefik.http.routers.paperlessai-int.service: paperlessai-svc traefik.http.services.paperlessai-svc.loadbalancer.server.port: 3000 Note that Authentik middleware is applied on both internal and external routes. Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/paperless/paperless-ai-data /app/data PaperlessAI configuration and state Networks PaperlessAI must be on the same network as Ollama to use http://ollama.home.local:11434. It reaches Paperless-NGX via its external HTTPS URL (outbound through Traefik). Dependencies Ollama — must be running with the gemma4:12b model loaded for classification to work. Pull the model first: docker exec -it ollama ollama pull gemma4:12b Paperless-NGX — must be accessible at https://paperless.jeeves5454.ddns.net with a valid API key Authentik for SSO on both routes Notes / Gotchas If Ollama is down or the model isn't loaded, documents will queue and not be processed until Ollama is available again. The AUTO_TAG ( ai-processed) helps track which documents were classified by AI vs manually. Review newly tagged documents periodically to validate AI accuracy. gemma4:12b (12 billion parameters) fits comfortably within the RTX 5080's 16 GB VRAM. If Ollama is under pressure from simultaneous inference requests, PaperlessAI classification may be slower. PaperlessAI connects to Paperless via the external URL ( https://paperless.jeeves5454.ddns.net). Ensure this domain remains reachable from within the container network. Last Updated: 2026-06-16 n8n Overview N8N is the workflow automation platform for the homelab. It connects services together through visual, node-based workflows — handling tasks such as document pipeline automation, audio transcription orchestration (Minuspod), API integrations, scheduled jobs, and webhook-triggered processing. N8N is the glue layer that ties together Whisper, Ollama, Paperless, and external APIs into end-to-end automated pipelines. Access Type URL Notes Internal https://n8n.home.local LAN access via Step-CA TLS — basic auth No external (internet-facing) Traefik route. Remote access via Tailscale. Webhooks are received at https://n8n.home.local/webhook/... — internal and Tailscale-reachable only. Configuration Image: n8nio/n8n:latest Compose project: Standalone (managed via Portainer) Ports Port Protocol Purpose 5678 TCP N8N web UI and API (host-bound) Traefik Labels traefik.enable: "true" traefik.docker.network: traefik-net traefik.http.routers.n8n.rule: Host(`n8n.home.local`) traefik.http.routers.n8n.entrypoints: websecure traefik.http.routers.n8n.tls.certresolver: step-ca traefik.http.services.n8n.loadbalancer.server.port: 5678 Internal-only route. Environment Variables Variable Value / Notes N8N_HOST n8n.home.local N8N_PROTOCOL https N8N_PORT 5678 N8N_EDITOR_BASE_URL https://n8n.home.local WEBHOOK_URL https://n8n.home.local N8N_BASIC_AUTH_ACTIVE true N8N_BASIC_AUTH_USER jeeves N8N_BASIC_AUTH_PASSWORD REDACTED N8N_ENCRYPTION_KEY REDACTED — encrypts stored credentials N8N_PAYLOAD_SIZE_MAX 16 (MB) EXECUTIONS_PROCESS main GENERIC_TIMEZONE America/Toronto N8N_ENCRYPTION_KEY encrypts all stored credentials (API keys, passwords) in the N8N database. This key must remain constant — changing it invalidates all stored credentials and they must be re-entered. Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/n8n/data /home/node/.n8n Workflows, credentials, execution history All workflow definitions, credentials, and execution logs are stored in this bind-mounted directory. Back up before upgrades. Networks Network Purpose traefik-net Exposes N8N UI and webhook endpoint N8N calls other services by hostname (e.g. http://ollama.home.local:11434, https://whisper.home.local) — it must be on traefik-net or have DNS resolution for these names. Dependencies AdGuard Home for *.home.local DNS resolution (N8N calls other services by name) Step-CA / Traefik for the n8n.home.local route Services called by workflows: Ollama, Faster-Whisper, Paperless-NGX, external APIs Notes / Gotchas N8N stores all credentials encrypted with N8N_ENCRYPTION_KEY. If this key is lost or rotated, all stored API keys and passwords must be re-entered manually. EXECUTIONS_PROCESS=main runs workflow executions in the main process. For high concurrency, consider EXECUTIONS_PROCESS=own with worker processes, but this requires additional setup. Webhook URLs are internal only ( https://n8n.home.local/webhook/...). External webhooks (e.g. from GitHub, Stripe) will not reach N8N unless a Tailscale exit node or Traefik external route is configured. N8N_PAYLOAD_SIZE_MAX=16 limits incoming webhook payload size to 16 MB. Increase this if processing large audio or document payloads via webhook. The latest tag may introduce breaking changes between workflow schema versions. Pin to a specific N8N version before any significant workflow investments. Last Updated: 2026-06-16 Open Notebook Overview Open Notebook is a self-hosted AI research notebook application — a local alternative to Google NotebookLM. It allows structured AI-assisted research sessions where sources (documents, URLs, text) are ingested into a notebook and queried via an LLM for synthesis, summaries, and question-answering. It uses SurrealDB as its backend database for storing notebooks, sources, and conversation state. Access Type URL Notes Internal https://notebook.home.local LAN access via Step-CA TLS External https://notebook.jeeves5454.ddns.net Authentik SSO + GeoBlock + CrowdSec Containers in This Stack Container Image Role open-notebook lfnovo/open_notebook:v1-latest Application server (Streamlit) open-notebook-db surrealdb/surrealdb:v2 SurrealDB database backend Configuration Compose project: ai-stack Environment Variables Variable Value / Notes API_URL https://notebook.home.local CORS_ORIGINS https://notebook.home.local,https://notebook.jeeves5454.ddns.net INTERNAL_API_URL http://localhost:5055 SURREAL_URL ws://open-notebook-db:8000/rpc SURREAL_NAMESPACE open_notebook SURREAL_DATABASE open_notebook SURREAL_USER root SURREAL_PASSWORD REDACTED HOSTNAME 0.0.0.0 Traefik Labels # External route traefik.http.routers.open-notebook-ext.rule: Host(`notebook.jeeves5454.ddns.net`) traefik.http.routers.open-notebook-ext.entrypoints: websecure traefik.http.routers.open-notebook-ext.tls.certresolver: letsencrypt traefik.http.routers.open-notebook-ext.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.open-notebook-ext.service: open-notebook-ui-svc traefik.http.routers.open-notebook-ext.priority: 1 # Internal route traefik.http.routers.open-notebook-int.rule: Host(`notebook.home.local`) traefik.http.routers.open-notebook-int.entrypoints: websecure traefik.http.routers.open-notebook-int.tls.certresolver: step-ca traefik.http.routers.open-notebook-int.service: open-notebook-ui-svc traefik.http.routers.open-notebook-int.priority: 1 traefik.http.services.open-notebook-ui-svc.loadbalancer.server.port: 8502 Volumes / Bind Mounts Host Path Container Path Purpose /media/jeeves/1TB_Vol1/docker/open-notebook/notebook_data /app/data Notebook content and uploads (Ceph OSD) /home/jeeves/docker/open-notebook/surreal_data /mydata SurrealDB data files Notebook data (uploaded sources, generated content) is stored on the Ceph OSD volume ( 1TB_Vol1) to keep large research files off the system NVMe. Sub-section: SurrealDB open-notebook-db runs SurrealDB v2, a multi-model database used by Open Notebook to store all notebook definitions, source metadata, embeddings, and conversation history. SurrealDB is connected to open-notebook via WebSocket at ws://open-notebook-db:8000/rpc over the ai-internal network. It is not exposed to traefik-net — no external access. Networks Network Purpose ai-stack_ai-internal open-notebook ↔ open-notebook-db communication traefik-net Exposes the Open Notebook UI via Traefik open-notebook-db is on ai-internal only and never on traefik-net. Dependencies open-notebook-db (SurrealDB must be running before the app starts) Ollama or an external LLM API configured within the app settings Authentik for SSO on the external route Ceph OSD volume mounted at /media/jeeves/1TB_Vol1 Notes / Gotchas The v1-latest image tag tracks the latest v1 stable release. Breaking changes between major versions may require a database migration. SurrealDB stores its data in the bind-mounted surreal_data directory. This must be backed up before upgrades to SurrealDB v3+ as schema changes are not automatically reversible. LLM provider settings (which Ollama model to use, API keys for external providers) are configured inside the Open Notebook UI, not via environment variables. CORS_ORIGINS must include both the internal and external URLs — missing one will cause browser CORS errors for the corresponding route. Last Updated: 2026-06-16 09-mcp-github.md kstack: book: Centerpoint Home Lab chapter: AI & Automation page: MCP GitHub Server tags: [mcp, github, oauth2, ai, claude] Overview The MCP GitHub Server exposes GitHub's Model Context Protocol (MCP) server over HTTPS with OAuth 2.1 authentication, making it accessible to Claude.ai and other MCP-compatible AI clients from anywhere on the internet. The stack consists of two containers: mcp-github-proxy — a custom Node.js OAuth 2.1 proxy (locally built image) that validates JWTs from Authentik before forwarding requests to the MCP server. github-mcp-server — the official GitHub MCP server running in HTTP mode, internal-only, with access scoped to repos and issues. Access Type URL Notes Internal https://mcp-github.home.local LAN access via Step-CA TLS External https://mcp-github.jeeves5454.ddns.net OAuth 2.1 authenticated — no Authentik ForwardAuth The external route uses OAuth 2.1 (not Authentik ForwardAuth) as the auth layer. GeoBlock and CrowdSec are still applied. The OAuth 2.1 issuer is Authentik. Containers in This Stack Container Image Role mcp-github-proxy mcp-github-proxy:latest (local build) OAuth 2.1 proxy + MCP request forwarder github-mcp-server ghcr.io/github/github-mcp-server:latest GitHub MCP server (HTTP mode, internal) Configuration Compose project: mcp-github Compose file: /home/jeeves/docker/mcp-github/proxy/docker-compose.yml mcp-github-proxy Environment Variables Variable Value / Notes PORT 3000 GITHUB_MCP_URL http://github-mcp-server:8080 GITHUB_PERSONAL_ACCESS_TOKEN REDACTED — PAT with read-only scopes (Contents, Issues, Metadata) AUTHENTIK_ISSUER https://auth.jeevesconsults.ca/application/o/mcp-github/ AUTHENTIK_JWKS_URL https://auth.jeevesconsults.ca/application/o/mcp-github/jwks/ SERVER_URL https://mcp-github.jeeves5454.ddns.net github-mcp-server Environment Variables Variable Value / Notes GITHUB_PERSONAL_ACCESS_TOKEN REDACTED — same read-only PAT as proxy GITHUB_TOOLSETS repos,issues — restricts available tools GITHUB_READ_ONLY 1 — belt-and-suspenders read-only flag Security note: GITHUB_READ_ONLY=1 has a known bug in HTTP mode (github/github-mcp-server #2156) — toolset flags may not fully enforce read-only behaviour. Primary read-only enforcement is the PAT scopes themselves (Contents/Issues/Metadata: read only). Traefik Labels # External route — GeoBlock + CrowdSec only (OAuth 2.1 handles auth) traefik.http.routers.mcp-github-ext.rule: Host(`mcp-github.jeeves5454.ddns.net`) traefik.http.routers.mcp-github-ext.entrypoints: websecure traefik.http.routers.mcp-github-ext.tls.certresolver: letsencrypt traefik.http.routers.mcp-github-ext.middlewares: plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.mcp-github-ext.service: mcp-github-proxy-svc # Internal route traefik.http.routers.mcp-github-int.rule: Host(`mcp-github.home.local`) traefik.http.routers.mcp-github-int.entrypoints: websecure traefik.http.routers.mcp-github-int.tls.certresolver: step-ca traefik.http.routers.mcp-github-int.service: mcp-github-proxy-svc traefik.http.services.mcp-github-proxy-svc.loadbalancer.server.port: 3000 Note: Authentik ForwardAuth ( authentik-auth@docker) is not used here. OAuth 2.1 is the authentication mechanism — the proxy validates the Authorization header JWT against Authentik's JWKS endpoint directly. Sub-section: GitHub MCP Server github-mcp-server runs the official GitHub MCP server in HTTP mode on port 8080, internal-only. It is never directly exposed to Traefik or the host — all requests arrive through the mcp-github-proxy over the mcp-github-internal network. The server is restricted to repos and issues toolsets, providing read-only access to repository content, metadata, and issues. Write operations are not available via the PAT scopes. Proxy Image Build The mcp-github-proxy image is built locally on Centerpoint before deployment: cd /home/jeeves/docker/mcp-github/proxy docker build -t mcp-github-proxy:latest . Source files: /home/jeeves/docker/mcp-github/proxy/{Dockerfile,package.json,src/} Volumes / Bind Mounts No persistent volumes required. Both containers are stateless — the proxy holds no state, and the MCP server reads from GitHub's API on every request. Networks Network Purpose traefik-net Exposes mcp-github-proxy via Traefik mcp-github_mcp-github-internal mcp-github-proxy ↔ github-mcp-server communication github-mcp-server is on the internal network only — never on traefik-net. Healthchecks Both containers have healthchecks defined: healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:/health"] interval: 30s timeout: 5s retries: 3 Proxy healthcheck: http://localhost:3000/health MCP server healthcheck: http://localhost:8080/health Authentik OAuth 2.1 Provider Setup In Authentik, an OAuth2/OIDC provider is configured for MCP GitHub with: Setting Value Redirect URI https://claude.ai/api/mcp/auth_callback Issuer https://auth.jeevesconsults.ca/application/o/mcp-github/ JWKS URL /jwks/ Dependencies Authentik ( auth.jeevesconsults.ca) — proxy validates JWTs against Authentik JWKS GitHub API (internet access required for the MCP server to function) Traefik on traefik-net for both routes Notes / Gotchas The GitHub PAT must be rotated before expiry. If it expires, all GitHub MCP tool calls will fail with 401 errors. Update in both container environment variables. The proxy image must be rebuilt after any source code changes: cd /home/jeeves/docker/mcp-github/proxy docker build -t mcp-github-proxy:latest . docker compose up -d --force-recreate mcp-github-proxy Claude.ai connects to this server via the external URL. The redirect URI ( https://claude.ai/api/mcp/auth_callback) must be registered in the Authentik OAuth2 provider — adding any other redirect URI will cause the OAuth flow to fail. GeoBlock allows CA, US, and IN. Claude.ai's servers may originate from other regions — if MCP calls fail, check Traefik logs for GeoBlock rejections and adjust the country allowlist accordingly. Last Updated: 2026-06-16 h04-media-entertainment 00-chapter-intro.md kstack: book: Centerpoint Home Lab chapter: Media & Entertainment page: Chapter Introduction tags: [media, plex, jellyfin, immich, audiobookshelf, stash] Overview This chapter covers all media consumption and photo management services on Centerpoint. Media files themselves are not stored locally — they live on the UnRAID NAS ( 192.168.1.119) and are bind-mounted into containers from the NFS shares at /mnt/Multimedia and /mnt/Photos. Shared Storage NFS Mount Contents Consumers /mnt/Multimedia Movies, TV, adult content, audiobooks, podcasts Plex, Jellyfin, Emby, Audiobookshelf, Stash /mnt/Photos Photo library, Google Photos imports Immich Services in This Chapter Service Container(s) Status Purpose Plex plex Active Primary media server Jellyfin jellyfin Active Open-source media server (secondary) Emby emby Offline Legacy media server — not running Tautulli tautulli Active Plex analytics and monitoring Immich immich_server, immich_machine_learning, immich_postgres, immich_redis Active Photo & video library Audiobookshelf audiobookshelf Active Audiobooks and podcasts Seerr seerr Active Media request management Stash stash, stash-vr Active Adult media library + VR frontend Threadfin threadfin Active IPTV M3U proxy for Plex/Jellyfin Last Updated: 2026-06-16 01-plex.md kstack: book: Centerpoint Home Lab chapter: Media & Entertainment page: Plex tags: [plex, media, streaming] Overview Plex Media Server is the primary media server for the homelab, serving movies, TV shows, and other content to Plex clients on the local network and remotely. It uses Plex's own account-based authentication — no Authentik ForwardAuth is applied. GeoBlock is active on the external route. A second Plex instance running on another device ( 192.168.1.186) is routed via Traefik's file provider as plex2.jeeves5454.ddns.net. Access Type URL Notes External https://plex.jeeves5454.ddns.net GeoBlock (CA/US/IN), no Authentik — Plex account auth Direct http://192.168.1.85:32400 LAN direct access No *.home.local internal Traefik route — Plex is accessed externally or by direct IP on the LAN. Configuration Image: lscr.io/linuxserver/plex:latest Compose project: Standalone (managed via Portainer) Ports Port Protocol Purpose 32400 TCP Plex Media Server API and web UI Traefik Labels traefik.enable: "true" traefik.http.routers.plex.rule: Host(`plex.jeeves5454.ddns.net`) traefik.http.routers.plex.entrypoints: websecure traefik.http.routers.plex.tls.certresolver: letsencrypt traefik.http.routers.plex.middlewares: plex-geoblock@file,plex-headers traefik.http.middlewares.plex-headers.headers.customrequestheaders.X-Forwarded-Proto: https traefik.http.services.plex.loadbalancer.server.port: 32400 The plex-headers middleware injects X-Forwarded-Proto: https — required for Plex to generate correct redirect and callback URLs when behind a reverse proxy. Environment Variables Variable Value Purpose PUID 1000 Run as user ID 1000 PGID 1000 Run as group ID 1000 TZ America/Toronto Timezone VERSION docker Use the latest Plex from Docker Hub ADVERTISE_IP https://plex.jeeves5454.ddns.net:443 External URL Plex advertises to clients ADVERTISE_IP must match the externally reachable URL for remote streaming to work correctly when behind Traefik. Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/plex/config /config Plex database, metadata, settings /mnt/Multimedia /Multimedia All media files (NFS from UnRAID) /home/jeeves/docker /docker Utility bind (admin access) Networks Network Purpose traefik-net Exposes Plex via Traefik external route Dependencies NFS mount /mnt/Multimedia must be healthy — if UnRAID is down, Plex will show libraries as unavailable Internet connectivity for metadata scraping and Plex account authentication Notes / Gotchas Plex uses its own authentication (Plex.tv account). Authentik ForwardAuth is not applied to the external route because it would break the Plex app auth flow. VERSION=docker always pulls the latest Plex release on container start — pin to a specific build number (e.g. VERSION=1.41.x.xxxx-xxxxxxxxx) for stability. Plex transcoding temp files are written to /tmp inside the container. For heavy transcoding workloads, consider adding a bind mount for the transcode directory to a fast local path. Tautulli connects to Plex via the direct API at http://plex:32400 over the traefik-net bridge. Last Updated: 2026-06-16 02-jellyfin.md kstack: book: Centerpoint Home Lab chapter: Media & Entertainment page: Jellyfin tags: [jellyfin, media, streaming, open-source] Overview Jellyfin is a free and open-source media server used as a secondary streaming solution alongside Plex. It serves TV shows and movies from the same UnRAID NFS share and is accessible on the LAN only. Access Type URL Notes Internal https://jellyfin.home.local LAN access via Step-CA TLS Direct http://192.168.1.85:8096 LAN direct access No external (internet-facing) route. Configuration Image: jellyfin/jellyfin:latest Compose project: Standalone (managed via Portainer) Ports Port Protocol Purpose 8096 TCP Jellyfin web UI and API (host-bound) Traefik Labels traefik.enable: "true" traefik.http.routers.jellyfin.rule: Host(`jellyfin.home.local`) traefik.http.routers.jellyfin.entrypoints: websecure traefik.http.routers.jellyfin.tls: "true" traefik.http.routers.jellyfin.tls.certresolver: step-ca traefik.http.services.jellyfin.loadbalancer.server.port: 8096 Environment Variables Variable Value Purpose PUID 1000 User ID PGID 1000 Group ID TZ America/Toronto Timezone Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/jellyfin/config /config Jellyfin database and settings /home/jeeves/docker/jellyfin/cache /cache Thumbnail and transcode cache /mnt/Multimedia/TV /media/tv:ro TV series (NFS from UnRAID, read-only) /mnt/Multimedia/Movies /media/movies:ro Movies (NFS from UnRAID, read-only) Networks Network Purpose traefik-net Exposes Jellyfin via Traefik Dependencies NFS mount /mnt/Multimedia must be healthy Step-CA for jellyfin.home.local TLS certificate Notes / Gotchas Media mounts are read-only ( :ro) — Jellyfin cannot modify or delete source files. Jellyfin and Plex both mount the same NFS directories. Metadata and library scans are independent between the two servers. Hardware transcoding is available on Jellyfin but not explicitly configured — the jellyfin/jellyfin:latest image supports it via FFmpeg with Intel/NVIDIA hooks. Configure in Admin → Dashboard → Playback if needed. Port 8096 is also used by Emby (currently offline). Only one can be host-bound at a time — ensure Emby is stopped before starting Jellyfin and vice versa. Last Updated: 2026-06-16 03-emby.md kstack: book: Centerpoint Home Lab chapter: Media & Entertainment page: Emby (Offline) tags: [emby, media, offline] Overview Status: OFFLINE — The Emby container is present on Centerpoint but is not currently running. It has been superseded by Jellyfin for open-source media serving needs. The container and its configuration are retained. Emby was previously used as an alternative media server. Its configuration and data remain intact and the container can be restarted if needed, subject to the port 8096 conflict with Jellyfin (see Notes). Access Type URL Notes Internal https://emby.home.local Traefik labels present — not routed while offline Configuration Image: emby/embyserver:latest Status: exited (container stopped) Traefik Labels (inactive while container is stopped) traefik.enable: "true" traefik.http.routers.emby.rule: Host(`emby.home.local`) traefik.http.routers.emby.entrypoints: websecure traefik.http.routers.emby.tls.certresolver: step-ca traefik.http.services.emby.loadbalancer.server.port: 8096 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/emby/config /config Emby database and library config /mnt/Multimedia/ST /data Media files (same NFS as Stash) Notes / Gotchas Emby and Jellyfin both use port 8096 internally. Only one can be host-bound at a time — starting Emby while Jellyfin is running will fail with a port conflict. If reactivating Emby, stop Jellyfin first or remap Emby to a different host port. The emby/embyserver:latest image tag pulls the latest release — verify compatibility with the existing config before restarting after a long pause. Last Updated: 2026-06-16 04-tautulli.md kstack: book: Centerpoint Home Lab chapter: Media & Entertainment page: Tautulli tags: [tautulli, plex, monitoring, analytics] Overview Tautulli is the monitoring and analytics companion for Plex Media Server. It tracks play history, user activity, and library statistics, and can send notifications (email, Telegram, etc.) on media events such as new content additions, playback starts, or user logins. Access Type URL Notes Internal https://tautulli.home.local LAN access via Step-CA TLS No external route — LAN and Tailscale access only. Configuration Image: lscr.io/linuxserver/tautulli:latest Compose project: Standalone (managed via Portainer) Traefik Labels traefik.enable: "true" traefik.http.routers.tautulli.rule: Host(`tautulli.home.local`) traefik.http.routers.tautulli.entrypoints: websecure traefik.http.routers.tautulli.tls.certresolver: step-ca traefik.http.services.tautulli.loadbalancer.server.port: 8181 Environment Variables Variable Value Purpose PUID 1000 User ID PGID 1000 Group ID TZ America/Toronto Timezone Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/Tautulli/local_tautulli /config Tautulli database and configuration Networks Network Purpose traefik-net Exposes Tautulli UI; also reaches Plex container Dependencies Plex Media Server — Tautulli connects to the Plex API to retrieve playback data. Configure the Plex connection in Tautulli Settings → Plex Media Server using the container hostname plex and port 32400. Notes / Gotchas Tautulli's database ( tautulli.db) contains full play history. Back it up before upgrades. Notification agents (Telegram, email, etc.) and their credentials are stored in the Tautulli database. If the /config bind mount is lost, all notification integrations must be reconfigured. Last Updated: 2026-06-16 05-immich.md kstack: book: Centerpoint Home Lab chapter: Media & Entertainment page: Immich tags: [immich, photos, media, cuda, gpu, self-hosted] Overview Immich is the self-hosted photo and video library for the homelab — a local alternative to Google Photos. It provides automatic mobile backup, face recognition, smart search, and album management. The machine learning container runs on the RTX 5080 via CUDA for accelerated facial recognition and CLIP-based smart search. Photo storage lives on the UnRAID NAS NFS mount at /mnt/Photos. Access Type URL Notes Internal https://photos.home.local LAN access via Step-CA TLS External https://photos.jeevesconsults.ca GeoBlock (CA/US/IN) + CrowdSec — no Authentik ForwardAuth Immich uses its own user authentication — Authentik ForwardAuth is not applied because it would break the mobile app OAuth flow. Containers in This Stack Container Image GPU Role immich_server ghcr.io/immich-app/immich-server:release No Main API and web server immich_machine_learning ghcr.io/immich-app/immich-machine-learning:release-cuda Yes Face recognition + CLIP search immich_postgres ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 No PostgreSQL with pgvecto.rs immich_redis valkey/valkey:8-bookworm No Job queue and cache Configuration Compose file: /home/jeeves/docker/immich/docker-compose.yml Compose project: immich Ports Port Protocol Purpose 2283 TCP Immich web UI and API (host-bound) Traefik Labels # External route traefik.http.routers.immich.rule: Host(`photos.jeevesconsults.ca`) traefik.http.routers.immich.entrypoints: websecure traefik.http.routers.immich.tls.certresolver: letsencrypt traefik.http.routers.immich.middlewares: plex-geoblock@file,crowdsec-bouncer@file,immich-headers traefik.http.middlewares.immich-headers.headers.customrequestheaders.X-Forwarded-Proto: https # Internal route traefik.http.routers.immich-internal.rule: Host(`photos.home.local`) traefik.http.routers.immich-internal.entrypoints: websecure traefik.http.routers.immich-internal.tls.certresolver: step-ca traefik.http.services.immich.loadbalancer.server.port: 2283 Key Environment Variables ( .env file) Variable Value / Notes UPLOAD_LOCATION /mnt/Photos/immich-library DB_HOSTNAME immich_postgres DB_USERNAME postgres DB_PASSWORD REDACTED DB_DATABASE_NAME immich REDIS_HOSTNAME immich_redis TZ America/Toronto IMMICH_VERSION release (pinned to latest stable) Volumes / Bind Mounts Host Path / Volume Container Path Purpose /mnt/Photos/immich-library /data Primary upload library (NFS) /mnt/Photos/Plex /mnt/Photos/Plex:rw Plex photo library (external library) /mnt/Photos/Google_Photos /mnt/Photos/Google_Photos:rw Google Photos import folder /etc/localtime /etc/localtime:ro Host timezone sync model-cache (named volume) /cache ML model weight cache (machine learning container) ${DB_DATA_LOCATION} (from .env) /var/lib/postgresql/data PostgreSQL data Sub-section: Machine Learning (CUDA) immich_machine_learning runs with runtime: nvidia, giving it access to the RTX 5080 for: Face detection and recognition — identifies and clusters faces across the library CLIP embeddings — powers smart search ("photos of dogs at the beach") Environment Variable Value Purpose NVIDIA_VISIBLE_DEVICES all GPU access NVIDIA_DRIVER_CAPABILITIES compute,utility CUDA compute caps MACHINE_LEARNING_DEVICE_ID 0 Use GPU device 0 ML model files are cached in the model-cache named Docker volume. Models are downloaded from HuggingFace on first use and cached for subsequent runs. Sub-section: PostgreSQL (pgvecto.rs) Immich uses a custom PostgreSQL 14 image with the pgvecto.rs and pgvectors extensions pre-installed. These extensions power the vector similarity search that underlies CLIP smart search and face clustering. ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 The shm_size: 128mb allocation is required for PostgreSQL's shared memory. Sub-section: Redis / Valkey immich_redis uses Valkey (the Redis fork) as the job queue and cache backend. It handles background job scheduling for ML processing, thumbnail generation, and library scans. Dependencies NFS mounts /mnt/Photos (UnRAID) must be healthy immich_postgres → immich_redis → immich_server startup order NVIDIA container runtime for immich_machine_learning Notes / Gotchas Immich does not use Authentik ForwardAuth — it has built-in multi-user auth. The mobile app connects directly to https://photos.jeevesconsults.ca. The X-Forwarded-Proto: https header ( immich-headers middleware) is required for Immich to generate correct share links and OAuth callbacks. The PostgreSQL image is pinned to a specific digest — do not change it arbitrarily. Immich releases are tied to specific database schema versions. Library scan and ML job processing can be CPU/GPU intensive. Schedule large library scans during off-peak hours via Admin → Jobs. External libraries (Plex photos, Google Photos) are read-only imports. Changes to files in these directories are picked up on the next library scan. Last Updated: 2026-06-16 06-audiobookshelf.md kstack: book: Centerpoint Home Lab chapter: Media & Entertainment page: Audiobookshelf tags: [audiobookshelf, audiobooks, podcasts, media] Overview Audiobookshelf is a self-hosted audiobook and podcast server. It manages and streams audiobook and podcast content from the UnRAID NFS share, tracks listening progress across devices, and supports mobile apps for on-the-go listening. A notable configuration detail: the Step-CA root certificate is injected into the container's trust store so that Audiobookshelf can make HTTPS calls to internal *.home.local services (e.g. for metadata lookups or integrations). Access Type URL Notes External https://audio.jeeves5454.ddns.net GeoBlock (CA/US/IN) + CrowdSec — no Authentik No *.home.local internal Traefik route — accessible externally or via direct LAN IP. Audiobookshelf uses its own account-based authentication. Configuration Image: ghcr.io/advplyr/audiobookshelf:latest Compose project: Standalone (managed via Portainer) Traefik Labels traefik.enable: "true" traefik.http.routers.audio.rule: Host(`audio.jeeves5454.ddns.net`) traefik.http.routers.audio.entrypoints: websecure traefik.http.routers.audio.tls.certresolver: letsencrypt traefik.http.routers.audio.middlewares: plex-geoblock@file,crowdsec-bouncer@file,audio-headers traefik.http.middlewares.audio-headers.headers.customrequestheaders.X-Forwarded-Proto: https traefik.http.services.audio.loadbalancer.server.port: 80 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/audiobookshelf/config /config App database and settings /home/jeeves/docker/audiobookshelf/metadata /metadata Cover art and cached metadata /mnt/Multimedia/Audio/Audio_Books /audiobooks Audiobook files (NFS from UnRAID) /mnt/Multimedia/Audio/podcasts /podcasts Podcast episode files (NFS from UnRAID) /home/jeeves/docker/step-ca/config/certs/root_ca.crt /usr/local/share/ca-certificates/step-ca.crt:ro Step-CA root cert trust injection The Step-CA root certificate is bind-mounted into the container's CA trust directory, allowing Audiobookshelf to trust *.home.local TLS certificates when making outbound HTTPS requests to internal services. Networks Network Purpose traefik-net Exposes the Audiobookshelf UI Dependencies NFS mount /mnt/Multimedia/Audio must be healthy for content to be accessible Step-CA root certificate at /home/jeeves/docker/step-ca/config/certs/root_ca.crt must exist for the bind mount to succeed Notes / Gotchas Audiobookshelf has its own user authentication — no Authentik ForwardAuth is applied. Manage users in the Admin panel. The X-Forwarded-Proto: https middleware is required for Audiobookshelf to generate correct callback and share URLs when behind Traefik. Podcast episode downloads are stored in /mnt/Multimedia/Audio/podcasts. Ensure UnRAID has sufficient free space for automatic podcast downloads. If Step-CA root cert is updated (e.g. after CA renewal), the container must be restarted to pick up the new cert from the bind mount. Listening progress is stored in the SQLite database under /config. Back up this directory before upgrades to preserve progress data. Last Updated: 2026-06-16 07-seerr.md kstack: book: Centerpoint Home Lab chapter: Media & Entertainment page: Seerr tags: [seerr, overseerr, media-requests, plex, jellyfin] Overview Seerr is a media request management application — a maintained fork of Overseerr. It provides a user-friendly interface for requesting movies and TV shows, which are then forwarded to the *arr stack (Radarr, Sonarr) for automated download and delivery to Plex and Jellyfin. It also surfaces Plex availability status so users can see what is already in the library before requesting. Access Type URL Notes Internal https://seerr.home.local LAN access via Step-CA TLS External https://seerr.jeeves5454.ddns.net Authentik SSO + GeoBlock + CrowdSec Configuration Image: ghcr.io/seerr-team/seerr:latest Compose project: arr stack (managed via Portainer alongside the *arr services) Traefik Labels # External route traefik.http.routers.seerr-external.rule: Host(`seerr.jeeves5454.ddns.net`) traefik.http.routers.seerr-external.entrypoints: websecure traefik.http.routers.seerr-external.tls.certresolver: letsencrypt traefik.http.routers.seerr-external.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.seerr-external.service: seerr-svc # Internal route traefik.http.routers.seerr-internal.rule: Host(`seerr.home.local`) traefik.http.routers.seerr-internal.entrypoints: websecure traefik.http.routers.seerr-internal.tls.certresolver: step-ca traefik.http.routers.seerr-internal.service: seerr-svc traefik.http.services.seerr-svc.loadbalancer.server.port: 5055 Environment Variables Variable Value Purpose PORT 5055 Application port LOG_LEVEL debug Logging verbosity TZ America/Toronto Timezone NODE_ENV production Runtime environment Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/Seer/config /app/config Seerr database and configuration Networks Network Purpose traefik-net Exposes Seerr UI media-network Internal network shared with *arr stack services (Sonarr, Radarr, etc.) The media-network attachment allows Seerr to communicate directly with Sonarr, Radarr, and Plex by container hostname without going through Traefik. Dependencies Plex Media Server — for library availability checks (connect via API) Sonarr, Radarr — to forward TV and movie requests Authentik for SSO on the external route Notes / Gotchas Seerr is a fork of Overseerr — configuration and API are compatible but issue tracking and releases are at github.com/seerr-team/seerr. User permissions and notification settings are configured inside the Seerr UI. When Authentik is used for ForwardAuth, Seerr should be configured to use "Sign-in with Plex" or local accounts for user identity — not OIDC directly. Seerr's config database at /app/config contains API keys for Plex, Sonarr, and Radarr. Back it up before upgrades. Last Updated: 2026-06-16 09-threadfin.md kstack: book: Centerpoint Home Lab chapter: Media & Entertainment page: Threadfin tags: [threadfin, iptv, m3u, xmltv, plex, jellyfin] Overview Threadfin is an M3U proxy and IPTV middleware that translates IPTV streams into a format consumable by Plex DVR and Jellyfin Live TV. It manages M3U playlists, XMLTV guide data, and presents a virtual tuner device to media servers via HDHR (HDHomeRun) emulation. Access Type URL / Endpoint Notes Web UI http://192.168.1.85:34400 Direct LAN access — no Traefik route HDHR http://192.168.1.85:34400 HDHomeRun device emulation endpoint No Traefik route is configured for Threadfin — Plex and Jellyfin connect to it via direct IP and port on the LAN. Configuration Image: fyb3roptik/threadfin:latest Compose project: Standalone (managed via Portainer) Ports Port Protocol Purpose 34400 TCP Threadfin web UI and HDHR endpoint (host-bound) Environment Variables Variable Value Purpose TZ America/Toronto Timezone THREADFIN_PORT 34400 Listen port THREADFIN_BRANCH main Update channel THREADFIN_BIND_IP_ADDRESS 0.0.0.0 Bind to all interfaces THREADFIN_DEBUG 0 Debug logging off Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/threadfin/config /home/threadfin/conf M3U playlists, XMLTV data, settings Networks Network Purpose traefik-net Present (container is on traefik-net but no labels) Threadfin is on traefik-net for network reachability but has no Traefik labels — it is accessed by direct IP. Plex / Jellyfin Integration Plex: In Plex Settings → Live TV & DVR → Set Up Plex Tuner Enter the Threadfin HDHR URL: http://192.168.1.85:34400/device.xml Plex discovers the virtual tuner and available channels Jellyfin: Admin Dashboard → Live TV → Add Tuner Device Select HDHomeRun and enter http://192.168.1.85:34400 Dependencies M3U source URL (IPTV provider) configured in Threadfin UI XMLTV guide source configured in Threadfin UI Plex or Jellyfin must be able to reach 192.168.1.85:34400 on the LAN Notes / Gotchas M3U playlists and XMLTV guide data are refreshed on a schedule configured in the Threadfin UI. If the IPTV provider URL changes, update it in Threadfin settings — Plex and Jellyfin do not need to be reconfigured. Threadfin does not store or proxy video streams itself; it redirects the media server directly to the IPTV stream URL. Buffering and quality are determined by the stream source. Port 34400 is non-standard. Ensure no firewall rules block LAN-to-LAN traffic on this port between Plex/Jellyfin and Centerpoint. Last Updated: 2026-06-16 h05-media-management 00-chapter-intro.md kstack: book: Centerpoint Home Lab chapter: Media Management page: Chapter Introduction tags: [media-management, arr, sonarr, radarr, prowlarr, nzbget] Overview This chapter documents the automated media acquisition and management stack — commonly referred to as the *arr ecosystem. These services work together to monitor, search, download, rename, and organise media content automatically, then deliver it to Plex and Jellyfin. Download Pipeline Seerr (request) ──► Sonarr / Radarr / Whisparr / LazyLibrarian │ ▼ Prowlarr (indexer search) │ ▼ NZBGet (Usenet download) │ ▼ /media/jeeves/1TB_Vol2/downloads (Ceph OSD — temp) │ Post-processing │ ▼ /mnt/Multimedia (NFS → UnRAID — permanent) │ Plex / Jellyfin Downloads land temporarily on the Ceph OSD volume ( nvme2n1, mounted at /media/jeeves/1TB_Vol2) for high-speed write performance. After post-processing, completed media is moved/hardlinked to the UnRAID NFS share at /mnt/Multimedia for permanent storage. Shared Networks All services in this chapter are on two Docker networks: Network Purpose traefik-net Web UI access via Traefik media-network Internal service-to-service communication (no Traefik hop) media-network allows *arr services to reach each other and NZBGet directly by container hostname without going through Traefik. Services in This Chapter Service Container Status Purpose Sonarr sonarr Active TV show monitoring and management Radarr radarr Active Movie monitoring and management Prowlarr prowlarr Active Indexer aggregator for all *arr apps Bazarr bazarr Active Subtitle management NZBGet nzbget Active Usenet download client Whisparr whisparr Active Adult content management (Sonarr fork) LazyLibrarian lazylibrarian Active Book and magazine management Mylar3 mylar3 Active Comics management Audiobookrequest audiobookrequest Active Audiobook request portal Profilarr profilarr Active Quality profile manager for *arr apps Dispatcharr dispatcharr Active IPTV channel and stream dispatcher Pulsarr pulsarr Active Watchlist automation and notifications FlareSolverr flaresolverr Offline Cloudflare bypass proxy for Prowlarr Last Updated: 2026-06-16 01-sonarr.md kstack: book: Centerpoint Home Lab chapter: Media Management page: Sonarr tags: [sonarr, tv, arr, media-management] Overview Sonarr monitors RSS feeds and indexers (via Prowlarr) for new TV show episodes, automatically searches for and downloads them via NZBGet, then renames and organises the files into the media library on UnRAID. Access Type URL Notes Internal https://sonarr.home.local LAN access via Step-CA TLS External https://sonarr.jeeves5454.ddns.net Authentik SSO + GeoBlock + CrowdSec Configuration Image: lscr.io/linuxserver/sonarr:latest Compose project: arr stack (managed via Portainer) Traefik Labels # External route traefik.http.routers.sonarr-external.rule: Host(`sonarr.jeeves5454.ddns.net`) traefik.http.routers.sonarr-external.entrypoints: websecure traefik.http.routers.sonarr-external.tls.certresolver: letsencrypt traefik.http.routers.sonarr-external.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.sonarr-external.service: sonarr-svc # Internal route traefik.http.routers.sonarr-internal.rule: Host(`sonarr.home.local`) traefik.http.routers.sonarr-internal.entrypoints: websecure traefik.http.routers.sonarr-internal.tls.certresolver: step-ca traefik.http.routers.sonarr-internal.service: sonarr-svc traefik.http.services.sonarr-svc.loadbalancer.server.port: 8989 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/Sonarr/config /config Sonarr database, settings, and series index /media/jeeves/1TB_Vol2/downloads /downloads NZBGet download staging (Ceph OSD) /mnt/Multimedia /media Final media library (NFS from UnRAID) Both /downloads and /media must be in the same container path namespace so Sonarr can hardlink completed downloads rather than copy them — essential for atomic moves between the download staging area and the media library. Integration Points Service Connection Method Purpose Prowlarr API ( http://prowlarr:9696) via media-network Indexer search NZBGet API ( http://nzbget:6789) via media-network Download client Bazarr API (Bazarr polls Sonarr) Subtitle fetching Pulsarr Sonarr webhook / API Watchlist sync Seerr API Receives show requests Notes / Gotchas Sonarr's root folder for TV series must point to /media/TV (or equivalent) inside the container, which maps to /mnt/Multimedia/TV on the host via NFS. Quality profiles are managed centrally via Profilarr and pushed to Sonarr via its API — do not manually edit quality profiles in Sonarr if Profilarr is active. API key is stored in /config/config.xml — required to configure Prowlarr, Bazarr, Seerr, and Pulsarr integrations. Last Updated: 2026-06-16 02-radarr.md kstack: book: Centerpoint Home Lab chapter: Media Management page: Radarr tags: [radarr, movies, arr, media-management] Overview Radarr is the movie counterpart to Sonarr. It monitors for new and existing movie releases, searches indexers via Prowlarr, downloads via NZBGet, and moves completed movies into the media library on UnRAID. Access Type URL Notes Internal https://radarr.home.local LAN access via Step-CA TLS External https://radarr.jeeves5454.ddns.net Authentik SSO + GeoBlock + CrowdSec Configuration Image: lscr.io/linuxserver/radarr:latest Compose project: arr stack Traefik Labels # External route traefik.http.routers.radarr-external.rule: Host(`radarr.jeeves5454.ddns.net`) traefik.http.routers.radarr-external.entrypoints: websecure traefik.http.routers.radarr-external.tls.certresolver: letsencrypt traefik.http.routers.radarr-external.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.radarr-external.service: radarr-svc # Internal route traefik.http.routers.radarr-internal.rule: Host(`radarr.home.local`) traefik.http.routers.radarr-internal.entrypoints: websecure traefik.http.routers.radarr-internal.tls.certresolver: step-ca traefik.http.routers.radarr-internal.service: radarr-svc traefik.http.services.radarr-svc.loadbalancer.server.port: 7878 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/Radarr/config /config Radarr database and settings /media/jeeves/1TB_Vol2/downloads /downloads NZBGet download staging (Ceph OSD) /mnt/Multimedia /media Final media library (NFS from UnRAID) Integration Points Service Connection Method Purpose Prowlarr API ( http://prowlarr:9696) via media-network Indexer search NZBGet API ( http://nzbget:6789) via media-network Download client Bazarr API (Bazarr polls Radarr) Subtitle fetching Pulsarr Radarr webhook / API Watchlist sync Seerr API Receives movie requests Notes / Gotchas Radarr root folder must be set to the path inside the container that maps to /mnt/Multimedia/Movies on the host. Hardlinking between /downloads and /media requires both paths to be on the same filesystem. The current setup has downloads on Ceph OSD and the final library on NFS — hardlinking is not possible across these. Radarr performs a copy+delete instead. This is slower but correct. Quality profiles are managed via Profilarr — avoid manual edits in Radarr UI. Last Updated: 2026-06-16 03-prowlarr.md kstack: book: Centerpoint Home Lab chapter: Media Management page: Prowlarr tags: [prowlarr, indexer, arr, media-management] Overview Prowlarr is the indexer aggregation layer for the *arr stack. It manages all Usenet and torrent indexer connections in one place and syncs them automatically to Sonarr, Radarr, Whisparr, and LazyLibrarian — eliminating the need to configure indexers individually in each application. Access Type URL Notes Internal https://prowlarr.home.local LAN access via Step-CA TLS No external route — internal management only. Configuration Image: lscr.io/linuxserver/prowlarr:latest Compose project: arr stack Traefik Labels traefik.enable: "true" traefik.http.routers.prowlarr.rule: Host(`prowlarr.home.local`) traefik.http.routers.prowlarr.entrypoints: websecure traefik.http.routers.prowlarr.tls.certresolver: step-ca traefik.http.services.prowlarr.loadbalancer.server.port: 9696 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/Prowlarr/config /config Indexer definitions and settings Integration Points Prowlarr pushes its configured indexers to downstream *arr apps via their APIs. Each app is added in Prowlarr Settings → Apps: App API Endpoint ( media-network) Sonarr http://sonarr:8989 Radarr http://radarr:7878 Whisparr http://whisparr:6969 LazyLibrarian http://lazylibrarian:5299 When an indexer is added or updated in Prowlarr, it is automatically propagated to all connected apps. No manual indexer configuration in individual *arr apps is needed. Notes / Gotchas FlareSolverr (currently offline) is used by Prowlarr to bypass Cloudflare-protected indexers. When FlareSolverr is down, any Cloudflare-protected indexer will fail. Configure the FlareSolverr proxy URL in Prowlarr Settings → Indexers → Proxies. Prowlarr's API key is required when adding Prowlarr as the indexer source in each *arr application. Find it in Prowlarr Settings → General → Security. Indexer categories must be correctly mapped in Prowlarr for each app (e.g. TV categories for Sonarr, movie categories for Radarr) — misconfigured categories result in wrong content being returned in searches. Last Updated: 2026-06-16 04-bazarr.md kstack: book: Centerpoint Home Lab chapter: Media Management page: Bazarr tags: [bazarr, subtitles, arr, media-management] Overview Bazarr automatically downloads subtitles for movies and TV shows managed by Radarr and Sonarr. It monitors both libraries and fetches subtitles from configured providers (OpenSubtitles, Subscene, etc.) whenever a new file is added or an existing file is missing subtitles. Access Type URL Notes Internal https://bazarr.home.local LAN access via Step-CA TLS No external route — internal management only. Configuration Image: lscr.io/linuxserver/bazarr:latest Compose project: arr stack Traefik Labels traefik.enable: "true" traefik.http.routers.bazarr.rule: Host(`bazarr.home.local`) traefik.http.routers.bazarr.entrypoints: websecure traefik.http.routers.bazarr.tls.certresolver: step-ca traefik.http.services.bazarr.loadbalancer.server.port: 6767 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/Bazarr/config /config Bazarr database and settings /mnt/Multimedia /media Media library for subtitle placement (NFS from UnRAID) The /media mount mirrors the Sonarr/Radarr mount so Bazarr can write subtitle files directly alongside the video files in the library. Integration Points Service Connection Purpose Sonarr API ( http://sonarr:8989) via media-network TV library sync Radarr API ( http://radarr:7878) via media-network Movie library sync Notes / Gotchas Subtitle provider API keys (OpenSubtitles, etc.) are stored in Bazarr's settings. These are rate-limited — avoid aggressive subtitle searches on large libraries. Bazarr writes subtitle .srt or .ass files next to the video files in /mnt/Multimedia. Plex and Jellyfin pick these up automatically on the next library scan. If Sonarr or Radarr is restarted, Bazarr may take a few minutes to re-sync its library view via the API. Last Updated: 2026-06-16 05-nzbget.md kstack: book: Centerpoint Home Lab chapter: Media Management page: NZBGet tags: [nzbget, usenet, download-client, arr, media-management] Overview NZBGet is the Usenet download client for the homelab. It receives download jobs from Sonarr, Radarr, Whisparr, and LazyLibrarian, downloads from configured Usenet providers, unpacks archives, and notifies the requesting *arr application when complete. Downloads are staged to the Ceph OSD volume ( 1TB_Vol2) for high-speed local writes before being processed and moved to UnRAID. Access Type URL Notes Internal https://nzbget.home.local LAN access via Step-CA TLS External https://nzb.jeeves5454.ddns.net Authentik SSO + GeoBlock + CrowdSec Configuration Image: lscr.io/linuxserver/nzbget:latest Compose project: arr stack Traefik Labels # External route traefik.http.routers.nzbget-external.rule: Host(`nzb.jeeves5454.ddns.net`) traefik.http.routers.nzbget-external.entrypoints: websecure traefik.http.routers.nzbget-external.tls.certresolver: letsencrypt traefik.http.routers.nzbget-external.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.nzbget-external.service: nzbget-svc # Internal route traefik.http.routers.nzbget-internal.rule: Host(`nzbget.home.local`) traefik.http.routers.nzbget-internal.entrypoints: websecure traefik.http.routers.nzbget-internal.tls.certresolver: step-ca traefik.http.routers.nzbget-internal.service: nzbget-svc traefik.http.services.nzbget-svc.loadbalancer.server.port: 6789 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/NZBGet/config /config NZBGet settings and queue database /media/jeeves/1TB_Vol2/downloads /downloads Download staging directory (Ceph OSD) /mnt/Multimedia /media Media library (used by post-process scripts) Download Directory Layout NZBGet organises downloads under /downloads with category subdirectories: /media/jeeves/1TB_Vol2/downloads/ ├── usenet/ │ ├── intermediate/ ← In-progress downloads │ └── complete/ │ ├── tv/ ← Completed TV episodes → Sonarr picks up │ ├── movies/ ← Completed movies → Radarr picks up │ ├── books/ ← Completed books → LazyLibrarian picks up │ └── adult/ ← Completed adult content → Whisparr picks up Notes / Gotchas Usenet provider credentials (server address, port, username, password) are stored in NZBGet's settings — these are REDACTED in all documentation. NZBGet's built-in web UI has its own username/password in addition to the Authentik ForwardAuth layer on the external route. Both must be configured. The Ceph OSD download volume provides fast local NVMe performance for downloads before they are moved to the slower NFS path. Ensure adequate free space is maintained on 1TB_Vol2. Each *arr app connects to NZBGet via http://nzbget:6789 on the media-network using the NZBGet API credentials configured in the *arr download client settings. Last Updated: 2026-06-16 06-whisparr.md kstack: book: Centerpoint Home Lab chapter: Media Management page: Whisparr tags: [whisparr, arr, media-management, adult-content] Overview Whisparr is a Sonarr fork purpose-built for managing adult content. It integrates with Prowlarr for indexer searches and NZBGet for downloads, following the same *arr workflow as Sonarr but with metadata sources appropriate for its content type. Downloaded content is delivered to the same media volume that Stash monitors. Access Type URL Notes Internal https://whisparr.home.local LAN access via Step-CA TLS No external route — internal management only. Configuration Image: ghcr.io/hotio/whisparr:v3 Compose project: arr stack Traefik Labels traefik.enable: "true" traefik.http.routers.whisparr.rule: Host(`whisparr.home.local`) traefik.http.routers.whisparr.entrypoints: websecure traefik.http.routers.whisparr.tls.certresolver: step-ca traefik.http.services.whisparr.loadbalancer.server.port: 6969 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/Whisparr/config /config Whisparr database and settings /media/jeeves/1TB_Vol2/downloads /downloads NZBGet download staging (Ceph OSD) /mnt/Multimedia /data Media library including adult content (NFS from UnRAID) Integration Points Service Connection Method Purpose Prowlarr API ( http://prowlarr:9696) via media-network Indexer search NZBGet API ( http://nzbget:6789) via media-network Download client Notes / Gotchas The hotio/whisparr:v3 image is a community-maintained build. Monitor the repository for updates as this is not an official Servarr project. Whisparr's library root folder should point to the same path that Stash monitors ( /mnt/Multimedia/ST) so content is automatically picked up by Stash after import. Prowlarr indexer categories for adult content must be configured correctly in Prowlarr and mapped to Whisparr in the Apps settings. Last Updated: 2026-06-16 07-lazylibrarian.md kstack: book: Centerpoint Home Lab chapter: Media Management page: LazyLibrarian tags: [lazylibrarian, books, ebooks, magazines, arr, media-management] Overview LazyLibrarian manages ebook, audiobook, and magazine acquisition. It monitors authors and publications, searches for releases via Prowlarr, downloads via NZBGet, and organises the completed files into the books library on UnRAID. Despite its internal name, it is accessed externally via the readarr.jeevesconsults.ca domain. Access Type URL Notes Internal https://readarr.home.local LAN access via Step-CA TLS External https://readarr.jeevesconsults.ca Authentik SSO + GeoBlock + CrowdSec The external domain uses readarr.jeevesconsults.ca (not lazylibrarian.*). Configuration Image: lscr.io/linuxserver/lazylibrarian:latest Compose project: arr stack Traefik Labels # External route traefik.http.routers.lazylibrarian-external.rule: Host(`readarr.jeevesconsults.ca`) traefik.http.routers.lazylibrarian-external.entrypoints: websecure traefik.http.routers.lazylibrarian-external.tls.certresolver: letsencrypt traefik.http.routers.lazylibrarian-external.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.lazylibrarian-external.service: lazylibrarian-svc # Internal route traefik.http.routers.lazylibrarian-internal.rule: Host(`readarr.home.local`) traefik.http.routers.lazylibrarian-internal.entrypoints: websecure traefik.http.routers.lazylibrarian-internal.tls.certresolver: step-ca traefik.http.routers.lazylibrarian-internal.service: lazylibrarian-svc traefik.http.services.lazylibrarian-svc.loadbalancer.server.port: 5299 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/lazylibrarian/config /config LazyLibrarian database and settings /media/jeeves/1TB_Vol2/downloads/usenet/complete/books /downloads Completed book downloads (Ceph OSD) /mnt/Multimedia/Books/Lazylibrarian /books Final book library (NFS from UnRAID) Integration Points Service Connection Method Purpose Prowlarr API ( http://prowlarr:9696) via media-network Indexer search NZBGet API ( http://nzbget:6789) via media-network Download client Notes / Gotchas LazyLibrarian covers ebooks, audiobook metadata management (separate from Audiobookshelf's podcast/streaming role), and magazines. Configure each content type under its own library section within LazyLibrarian. The external domain ( readarr.jeevesconsults.ca) was chosen for cleaner branding — update DNS and Traefik labels if the domain ever changes. Calibre integration can be enabled in LazyLibrarian settings if a Calibre server is available on the network for ebook format conversion. Last Updated: 2026-06-16 08-mylar3.md kstack: book: Centerpoint Home Lab chapter: Media Management page: Mylar3 tags: [mylar3, comics, cbz, arr, media-management] Overview Mylar3 is the automated comic book downloader and manager for the homelab. It monitors comic series, searches for new issues via Usenet indexers (through NZBGet), and organises downloaded CBZ/CBR files into the comics library on UnRAID. Access Type URL Notes Internal https://mylar.home.local LAN access via Step-CA TLS No external route — internal management only. Configuration Image: lscr.io/linuxserver/mylar3:latest Compose project: arr stack Traefik Labels traefik.enable: "true" traefik.http.routers.mylar3.rule: Host(`mylar.home.local`) traefik.http.routers.mylar3.entrypoints: websecure traefik.http.routers.mylar3.tls.certresolver: step-ca traefik.http.services.mylar3.loadbalancer.server.port: 8090 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/mylar/config /config Mylar3 database and settings /media/jeeves/1TB_Vol2/downloads/usenet/complete/books /downloads Completed downloads (Ceph OSD, shared with books) /mnt/Multimedia/Books/Comics /comics Comics library (NFS from UnRAID) Integration Points Service Connection Method Purpose NZBGet API ( http://nzbget:6789) via media-network Download client Mylar3 connects directly to NZBGet rather than through Prowlarr. Indexers are configured directly in Mylar3 settings. Notes / Gotchas Mylar3 uses ComicVine as its metadata source — a free API key from comicvine.gamespot.com is required and stored in the Mylar3 config. The /downloads path is shared with LazyLibrarian (both point to the books completion directory). Ensure Mylar3's category in NZBGet is distinct to avoid processing conflicts. Mylar3's database ( mylar.db) stores the full comic watchlist and download history. Back it up before upgrades. Last Updated: 2026-06-16 09-audiobookrequest.md kstack: book: Centerpoint Home Lab chapter: Media Management page: Audiobookrequest tags: [audiobookrequest, audiobooks, requests, media-management] Overview Audiobookrequest is a self-hosted audiobook request portal — similar in concept to Seerr but for audiobooks. Users can search for and request audiobooks, which are then queued for download via the configured acquisition tools (LazyLibrarian). Access Type URL Notes Internal https://audiobookseer.home.local LAN access via Step-CA TLS No external route — LAN and Tailscale access only. Configuration Image: markbeep/audiobookrequest:1 Compose project: arr stack Traefik Labels traefik.enable: "true" traefik.http.routers.audiobookrequest.rule: Host(`audiobookseer.home.local`) traefik.http.routers.audiobookrequest.entrypoints: websecure traefik.http.routers.audiobookrequest.tls.certresolver: step-ca traefik.http.services.audiobookrequest.loadbalancer.server.port: 8000 Key Environment Variables Variable Value Purpose TZ America/Toronto Timezone ABR_APP__PORT 8000 Application listen port ABR_APP__FORCE_LOGIN_TYPE forms Forces form-based login ABR_APP__VERSION 1.10.5 Application version ABR_APP__FORCE_LOGIN_TYPE=forms disables any SSO/header-based login and enforces the standard username/password login form. Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/audiobookrequest/config /config Request database and settings Notes / Gotchas User accounts and request history are stored in the /config bind mount. Audiobookrequest is pinned to image tag 1 — check for newer tagged releases before upgrading. Integration with LazyLibrarian (for fulfilling requests) must be configured in Audiobookrequest settings with the LazyLibrarian API endpoint and key. Last Updated: 2026-06-16 10-profilarr.md kstack: book: Centerpoint Home Lab chapter: Media Management page: Profilarr tags: [profilarr, quality-profiles, arr, media-management] Overview Profilarr is a quality profile and custom format manager for the *arr stack. It maintains a centralised set of quality profiles and custom format definitions and syncs them across Sonarr, Radarr, and other *arr apps via their APIs — ensuring consistent quality standards across all services without manual duplication. Access Type URL Notes Internal https://profilarr.home.local LAN access via Step-CA TLS No external route — internal management only. Configuration Image: santiagosayshey/profilarr:latest Compose project: arr stack Traefik Labels traefik.enable: "true" traefik.http.routers.profilarr.rule: Host(`profilarr.home.local`) traefik.http.routers.profilarr.entrypoints: websecure traefik.http.routers.profilarr.tls.certresolver: step-ca traefik.http.services.profilarr.loadbalancer.server.port: 6868 Environment Variables Variable Value Purpose TZ America/Toronto Timezone Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/profilarr/config /config Profile definitions and sync state Notes / Gotchas Quality profiles in Sonarr and Radarr should not be edited manually when Profilarr is managing them — changes will be overwritten on the next sync. Profilarr can import community-maintained profile packs (e.g. TRaSH Guides profiles) and push them to all connected *arr apps. API keys for each connected *arr application must be configured in Profilarr settings. Last Updated: 2026-06-16 11-dispatcharr.md kstack: book: Centerpoint Home Lab chapter: Media Management page: Dispatcharr tags: [dispatcharr, iptv, m3u, media-management] Overview Dispatcharr is an IPTV playlist and stream management tool. It organises M3U channel lists, manages IPTV stream sources, and acts as a companion to Threadfin — providing a more structured interface for managing channel groups, EPG mappings, and stream prioritisation before they are published to Plex and Jellyfin via Threadfin. Dispatcharr runs in all-in-one ( aio) mode, bundling its application server, Celery task worker, and Redis instance into a single container. Access Type URL Notes Internal https://dispatcharr.home.local LAN access via Step-CA TLS No external route — internal management only. Configuration Image: ghcr.io/dispatcharr/dispatcharr:latest Compose project: arr stack Traefik Labels traefik.enable: "true" traefik.http.routers.dispatcharr.rule: Host(`dispatcharr.home.local`) traefik.http.routers.dispatcharr.entrypoints: websecure traefik.http.routers.dispatcharr.tls.certresolver: step-ca traefik.http.services.dispatcharr.loadbalancer.server.port: 9191 Key Environment Variables Variable Value Purpose DISPATCHARR_ENV aio All-in-one mode (app + worker + Redis) DISPATCHARR_LOG_LEVEL info Logging verbosity REDIS_HOST localhost Internal Redis (bundled in AIO mode) CELERY_BROKER_URL redis://localhost:6379/0 Celery task queue Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/dispatcharr/data /data Dispatcharr database and config Notes / Gotchas In aio mode, Redis runs inside the container and does not persist state between restarts beyond what is stored in the /data bind mount. Task queue state is lost on container restart. Dispatcharr integrates with Threadfin as the downstream consumer of its managed M3U playlists. Configure the Dispatcharr output URL in Threadfin as the M3U source. ghcr.io/dispatcharr/dispatcharr:latest is an actively developed project — check release notes before pulling updates as breaking changes may affect the channel database schema. Last Updated: 2026-06-16 12-pulsarr.md kstack: book: Centerpoint Home Lab chapter: Media Management page: Pulsarr tags: [pulsarr, watchlist, notifications, arr, media-management] Overview Pulsarr is a watchlist automation and notification bridge for the *arr stack. It monitors Plex and Jellyfin watchlists, automatically adds requested movies and TV shows to Radarr and Sonarr, and sends notifications when content is downloaded and available. It also integrates with TMDB for metadata-enriched notifications. Access Type URL Notes Internal https://pulsarr.home.local LAN access via Step-CA TLS External https://pulsarr.jeeves5454.ddns.net Authentik SSO + GeoBlock + CrowdSec Configuration Image: lakker/pulsarr:latest Compose project: arr stack Traefik Labels # External route traefik.http.routers.pulsarr-external.rule: Host(`pulsarr.jeeves5454.ddns.net`) traefik.http.routers.pulsarr-external.entrypoints: websecure traefik.http.routers.pulsarr-external.tls.certresolver: letsencrypt traefik.http.routers.pulsarr-external.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.pulsarr-external.service: pulsarr-svc # Internal route traefik.http.routers.pulsarr-internal.rule: Host(`pulsarr.home.local`) traefik.http.routers.pulsarr-internal.entrypoints: websecure traefik.http.routers.pulsarr-internal.tls.certresolver: step-ca traefik.http.routers.pulsarr-internal.service: pulsarr-svc traefik.http.services.pulsarr-svc.loadbalancer.server.port: 3003 Key Environment Variables Variable Value Purpose TZ America/Toronto Timezone port 3003 Application listen port tmdbApiKey REDACTED TMDB API key for metadata and notification enrichment Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/arr/pulsarr/config /app/data Pulsarr database and configuration Integration Points Service Purpose Sonarr Adds TV shows from watchlists; receives completion events Radarr Adds movies from watchlists; receives completion events Plex Reads watchlists from Plex accounts TMDB Fetches poster art and metadata for notifications Notes / Gotchas The TMDB API key ( tmdbApiKey) is visible in the container environment — treat it as a secret. Rotate at themoviedb.org if exposed. Pulsarr's watchlist sync requires Plex account API tokens configured in the Pulsarr UI settings. Pulsarr is healthy-checked ( (healthy) status in docker ps) — the healthcheck endpoint is configured internally. Notification channels (Discord, Telegram, etc.) are configured in Pulsarr settings and stored in the /app/data bind mount. Last Updated: 2026-06-16 13-flaresolverr.md kstack: book: Centerpoint Home Lab chapter: Media Management page: FlareSolverr (Offline) tags: [flaresolverr, cloudflare, prowlarr, offline, media-management] Overview Status: OFFLINE — The FlareSolverr container is present but currently not running (exited 8 weeks ago). It can be restarted when Cloudflare-protected indexers need to be accessed via Prowlarr. FlareSolverr is a proxy server that bypasses Cloudflare's anti-bot protection for web-based indexers. Prowlarr routes requests for Cloudflare-protected indexers through FlareSolverr, which uses a headless browser session to solve the challenge and return the page content. Access FlareSolverr has no web UI. It is accessed internally by Prowlarr via its HTTP API at http://flaresolverr:8191. Configuration Image: ghcr.io/flaresolverr/flaresolverr:latest Status: exited Prowlarr Integration Once running, configure in Prowlarr: Settings → Indexers → Add Proxy Type: FlareSolverr Host: http://flaresolverr:8191 Tag: Apply the tag to any Cloudflare-protected indexer Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/flaresolver /config FlareSolverr config Notes / Gotchas FlareSolverr uses a headless Chromium browser. It consumes significant CPU and RAM during challenge solving — resource spikes are normal. Cloudflare regularly updates its bot detection. FlareSolverr may stop working after Cloudflare updates and require an image update. When FlareSolverr is offline, any Prowlarr indexer tagged with the FlareSolverr proxy will fail silently on search — no error surfaced in Sonarr/Radarr. Check Prowlarr indexer status if searches return no results. To restart: docker start flaresolverr or redeploy via Portainer. Last Updated: 2026-06-16 h06-documents-organization 00-chapter-intro.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: Chapter Introduction tags: [documents, organization, paperless, bookstack, productivity] Overview This chapter covers personal productivity, knowledge management, and life-organisation services. These range from document archival and wiki systems through to recipe management, home inventory, plant care, and fitness tracking. Services in This Chapter Service Container(s) Purpose Paperless-NGX paperless-webserver-1, paperless-db-1, paperless-broker-1, paperless_gotenberg, paperless_tika Document management and archival BookStack bookstack, bookstack-db Self-hosted wiki and documentation Karakeep karakeep-web Bookmarks and read-it-later Memos memos Quick notes and journal Booklore booklore, booklore-db Personal book library and reading tracker Mealie mealie, mealieaddons Recipe management and meal planning Homebox homebox Home inventory management Medikeep medikeep, medikeep-db Personal medical records tracker LubeLogger lubelogger, lubelogger-db Vehicle maintenance log HortusFox hortusfox, hortusfox-db, hortusfox-cron Plant care and garden management LinkStack linkstack Public link-in-bio / personal landing page Reitti reitti-app, reitti-postgis, reitti-redis, reitti-rabbitmq, reitti-tiles, reitti-photon Activity and fitness tracking Swarm-Reitti Bridge swarm-reitti-bridge Custom Foursquare→Reitti check-in sync service Last Updated: 2026-06-17 01-paperless-ngx.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: Paperless-NGX tags: [paperless, documents, ocr, archive, productivity] Overview Paperless-NGX is the centralised document management and archival system. It ingests scanned documents and PDFs via a watched consume directory and email, applies OCR, tags, correspondents, and document types, then stores the processed results in a structured archive. Gmail OAuth is configured for email ingestion. SSO is provided via Authentik OpenID Connect. Access Type URL Auth External https://paperless.jeeves5454.ddns.net Authentik OIDC + GeoBlock + CrowdSec Internal https://paperless.home.local Step-CA TLS (internal) Authentication uses Authentik OIDC ( openid_connect Django allauth provider). Regular local login is kept enabled ( PAPERLESS_DISABLE_REGULAR_LOGIN=false). Auto-signup for new OIDC users is disabled — accounts must be pre-created. Containers Five containers in this stack: Container Image Role paperless-webserver-1 ghcr.io/paperless-ngx/paperless-ngx:latest Web UI + workers paperless-db-1 postgres:16 Primary database paperless-broker-1 redis:7 Celery task queue paperless_gotenberg gotenberg/gotenberg:8.27 DOCX→PDF conversion paperless_tika apache/tika:latest Content extraction paperless-webserver-1 Main application container running both the Django web server and Celery workers. Key environment variables: Variable Value / Notes PAPERLESS_URL https://paperless.jeeves5454.ddns.net PAPERLESS_CSRF_TRUSTED_ORIGINS https://paperless.jeeves5454.ddns.net PAPERLESS_OAUTH_CALLBACK_BASE_URL https://paperless.jeeves5454.ddns.net PAPERLESS_DBHOST db PAPERLESS_REDIS redis://broker:6379 PAPERLESS_TIKA_ENABLED 1 PAPERLESS_TIKA_ENDPOINT http://tika:9998 PAPERLESS_TIKA_GOTENBERG_ENDPOINT http://gotenberg:3000 PAPERLESS_TASK_WORKERS 2 PAPERLESS_THREADS_PER_WORKER 2 PAPERLESS_TIME_ZONE America/Toronto PAPERLESS_OCR_LANGUAGE eng PAPERLESS_CONSUMER_RECURSIVE true PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS true PAPERLESS_CONSUMER_POLLING 5 (seconds) PAPERLESS_FILENAME_FORMAT {{ created_year }}/{{ document_type }}/{{ created_year }}-{{ created_month }}-{{ created_day }}_{{ correspondent }}_{{ title }} PAPERLESS_FILENAME_FORMAT_REMOVE_NONE true PAPERLESS_SOCIAL_AUTO_SIGNUP false PAPERLESS_ACCOUNT_EMAIL_VERIFICATION none PAPERLESS_GMAIL_OAUTH_CLIENT_ID (visible — treat as semi-public) PAPERLESS_GMAIL_OAUTH_CLIENT_SECRET REDACTED PAPERLESS_APPS allauth.socialaccount.providers.openid_connect OIDC provider client secret REDACTED (inside PAPERLESS_SOCIALACCOUNT_PROVIDERS) Bind mounts: Host Path Container Path Purpose /home/jeeves/docker/paperless/data /usr/src/paperless/data Index and SQLite state /mnt/data/paperless/media /usr/src/paperless/media Archived document files /mnt/data/paperless/consume /usr/src/paperless/consume Watched consume folder /mnt/data/paperless/export /usr/src/paperless/export Export output folder The media, consume, and export directories live on the NFS-equivalent Ceph volume at /mnt/data/, providing separation from the system drive. paperless-db-1 PostgreSQL 16 database backend. Bind mounts: Host Path Container Path /home/jeeves/docker/paperless/pgdata /var/lib/postgresql/data paperless-broker-1 Redis 7 for Celery task queue. Bind mounts: Host Path Container Path /home/jeeves/docker/paperless/redis /data paperless_gotenberg Gotenberg 8.27 — converts Office documents (DOCX, XLSX, etc.) to PDF for ingestion. No persistent volumes. paperless_tika Apache Tika — extracts content and metadata from complex document formats. No persistent volumes. Traefik Labels traefik.http.routers.paperless-external.rule: Host(`paperless.jeeves5454.ddns.net`) traefik.http.routers.paperless-external.entrypoints: websecure traefik.http.routers.paperless-external.tls.certresolver: letsencrypt traefik.http.routers.paperless-external.middlewares: plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.paperless-internal.rule: Host(`paperless.home.local`) traefik.http.routers.paperless-internal.entrypoints: websecure traefik.http.routers.paperless-internal.tls.certresolver: step-ca Notes / Gotchas Secrets in PAPERLESS_SOCIALACCOUNT_PROVIDERS (OIDC client secret) and PAPERLESS_GMAIL_OAUTH_CLIENT_SECRET are in plaintext in the Portainer-managed compose environment — rotate if the Portainer env is ever exposed. The consume directory polling interval is 5 seconds. Drop files into /mnt/data/paperless/consume (or a subdirectory — subdirectory names become tags automatically via PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS). Tika and Gotenberg must be running for complex document formats to process; plain-text PDFs work without them. The PAPERLESS_LOGOUT_REDIRECT_URL is set to the Authentik end-session endpoint — clicking Log Out in Paperless also terminates the Authentik session. Last Updated: 2026-06-17 02-bookstack.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: BookStack tags: [bookstack, wiki, documentation, mariadb, oidc] Overview BookStack is the self-hosted personal knowledge base and documentation wiki — the platform this very documentation is written in. It organises content into Shelves → Books → Chapters → Pages and provides a full Markdown editor. Access is protected by Authentik OIDC on the external route. On the internal route, authentication still passes through BookStack's own OIDC flow (no Traefik ForwardAuth bypass), so the Authentik session is always required. Access Type URL Auth External https://wiki.jeeves5454.ddns.net Authentik OIDC + GeoBlock + CrowdSec Internal https://wiki.home.local Authentik OIDC (Step-CA TLS) BookStack uses its own OIDC integration rather than Traefik ForwardAuth. The external Traefik route applies GeoBlock and CrowdSec but not authentik-auth@docker — BookStack manages the OIDC redirect itself. Containers Container Image Role bookstack lscr.io/linuxserver/bookstack:latest Web application bookstack-db mariadb:10.11 MariaDB database bookstack (application) Key environment variables: Variable Value / Notes APP_URL https://wiki.jeeves5454.ddns.net AUTH_METHOD oidc AUTH_AUTO_INITIATE true — skips BookStack login page, redirects to Authentik OIDC_NAME Authentik OIDC_ISSUER https://auth.jeevesconsults.ca/application/o/book-stack-website-s/ OIDC_ISSUER_DISCOVER true OIDC_CLIENT_ID QjpMMmpDqNCQIM75np6gGyMkN9Y569AtyNVhfJvx OIDC_CLIENT_SECRET REDACTED OIDC_EXTERNAL_ID_CLAIM email OIDC_DISPLAY_NAME_CLAIMS name OIDC_FETCH_AVATAR false OIDC_END_SESSION_ENDPOINT false DB_HOST bookstack-db DB_DATABASE bookstack DB_USERNAME bookstack DB_PASSWORD REDACTED MAIL_HOST smtp.gmail.com MAIL_PORT 587 MAIL_USERNAME jeeves5454@gmail.com MAIL_ENCRYPTION TLS MAIL_FROM noreply@jeevesconsults.ca PUID / PGID 1000 TZ America/Toronto Bind mounts: Host Path Container Path Purpose /home/jeeves/docker/bookstack/config /config App config, attachments, uploads /home/jeeves/docker/bookstack/public /public Public web assets bookstack-db (MariaDB 10.11) Bind mounts: Host Path Container Path /home/jeeves/docker/bookstack/db /var/lib/mysql Traefik Labels traefik.http.routers.bookstack-ext.rule: Host(`wiki.jeeves5454.ddns.net`) traefik.http.routers.bookstack-ext.entrypoints: websecure traefik.http.routers.bookstack-ext.tls.certresolver: letsencrypt traefik.http.routers.bookstack-ext.middlewares: plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.bookstack-ext.service: bookstack-svc traefik.http.routers.bookstack-int.rule: Host(`wiki.home.local`) traefik.http.routers.bookstack-int.entrypoints: websecure traefik.http.routers.bookstack-int.tls.certresolver: step-ca traefik.http.routers.bookstack-int.service: bookstack-svc traefik.http.services.bookstack-svc.loadbalancer.server.port: 80 Notes / Gotchas APP_URL is set to the external URL even though both internal and external routes exist. This is intentional — it is used for OIDC redirect URIs and email links, which must be reachable externally. AUTH_AUTO_INITIATE: true means anyone who hits the URL is immediately redirected to Authentik. There is no BookStack login form shown unless OIDC fails. To bypass OIDC in an emergency, use ?prevent_auto_init=true appended to the URL. For an existing user to link their Authentik account, go to Admin → Users → select user → set External Authentication ID to their email address. The LSIO image uses DB_USERNAME, not DB_USER. Using DB_USER silently falls back to no authentication and causes confusing login failures. APP_KEY (Laravel application key) is stored in the /config directory and persists across container restarts. Do not delete the config bind mount or the key must be regenerated and all sessions will be invalidated. Last Updated: 2026-06-17 03-karakeep.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: Karakeep tags: [karakeep, bookmarks, read-later, productivity] Overview Karakeep is a self-hosted bookmark manager and read-it-later service. It crawls saved URLs for their full text and screenshots, indexes them via Meilisearch, and supports OCR on images ( ocr_langs=eng). Video download is enabled (yt-dlp backend, no size limit). SMTP is configured for email-to-save via Gmail. New signups are disabled — account management is manual. Access Type URL Auth External https://bookmark.jeevesconsults.ca GeoBlock + CrowdSec (own auth) External-only — no internal home.local route configured. Configuration Image: ghcr.io/karakeep-app/karakeep:latest Key Environment Variables Variable Value / Notes NEXTAUTH_URL https://bookmark.jeevesconsults.ca NEXTAUTH_URL_INTERNAL http://karakeep-web:3000 NEXTAUTH_TRUST_HOST true NEXTAUTH_SECRET REDACTED DATA_DIR /data ASSETS_DIR /assets MEILI_ADDR http://meilisearch:7700 MEILI_MASTER_KEY REDACTED BROWSER_WEB_URL http://chrome:9222 (headless Chromium) OCR_LANGS eng OCR_CONFIDENCE_THRESHOLD 75 CRAWLER_VIDEO_DOWNLOAD true CRAWLER_VIDEO_DOWNLOAD_MAX_SIZE -1 (unlimited) CRAWLER_VIDEO_DOWNLOAD_TIMEOUT_SEC 7200 INFERENCE_ENABLE_AUTO_SUMMARIZATION true MAX_ASSET_SIZE_MB 50 DISABLE_SIGNUPS true EMAIL_VERIFICATION_REQUIRED false SMTP_HOST smtp.gmail.com SMTP_PORT 587 SMTP_SECURE true SMTP_USER jeeves5454@gmail.com SMTP_FROM jeeves5454@gmail.com SMTP_PASSWORD REDACTED Traefik Labels traefik.http.routers.karakeep.rule: Host(`bookmark.jeevesconsults.ca`) traefik.http.routers.karakeep.entrypoints: websecure traefik.http.routers.karakeep.tls.certresolver: letsencrypt traefik.http.routers.karakeep.middlewares: plex-geoblock@file,crowdsec-bouncer@file,karakeep-headers traefik.http.middlewares.karakeep-headers.headers.customrequestheaders.X-Forwarded-Proto: https traefik.http.services.karakeep.loadbalancer.server.port: 3000 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/karakeep/data /data Database and app data /mnt/data/karakeep /assets Saved assets (screenshots, videos) Stack Companions Karakeep requires additional sidecar containers (Meilisearch, Headless Chrome) in the same compose network to function. These are not independently accessible. Notes / Gotchas The X-Forwarded-Proto: https middleware is required for Karakeep (Next.js) to trust the proxy and generate correct redirect URIs — without it, HTTPS callbacks will fail. Video downloads can consume significant storage on /mnt/data/karakeep. The timeout is 2 hours, which accommodates long-form content. Meilisearch index data is stored in /data — this must be backed up alongside the app database for search to remain functional after a restore. DISABLE_SIGNUPS: true — new accounts must be created manually in the admin UI. Last Updated: 2026-06-17 04-memos.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: Memos tags: [memos, notes, journal, productivity] Overview Memos is a lightweight, self-hosted note-taking and micro-journaling app. It provides a simple Twitter/Mastodon-style feed of notes, supports Markdown, tags, and public/private visibility per memo. Internal-only access — no external route is configured. Access Type URL Auth Internal https://memos.home.local Memos own auth (Step-CA TLS) No external route — accessible only on the LAN. Configuration Image: neosmemo/memos:stable Environment Variables Variable Value Purpose MEMOS_INSTANCE_URL https://memos.home.local Canonical URL MEMOS_PORT 5230 Listen port TZ America/Toronto Timezone Traefik Labels traefik.http.routers.memos.rule: Host(`memos.home.local`) traefik.http.routers.memos.entrypoints: websecure traefik.http.routers.memos.tls.certresolver: step-ca traefik.http.services.memos.loadbalancer.server.port: 5230 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/memos/data /var/opt/memos Database and assets Memos uses SQLite stored in /var/opt/memos. No external database dependency. Notes / Gotchas neosmemo/memos:stable tracks the stable release tag. Upgrading the image will migrate the SQLite database automatically on first startup. There is no Authentik integration on this service — Memos manages its own accounts internally. Memos supports public content sharing via short links; ensure private memos are set to private if personal content is being stored. Last Updated: 2026-06-17 05-booklore.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: Booklore tags: [booklore, books, library, mariadb, step-ca] Overview Booklore is a personal e-book library manager. It indexes books from the NFS Multimedia share (read-only) and a local bookdrop directory for new additions. The app is a Java/Spring Boot application (JDK 25 with Shenandoah GC) backed by MariaDB. It uses its own internal authentication — no Authentik ForwardAuth or OIDC. The Step-CA root certificate is bind-mounted into the system trust store to allow Booklore to make HTTPS calls to internal *.home.local services. Access Type URL Auth External https://booklore.jeevesconsults.ca GeoBlock + CrowdSec (own auth) Internal https://booklore.home.local Own auth (Step-CA TLS) Containers Container Image Role booklore grimmory/grimmory:latest Web application booklore-db lscr.io/linuxserver/mariadb:latest MariaDB database booklore (application) Key environment variables: Variable Value / Notes DATABASE_USERNAME booklore DATABASE_PASSWORD REDACTED DATABASE_URL jdbc:mariadb://booklore-db:3306/booklore BOOKLORE_PORT 6060 DISK_TYPE NETWORK (books from NFS mount) USER_ID / GROUP_ID 1000 TZ America/Toronto APP_VERSION v3.2.0 Runtime: Java 25 (Temurin JDK 25.0.3) with Shenandoah GC. JVM tuning: max 60% RAM, Shenandoah compact heuristics, 256MB metaspace cap. Bind mounts: Host Path Container Path Purpose /mnt/Multimedia/Books /books Book library (read-only via NFS) /home/jeeves/docker/booklore/data /app/data App state and metadata /home/jeeves/docker/booklore/bookdrop /bookdrop Drop zone for new books /home/jeeves/docker/step-ca/config/certs/root_ca.crt /usr/local/share/ca-certificates/step-ca.crt Step-CA root trust injection booklore-db (MariaDB LSIO) Image: lscr.io/linuxserver/mariadb:latest Variable Value MYSQL_DATABASE booklore MYSQL_USER booklore MYSQL_PASSWORD REDACTED PUID / PGID 1000 Bind mounts: Host Path Container Path /home/jeeves/docker/booklore/mariadb /config Traefik Labels traefik.http.routers.booklore-external.rule: Host(`booklore.jeevesconsults.ca`) traefik.http.routers.booklore-external.entrypoints: websecure traefik.http.routers.booklore-external.tls.certresolver: letsencrypt traefik.http.routers.booklore-external.middlewares: plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.booklore-external.service: booklore-svc traefik.http.routers.booklore-internal.rule: Host(`booklore.home.local`) traefik.http.routers.booklore-internal.entrypoints: websecure traefik.http.routers.booklore-internal.tls.certresolver: step-ca traefik.http.routers.booklore-internal.service: booklore-svc traefik.http.services.booklore-svc.loadbalancer.server.port: 6060 Notes / Gotchas The Step-CA root certificate bind mount ( root_ca.crt → /usr/local/share/ca-certificates/) makes the container's Java runtime trust internal TLS certificates. This is required if Booklore makes any HTTPS requests to *.home.local services. /mnt/Multimedia/Books is the NFS share from UnRAID — it must be mounted before Booklore starts or the library scan will fail silently. Check df -h /mnt/Multimedia if the library appears empty. Drop new e-books (EPUB, PDF) into /home/jeeves/docker/booklore/bookdrop for automatic library import. grimmory/grimmory:latest is the container image name for the Booklore project. Last Updated: 2026-06-17 06-mealie.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: Mealie tags: [mealie, recipes, meal-planning, oidc, authentik] Overview Mealie is a self-hosted recipe manager and meal planner. It imports recipes from URLs, supports manual entry, and provides a weekly meal plan interface. OIDC authentication is via Authentik, with auto-redirect enabled. The mealieaddons sidecar (Mealie Addons by razziel89) provides enhanced recipe retrieval, PDF export via Pandoc, and image embedding. Access Type URL Auth External https://recipe.jeevesconsults.ca Authentik OIDC + GeoBlock + CrowdSec Internal https://mealie.home.local Authentik OIDC (Step-CA TLS) OIDC auto-redirect is enabled ( OIDC_AUTO_REDIRECT: true) — the Mealie login form is bypassed and Authentik is shown directly. Containers Container Image Role mealie ghcr.io/mealie-recipes/mealie:latest Core application mealieaddons ghcr.io/razziel89/mealie-addons:latest Enhanced retrieval and export mealie (application) Key environment variables: Variable Value / Notes BASE_URL https://recipe.jeevesconsults.ca OIDC_AUTH_ENABLED true OIDC_AUTO_REDIRECT true OIDC_PROVIDER_NAME Authentik OIDC_CONFIGURATION_URL https://auth.jeevesconsults.ca/application/o/mealie/.well-known/openid-configuration OIDC_CLIENT_ID cjTjv6CLnu0pT7qkkthDTjXmaVPYdxMfOKW9d5Wy OIDC_CLIENT_SECRET REDACTED OIDC_USER_GROUP family-friends OIDC_ADMIN_GROUP admins OIDC_SIGNUP_ENABLED false OIDC_REMEMBER_ME true ALLOW_SIGNUP false SMTP_HOST smtp.gmail.com SMTP_PORT 587 SMTP_AUTH_STRATEGY TLS SMTP_USER jeeves5454@gmail.com SMTP_PASSWORD REDACTED SMTP_FROM_EMAIL jeeves5454@gmail.com PUID / PGID 1000 TZ America/Toronto Bind mounts: Host Path Container Path Purpose /home/jeeves/docker/mealie/data /app/data Database and assets mealieaddons Mealie Addons provides enhanced recipe scraping, Pandoc-based PDF/EPUB export, and image embedding for recipes. Key environment variables: Variable Value / Notes MEALIE_BASE_URL https://recipe.jeevesconsults.ca MEALIE_RETRIEVAL_URL http://mealie:9000 MA_SELF_URL http://localhost:9000 MA_LISTEN_INTERFACE :9000 MA_IMAGE_ACTION embed MA_TIMEOUT_SECS 60 MA_RETRIEVAL_LIMIT 5 GIN_MODE release PANDOC_FLAGS --epub-title-page=false No bind mounts — stateless. Traefik route: traefik.http.routers.mealieaddons.rule: Host(`mealieaddons.home.local`) traefik.http.routers.mealieaddons.entrypoints: websecure traefik.http.routers.mealieaddons.tls.certresolver: step-ca traefik.http.services.mealieaddons.loadbalancer.server.port: 9000 Traefik Labels (mealie) traefik.http.routers.mealie-ext.rule: Host(`recipe.jeevesconsults.ca`) traefik.http.routers.mealie-ext.entrypoints: websecure traefik.http.routers.mealie-ext.tls.certresolver: letsencrypt traefik.http.routers.mealie-ext.middlewares: plex-geoblock@file,crowdsec-bouncer@file,mealie-headers traefik.http.routers.mealie-ext.service: mealie-svc traefik.http.routers.mealie-int.rule: Host(`mealie.home.local`) traefik.http.routers.mealie-int.entrypoints: websecure traefik.http.routers.mealie-int.tls.certresolver: step-ca traefik.http.routers.mealie-int.service: mealie-svc traefik.http.middlewares.mealie-headers.headers.customrequestheaders.X-Forwarded-Proto: https traefik.http.services.mealie-svc.loadbalancer.server.port: 9000 Notes / Gotchas OIDC_USER_GROUP: family-friends limits OIDC login to members of that Authentik group. Users not in this group can be denied access even with valid Authentik credentials. Manage group membership in Authentik admin. The X-Forwarded-Proto: https header middleware is required for Mealie to generate correct OIDC redirect URIs. Without it, HTTPS URLs will not form correctly and the OIDC flow will fail. Mealie uses SQLite by default when no external database is configured. The database file lives in /app/data. Back up this directory before upgrades. mealieaddons is accessible at mealieaddons.home.local — this is the URL to configure in Mealie settings for the enhanced importer. Last Updated: 2026-06-17 07-homebox.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: Homebox tags: [homebox, inventory, home-management, authentik] Overview Homebox is a home inventory management app for tracking household items, their locations, warranties, and purchase records. The external route uses Authentik ForwardAuth for SSO. The internal route applies X-Forwarded-Proto only (Homebox manages its own session internally). Signups are disabled; registration is not allowed. Access Type URL Auth External https://homebox.jeeves5454.ddns.net Authentik ForwardAuth + GeoBlock + CrowdSec Internal https://homebox.home.local Homebox own session (Step-CA TLS) Configuration Image: ghcr.io/sysadminsmedia/homebox:latest Key Environment Variables Variable Value / Notes HBOX_MODE production HBOX_LOG_LEVEL info HBOX_LOG_FORMAT text HBOX_WEB_MAX_UPLOAD_SIZE 10 (MB) HBOX_OPTIONS_ALLOW_REGISTRATION false HBOX_OPTIONS_ALLOW_ANALYTICS false HBOX_STORAGE_PREFIX_PATH data TZ America/Toronto Traefik Labels traefik.http.routers.homebox-ext.rule: Host(`homebox.jeeves5454.ddns.net`) traefik.http.routers.homebox-ext.entrypoints: websecure traefik.http.routers.homebox-ext.tls.certresolver: letsencrypt traefik.http.routers.homebox-ext.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file,homebox-headers traefik.http.routers.homebox-ext.service: homebox-svc traefik.http.routers.homebox-int.rule: Host(`homebox.home.local`) traefik.http.routers.homebox-int.entrypoints: websecure traefik.http.routers.homebox-int.tls.certresolver: step-ca traefik.http.routers.homebox-int.middlewares: homebox-headers traefik.http.routers.homebox-int.service: homebox-svc traefik.http.middlewares.homebox-headers.headers.customrequestheaders.X-Forwarded-Proto: https traefik.http.services.homebox-svc.loadbalancer.server.port: 7745 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/homebox/data /data SQLite database and uploads Homebox uses SQLite ( homebox.db) stored in the /data bind mount with WAL journal mode and foreign key enforcement enabled. Notes / Gotchas The external route applies authentik-auth@docker ForwardAuth — all external access requires a valid Authentik session. The internal route skips ForwardAuth but applies the X-Forwarded-Proto header middleware (required for HTTPS-aware links and session cookies). HBOX_OPTIONS_ALLOW_REGISTRATION: false disables self-service account creation. New accounts must be created by an admin in the Homebox UI. Homebox stores photos and attachments in the /data bind mount alongside the SQLite database. Back up the entire directory before upgrades. Last Updated: 2026-06-17 08-medikeep.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: Medikeep tags: [medikeep, medical, health, postgres, authentik-sso] Overview Medikeep is a personal medical records tracker. It stores health records, prescriptions, appointments, and related documents. SSO is configured via Authentik (native OIDC integration — not Traefik ForwardAuth). The external route uses GeoBlock and CrowdSec; the internal route is TLS-only with no additional auth middleware. Access Type URL Auth External https://medical.jeeves5454.ddns.net Authentik OIDC + GeoBlock + CrowdSec Internal https://medikeep.home.local Authentik OIDC (Step-CA TLS) SSO is handled by Medikeep's built-in SSO_ENABLED: true — it redirects to Authentik on login. Traefik does not apply ForwardAuth to this service. Containers Container Image Role medikeep ghcr.io/afairgiant/medikeep:latest Web application medikeep-db postgres:15.8-alpine PostgreSQL database medikeep (application) Key environment variables: Variable Value / Notes SSO_ENABLED true SSO_PROVIDER_TYPE authentik SSO_ISSUER_URL https://auth.jeevesconsults.ca/application/o/medikeep/ SSO_CLIENT_ID EN44sdEMtf29bgTN077W48XsCSpi9bj1Wk0eypI1 SSO_CLIENT_SECRET REDACTED SSO_REDIRECT_URI https://medical.jeeves5454.ddns.net/auth/sso/callback DB_HOST medikeep-db DB_PORT 5432 DB_NAME medical_records DB_USER medapp DB_PASSWORD REDACTED LOG_LEVEL DEBUG LOG_ROTATION_METHOD logrotate ENABLE_API_DOCS false DEBUG false PUID / PGID 1000 TZ America/Toronto Bind mounts: Host Path Container Path Purpose /home/jeeves/docker/medikeep/uploads /app/uploads Document uploads /home/jeeves/docker/medikeep/logs /app/logs Application logs /home/jeeves/docker/medikeep/backups /app/backups Backup output medikeep-db (PostgreSQL 15.8) Image: postgres:15.8-alpine Variable Value POSTGRES_DB medical_records POSTGRES_USER medapp POSTGRES_PASSWORD REDACTED Bind mounts: Host Path Container Path /home/jeeves/docker/medikeep/postgres/data /var/lib/postgresql/data Traefik Labels traefik.http.routers.medikeep-ext.rule: Host(`medical.jeeves5454.ddns.net`) traefik.http.routers.medikeep-ext.entrypoints: websecure traefik.http.routers.medikeep-ext.tls.certresolver: letsencrypt traefik.http.routers.medikeep-ext.middlewares: plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.medikeep-ext.service: medikeep-svc traefik.http.routers.medikeep-int.rule: Host(`medikeep.home.local`) traefik.http.routers.medikeep-int.entrypoints: websecure traefik.http.routers.medikeep-int.tls.certresolver: step-ca traefik.http.routers.medikeep-int.service: medikeep-svc traefik.http.services.medikeep-svc.loadbalancer.server.port: 8000 Notes / Gotchas SSO_REDIRECT_URI points to the external domain — this is the OAuth callback URL registered in Authentik. Even when accessing via the internal home.local URL, the OIDC callback will redirect through the external domain. Medikeep's LOG_LEVEL is set to DEBUG — logs may be verbose. Logs are accessible in the bind-mounted /app/logs directory. Contains sensitive personal medical data. Limit backup exposure and ensure the PostgreSQL data directory is included in regular backup schedules. Last Updated: 2026-06-17 09-lubelogger.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: LubeLogger tags: [lubelogger, vehicles, maintenance, postgres] Overview LubeLogger is a vehicle maintenance and fuel log tracker. It stores service records, oil changes, tyre rotations, fuel fill-ups, and other vehicle events. External-only route with GeoBlock and CrowdSec — no internal home.local route. Authentication is LubeLogger's own built-in user system. Backend is PostgreSQL 16. Access Type URL Auth External https://logger.jeeves5454.ddns.net GeoBlock + CrowdSec (own auth) No internal route configured. Containers Container Image Role lubelogger ghcr.io/hargata/lubelogger:latest Web application lubelogger-db postgres:16 PostgreSQL database lubelogger (application) Image: ghcr.io/hargata/lubelogger:latest Runtime: .NET 10.0.8 (ASP.NET Core) Key environment variables: Variable Value / Notes POSTGRES_CONNECTION REDACTED (includes password) ASPNETCORE_HTTP_PORTS 8080 LC_ALL / LANG en_US.UTF-8 Bind mounts: Host Path Container Path Purpose /home/jeeves/docker/lubelogger/data /App/data App data and uploads /home/jeeves/docker/lubelogger/keys /root/.aspnet/DataProtection-Keys ASP.NET data protection keys lubelogger-db (PostgreSQL 16) Image: postgres:16 Variable Value POSTGRES_USER lubelogger POSTGRES_DB lubelogger POSTGRES_PASSWORD REDACTED Bind mounts: Host Path Container Path /home/jeeves/docker/lubelogger/db /var/lib/postgresql/data Traefik Labels traefik.http.routers.lubelogger.rule: Host(`logger.jeeves5454.ddns.net`) traefik.http.routers.lubelogger.entrypoints: websecure traefik.http.routers.lubelogger.tls.certresolver: letsencrypt traefik.http.routers.lubelogger.middlewares: plex-geoblock@file,crowdsec-bouncer@file,lubelogger-headers traefik.http.middlewares.lubelogger-headers.headers.customrequestheaders.X-Forwarded-Proto: https traefik.docker.network: traefik-net traefik.http.services.lubelogger.loadbalancer.server.port: 8080 Notes / Gotchas POSTGRES_CONNECTION in the container environment contains the database password in plaintext — treat this as sensitive and avoid logging the environment of this container. The DataProtection-Keys bind mount ( /lubelogger/keys) must persist across container restarts. If it is lost, all active user sessions will be invalidated and users will need to log in again. The X-Forwarded-Proto: https middleware is required for ASP.NET Core to correctly identify requests as HTTPS when behind a reverse proxy. Last Updated: 2026-06-17 10-hortusfox.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: HortusFox tags: [hortusfox, plants, garden, mariadb] Overview HortusFox is a plant care and garden management app. It tracks plants, watering schedules, fertilisation, and overdue care tasks. A separate cron container triggers scheduled task checks. Internal-only access via Step-CA TLS — no external route. The workspace is named "Jeeves Garden". Access Type URL Auth Internal https://hortusfox.home.local HortusFox own auth (Step-CA TLS) No external route — LAN access only. Containers Container Image Role hortusfox ghcr.io/danielbrendel/hortusfox-web:latest Web application hortusfox-db mariadb:11 MariaDB database hortusfox-cron alpine:latest Scheduled task runner hortusfox (application) Runtime: PHP 8.3 on Apache Key environment variables: Variable Value / Notes APP_WORKSPACE Jeeves Garden APP_TIMEZONE America/Toronto APP_LANG en APP_DEBUG true APP_ADMIN_EMAIL jeeves5454@gmail.com APP_OVERDUE_TASK_HOURS 10 APP_ENABLE_HISTORY true APP_HISTORY_NAME Garden Log APP_ENABLE_SYSTEM_MESSAGES true APP_ENABLE_PHOTO_SHARE false APP_ENABLE_CHAT false APP_ENABLE_SCROLLER true APP_CRON_PW REDACTED DB_HOST hortusfox-db DB_PORT 3306 DB_DATABASE hortusfox DB_USERNAME hortusfox DB_CHARSET utf8mb4 Bind mounts: Host Path Container Path Purpose /home/jeeves/docker/hortusfox/images /var/www/html/public/img Plant images /home/jeeves/docker/hortusfox/themes /var/www/html/public/themes Custom themes /home/jeeves/docker/hortusfox/logs /var/www/html/app/logs App logs /home/jeeves/docker/hortusfox/migrations /var/www/html/app/migrations DB migration files /home/jeeves/docker/hortusfox/backup /var/www/html/public/backup Export/backup files hortusfox-db (MariaDB 11) Image: mariadb:11 (MariaDB 11.8.6) Variable Value MYSQL_DATABASE hortusfox MYSQL_USER hortusfox MYSQL_PASSWORD REDACTED MYSQL_ROOT_PASSWORD REDACTED Bind mounts: Host Path Container Path /home/jeeves/docker/hortusfox/db /var/lib/mysql hortusfox-cron (Alpine) Image: alpine:latest A lightweight Alpine container that runs periodic cron jobs to trigger HortusFox's scheduled task processing (e.g., overdue task notifications). It communicates with the main hortusfox container using the APP_CRON_PW credential. No persistent volumes. Traefik Labels traefik.http.routers.hortusfox.rule: Host(`hortusfox.home.local`) traefik.http.routers.hortusfox.entrypoints: websecure traefik.http.routers.hortusfox.tls.certresolver: step-ca traefik.http.services.hortusfox.loadbalancer.server.port: 80 Notes / Gotchas APP_CRON_PW authenticates the cron container's requests to the HortusFox web app. This is set as a plaintext environment variable — treat it as a secret. It is REDACTED in documentation. APP_DEBUG: true means verbose PHP error output is enabled. Disable this if HortusFox ever becomes accessible externally. Plant images are stored in the images bind mount. This directory should be included in backups to preserve photo history. Last Updated: 2026-06-17 11-linkstack.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: LinkStack tags: [linkstack, linktree, about-me, public, website] Overview LinkStack is a self-hosted link-in-bio / personal landing page, serving as the public-facing "about me" page and link hub for jeevesconsults.ca. It responds to two separate domain names on a single Traefik route: the primary aboutme.jeevesconsults.ca and the root www.jeevesconsults.ca. Access URL Auth https://aboutme.jeevesconsults.ca Public — no auth https://www.jeevesconsults.ca Public — no auth Both hostnames resolve to the same LinkStack instance. No authentication middleware — this is a public-facing page. Configuration Image: linkstackorg/linkstack:latest Environment Variables Variable Value / Notes HTTPS_SERVER_NAME aboutme.jeevesconsults.ca SERVER_ADMIN ask@jeevesconsults.ca PHP_MEMORY_LIMIT 512M UPLOAD_MAX_FILESIZE 8M TZ America/Toronto Traefik Labels traefik.http.routers.linkstack.rule: Host(`aboutme.jeevesconsults.ca`) || Host(`www.jeevesconsults.ca`) traefik.http.routers.linkstack.entrypoints: websecure traefik.http.routers.linkstack.tls.certresolver: letsencrypt traefik.http.services.linkstack.loadbalancer.server.port: 80 The || in the Traefik host rule matches either domain — both resolve to the same container and present the same LinkStack profile. Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/linkstack/data /htdocs App files, config, SQLite Notes / Gotchas HTTPS_SERVER_NAME is set to aboutme.jeevesconsults.ca. Even though both domains are handled by Traefik, LinkStack internally uses this value for canonical URL generation. Links may contain this hostname. No GeoBlock or CrowdSec on this route — it is intentionally fully public. Monitor access logs if abuse is suspected. LinkStack stores its own SQLite database and assets under /htdocs. The entire data/ bind mount must be backed up to preserve the profile content. Letsencrypt will issue certificates for both aboutme.jeevesconsults.ca and www.jeevesconsults.ca via the same Traefik router — both must resolve to Centerpoint's public IP in DNS for certificate issuance to succeed. Last Updated: 2026-06-17 12-reitti.md kstack: book: Centerpoint Home Lab chapter: Documents & Organization page: Reitti tags: [reitti, fitness, activity-tracking, postgis, rabbitmq, redis] Overview Reitti is a self-hosted activity and fitness tracking platform. It records GPS tracks, check-ins, and location history and displays them on interactive maps. The platform is a 6-container stack: the core Java application, a PostGIS spatial database, Redis cache, RabbitMQ message queue, a tile proxy for map tiles, and Photon for geocoding. External access is at https://reitti.jeeves5454.ddns.net. Check-in data from Foursquare/Swarm is synced into Reitti via the separate swarm-reitti-bridge service (see next page). Access Type URL Auth External https://reitti.jeeves5454.ddns.net Reitti own auth + letsencrypt No internal home.local route — external-only. Containers Container Image Role reitti-app dedicatedcode/reitti:latest Core Java application reitti-postgis postgis/postgis:17-3.5-alpine Spatial database (PostgreSQL 17 + PostGIS 3.5) reitti-redis redis:7-alpine Cache and session store reitti-rabbitmq rabbitmq:3-management-alpine Message queue for async jobs reitti-tiles nginx:alpine Map tile caching proxy reitti-photon rtuszik/photon-docker:1.3.0 Local geocoding (Nominatim-based) reitti-app The main Spring Boot application serving the Reitti web UI and REST API. Ingests location data via the OwnTracks-compatible endpoint used by swarm-reitti-bridge. Key environment variables: Variable Value / Notes POSTGIS_HOST postgis POSTGIS_PORT 5432 POSTGIS_DB reittidb POSTGIS_USER reitti POSTGIS_PASSWORD REDACTED REDIS_HOST redis REDIS_PORT 6379 RABBITMQ_HOST rabbitmq RABBITMQ_PORT 5672 RABBITMQ_USER reitti RABBITMQ_PASSWORD REDACTED PHOTON_BASE_URL http://photon:2322 Volumes: Volume / Mount Container Path Purpose Docker volume reitti_reitti-data /data Application data and uploads Traefik labels: traefik.http.routers.reitti.rule: Host(`reitti.jeeves5454.ddns.net`) traefik.http.routers.reitti.entrypoints: websecure traefik.http.routers.reitti.tls.certresolver: letsencrypt traefik.http.services.reitti.loadbalancer.server.port: 8080 reitti-postgis PostgreSQL 17 with PostGIS 3.5 spatial extension. Stores all GPS track data, waypoints, and geospatial indices. Image: postgis/postgis:17-3.5-alpine Variable Value POSTGRES_USER reitti POSTGRES_DB reittidb POSTGRES_PASSWORD REDACTED Volumes: Volume Container Path reitti_postgis-data (named) /var/lib/postgresql/data reitti-redis Redis 7 (Alpine) — used for caching and session management. Image: redis:7-alpine Volumes: Volume Container Path reitti_redis-data (named) /data No authentication configured — network-isolated to the Reitti internal stack network. reitti-rabbitmq RabbitMQ 3 with management plugin. Handles async processing of imported tracks and location events. Image: rabbitmq:3-management-alpine Variable Value RABBITMQ_DEFAULT_USER reitti RABBITMQ_DEFAULT_PASS REDACTED Volumes: Volume Container Path reitti_rabbitmq-data (named) /var/lib/rabbitmq RabbitMQ management UI is available internally on port 15672 (not exposed via Traefik). reitti-tiles NGINX-based map tile proxy/cache. Serves map tiles from upstream tile providers with local caching to reduce external requests. Image: nginx:alpine (NGINX 1.29.8) Variable Value NGINX_CACHE_SIZE 1g No persistent volumes — tile cache is in-memory. No external Traefik route. reitti-photon Local geocoding service using the Photon engine (Nominatim/OSM-based). Configured for the ca (Canada) region with parallel update strategy. Image: rtuszik/photon-docker:1.3.0 Variable Value REGION ca UPDATE_STRATEGY PARALLEL Volumes: Volume Container Path reitti_photon-data (named) /photon/data The photon data volume holds the downloaded Nominatim extract for the CA region. It can be large (several GB); allow time for the initial data download on first start. Data Flow GPS device / OwnTracks → Reitti OwnTracks ingest API Foursquare/Swarm check-ins → swarm-reitti-bridge → Reitti OwnTracks ingest API ↓ reitti-app (Spring Boot) ↓ RabbitMQ (async processing) ↓ PostGIS (spatial storage) ↓ Redis (cache) + Photon (geocoding) ↓ Web UI / Map tiles Notes / Gotchas All named Docker volumes ( reitti_reitti-data, reitti_postgis-data, reitti_redis-data, reitti_rabbitmq-data, reitti_photon-data) must be included in any backup strategy — none are bind-mounted to host paths. PostGIS 17 requires spatial extensions to be enabled in the database on first run — dedicatedcode/reitti:latest handles this automatically on startup. The Photon geocoding data download is region-specific ( REGION: ca). If geocoding stops working after a restart, check that the photon data volume is intact and the photon container started successfully. RabbitMQ management UI (port 15672) is available inside the Docker network but is not exposed externally. Access via docker exec or Portainer console if needed. Check-in data from Foursquare/Swarm flows through swarm-reitti-bridge (see next page). Reitti itself has no Foursquare integration — the bridge translates and delivers data via the OwnTracks ingestion endpoint. Last Updated: 2026-06-17 Swarm-Reitti Bridge Overview Swarm-Reitti Bridge (v1.1.0) is a custom-built Node.js service that automatically syncs Foursquare/Swarm check-ins into two downstream services: Reitti — self-hosted location history tracker, receiving check-ins in OwnTracks format AdventureLog — self-hosted travel journal, receiving check-ins as Locations, Visits, and World Travel region marks It was built specifically for this homelab as a bridge between the Foursquare API and these self-hosted services — keeping all location history under local control. The service runs at https://swarm.jeeves5454.ddns.net and is externally reachable so the OAuth callback from Foursquare can complete. This service was built in this homelab environment. Its source code lives at /home/jeeves/docker/code-server/projects/swarm-reitti-bridge/ and the production deployment at /home/jeeves/docker/swarmreitti/swarm-reitti-bridge/. The container image ( swarm-reitti-bridge) is built locally via docker compose build and is not published to any registry. Background: Why Polling Mode? This bridge was originally designed to use Foursquare push notifications — Foursquare would POST to the bridge's /push endpoint on every check-in, enabling near-instant syncing. That architecture worked correctly. In late 2024 / early 2025, Foursquare changed their API pricing and moved push notifications behind a paid credit system. Attempting to subscribe to push notifications returns: { "errorType": "credits_exhausted", "code": 402 } The bridge was updated to polling mode as the solution. Instead of waiting for Foursquare to push, it actively polls the Foursquare API every 15 minutes for new check-ins. Aspect Push (legacy, broken) Polling (current) Trigger Foursquare → Bridge (instant) Bridge → Foursquare (timer) Latency < 1 second Up to 15 minutes API credits required Yes (paid) No (free tier) API calls/day ~0 idle 96 (10% of free quota) Cost Requires payment $0.00 The 15-minute maximum latency is entirely acceptable for personal location history tracking. How It Works High-Level Flow User checks in on Swarm ↓ Foursquare API stores check-in ↓ Bridge polls every 15 minutes (or immediately on container start) GET /v2/users/self/checkins?oauth_token=... ↓ Bridge identifies new check-ins (deduplication via lastCheckinIds) ↓ ┌────────────────────────────────────────────────────────┐ │ For each new check-in (oldest-first): │ │ │ │ 1. Convert to OwnTracks format │ │ POST to Reitti ingest API │ │ Authorization: Bearer │ │ ↓ │ │ Check-in on Reitti map │ │ │ │ 2. (if AdventureLog configured) │ │ Find or create AL Category (Foursquare → emoji) │ │ Find or create AL Location (dedup by name + coords) │ │ Create AL Visit (dedup: one per location per day) │ │ Mark AL World Travel region as visited │ │ ↓ │ │ Location + Visit in AdventureLog │ │ Region marked on world map │ └────────────────────────────────────────────────────────┘ ↓ State saved to disk (lastCheckinId, lastCheckinTimestamp) AdventureLog failures are non-fatal — Reitti sync completes regardless. Deduplication The bridge tracks lastCheckinIds per user — a map of userId → most recently processed check-in ID. On each poll: Quick check — fetches the single most recent check-in from Foursquare. If its ID matches lastCheckinIds[userId], the poll exits immediately (already up to date). Gap fetch — if different, fetches all check-ins within a 2-week window (paginated, 50 per page), sorted oldest-first so Reitti receives events in chronological order. Startup Behaviour On container start, the bridge immediately polls for any users already stored in state.json — it does not wait for the first 15-minute interval. This ensures check-ins that occurred while the container was down are processed within seconds of restart. State Persistence Three state objects are saved to /app/data/state.json: Key Contents userTokens OAuth access tokens per userId (from Foursquare) lastCheckinTimestamps Last seen check-in Unix timestamp per userId lastCheckinIds Last processed check-in ID per userId Writes are atomic (write to .tmp, then rename) to avoid corruption on crash. Permission note: state.json is owned by root (written inside the container). Use sudo when editing on the host. Data Mapping Reitti (OwnTracks Format) OwnTracks Field Source Notes _type Hardcoded: "location" OwnTracks type identifier lat checkin.venue.location.lat Venue latitude lon checkin.venue.location.lng Venue longitude ( lng → lon) tst checkin.createdAt Unix timestamp of check-in tid First 2 chars of username, uppercased OwnTracks tracker ID acc Hardcoded: 10 Accuracy in metres alt Hardcoded: 0 Altitude (not available from Foursquare) batt Hardcoded: 100 Battery (not applicable) vel Hardcoded: 0 Velocity (not applicable) t Hardcoded: "c" OwnTracks trigger type: check-in desc checkin.venue.name (max 200 chars) Venue name addr Address + City + State + Country (max 300 chars) Formatted address string POST Authorization: Bearer Content-Type: application/json AdventureLog Integration Category Mapping The primary Foursquare category maps to an AdventureLog category (created if absent) Category icons are resolved from a static Foursquare → emoji lookup table ( adventurelog-categories.js); unknown categories fall back to 🌍 Secondary Foursquare categories become AdventureLog tags Location Deduplication Locations are matched by venue name (lowercase) + coordinates rounded to 2 decimal places (≈1.1 km tolerance). If a match exists, it is reused without modification. If not, a new location is created. Coordinates sent to AdventureLog are rounded to 6 decimal places (AdventureLog enforces a 9-total-digit limit on lat/lng fields). Visit Deduplication One visit is created per location per local calendar day. The local date is derived using the venue's timezone (resolved from lat/lng via geo-tz). A check-in shout and any companion names ( with array) are combined into the visit notes field. World Travel (Region Marking) After each visit is created, the bridge resolves the check-in's country and state/province to an ISO 3166-2 region code via AdventureLog's GET /api/regions list, then POSTs to /api/visitedregion. Already-visited regions are cached per poll cycle and skipped silently. A small country name map handles known Foursquare → AdventureLog naming mismatches (e.g. México → Mexico). Unresolvable regions are logged as warnings. Note: Creating Locations and Visits via the API does not automatically update the World Travel world map — these are separate systems in AdventureLog. The bridge handles both independently. OAuth Setup Foursquare OAuth 2.0 is used to grant the bridge permission to read check-in data. This is a one-time setup that persists in the state file. Initial Setup Steps Visit the auth URL: https://swarm.jeeves5454.ddns.net/auth The bridge redirects to Foursquare with the configured client_id and redirect_uri. Authenticate with your Foursquare account and authorise the app. Foursquare redirects back to https://swarm.jeeves5454.ddns.net/callback with an authorisation code. The bridge exchanges the code for an access token, saves it to state, and begins polling immediately. Important: Re-authenticating via /auth resets the sync baseline to the most recent check-in at that moment — older unsynced check-ins will be skipped. Only re-authenticate if starting fresh or after a token expiry. If you need to reprocess past check-ins, patch state.json directly (see Operations below). Foursquare App Configuration Field Value Redirect URI https://swarm.jeeves5454.ddns.net/callback Push API URL (not required — polling mode) Access and Endpoints External URL: https://swarm.jeeves5454.ddns.net Certificate: Let's Encrypt (letsencrypt resolver) Port: 3000 (internal) API Endpoints Method Path Purpose GET / Status dashboard — service cards, recent check-ins, recent errors GET /auth Start Foursquare OAuth flow (rate-limited: 10/15 min) GET /callback OAuth callback (Foursquare redirects here) GET /health JSON health — ok, per-service status, last sync times POST /push Legacy webhook endpoint (inactive in polling mode) The /health endpoint returns structured JSON including an ok: true/false field suitable for Uptime Kuma's JSON Query monitor: { "ok": true, "status": "healthy", "connectedUsers": 1, "reitti": { "configured": true, "lastSuccessAt": "...", "lastSuccessVenue": "..." }, "adventurelog": { "configured": true, "lastSuccessAt": "...", "lastSuccessVenue": "..." }, "lastPollAt": "..." } Configuration Environment Variables Variable Value / Notes FOURSQUARE_CLIENT_ID REDACTED (Foursquare developer console) FOURSQUARE_CLIENT_SECRET REDACTED FOURSQUARE_REDIRECT_URI https://swarm.jeeves5454.ddns.net/callback REITTI_API_URL https://reitti.jeeves5454.ddns.net/api/v1/ingest/owntracks REITTI_API_TOKEN REDACTED (Reitti API bearer token) PUSH_SECRET REDACTED (session signing secret) ADVENTURELOG_API_URL https://travel.jeeves5454.ddns.net ADVENTURELOG_API_KEY REDACTED (AdventureLog API key) UPTIME_KUMA_PUSH_URL REDACTED (Uptime Kuma push monitor URL, no query params) POLLING_INTERVAL_MINUTES 15 (default) PORT 3000 NODE_ENV production Omitting both ADVENTURELOG_API_URL and ADVENTURELOG_API_KEY disables AdventureLog sync entirely. Omitting UPTIME_KUMA_PUSH_URL disables push heartbeats. Traefik Labels traefik.http.routers.swarm-bridge.rule: Host(`swarm.jeeves5454.ddns.net`) traefik.http.routers.swarm-bridge.entrypoints: websecure traefik.http.routers.swarm-bridge.tls.certresolver: letsencrypt traefik.http.services.swarm-bridge.loadbalancer.server.port: 3000 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/swarmreitti/swarm-reitti-bridge/data /app/data State file ( state.json) persisted across restarts Monitoring (Uptime Kuma) Three complementary monitors are recommended: Monitor Type Target What it catches Docker Container Container name swarm-reitti-bridge Container crash / OOM kill HTTP(s) https://swarm.jeeves5454.ddns.net/health App unresponsive HTTP(s) JSON Query https://swarm.jeeves5454.ddns.net/health → $.ok Reitti/AL sync errors, no users Push Uptime Kuma push monitor URL Poll loop stopped, token expired The push monitor receives ?status=up after every successful poll and ?status=down&msg= on failure. Configure the heartbeat interval to POLLING_INTERVAL_MINUTES + 2 minutes (e.g. 17 minutes for a 15-minute poll interval). Logging The bridge uses structured JSON logging to stdout (Dozzle-compatible): {"ts":"2026-06-23T12:00:00.000Z","level":"INFO","cat":"POLL","msg":"Scheduled poll","userCount":1} {"ts":"2026-06-23T12:00:01.000Z","level":"INFO","cat":"CHECKIN","msg":"Sent to Reitti","venue":"Tim Hortons"} {"ts":"2026-06-23T12:00:02.000Z","level":"INFO","cat":"AL","msg":"Location created","venue":"Tim Hortons","id":"..."} {"ts":"2026-06-23T12:00:02.000Z","level":"INFO","cat":"AL","msg":"Visit created","locationId":"...","date":"2026-06-23"} {"ts":"2026-06-23T12:00:02.000Z","level":"INFO","cat":"AL","msg":"Region marked as visited","regionId":"CA-ON"} {"ts":"2026-06-23T12:00:03.000Z","level":"INFO","cat":"STATE","msg":"State saved","usersCount":1} Log categories: SERVER, POLL, CHECKIN, AL, AUTH, STATE. WARN and ERROR entries are also captured in an in-memory ring buffer (last 20) and displayed on the status dashboard at /. View live logs: docker logs -f swarm-reitti-bridge Operations and Management Verify the Bridge is Running # Container status docker ps | grep swarm-reitti-bridge # Health check (includes ok field and per-service status) curl -s https://swarm.jeeves5454.ddns.net/health | jq . # Status dashboard open https://swarm.jeeves5454.ddns.net/ Re-authenticate with Foursquare If the OAuth token expires or becomes invalid (bridge logs show Token expired WARN): Visit https://swarm.jeeves5454.ddns.net/auth Log in and authorise the app The bridge saves the new token and polls immediately Do not re-authenticate to retry past check-ins — it will move the sync baseline forward and those check-ins will be permanently skipped. Reprocess Past Check-ins To force the bridge to reprocess a specific check-in (e.g. after fixing an AL sync error): cd /home/jeeves/docker/swarmreitti/swarm-reitti-bridge # View current state sudo cat data/state.json # Roll back to just before a specific check-in Unix timestamp sudo python3 -c " import json with open('data/state.json') as f: s = json.load(f) s['lastCheckinIds'][''] = None s['lastCheckinTimestamps'][''] = with open('data/state.json', 'w') as f: json.dump(s, f, indent=2) print('Done') " docker compose restart swarm-reitti-bridge The startup poll will immediately pick up the check-in on restart. Adjust Polling Interval Edit POLLING_INTERVAL_MINUTES in the compose environment and rebuild: Interval API calls/day Use case 5 288 More responsive (30% quota) 15 96 Recommended (10% quota) 30 48 Most conservative (5% quota) Rebuild the Container cd /home/jeeves/docker/swarmreitti/swarm-reitti-bridge docker compose up -d --build docker logs swarm-reitti-bridge --follow Troubleshooting Check-ins not appearing in Reitti Check the status dashboard at / — the Reitti card shows last push time and any error message Check logs: docker logs swarm-reitti-bridge | grep -E '"cat":"CHECKIN"|"level":"ERROR"' Verify token is connected: curl -s https://swarm.jeeves5454.ddns.net/health | jq .connectedUsers — if 0, re-authenticate via /auth Test Reitti API directly: curl -s -o /dev/null -w "%{http_code}" -X POST \ https://reitti.jeeves5454.ddns.net/api/v1/ingest/owntracks \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"_type":"location","lat":43.65,"lon":-79.37,"tst":1782088106}' Expected: 200. If 401, the token in .env is wrong. Check-ins not appearing in AdventureLog Check the status dashboard / — the AdventureLog card shows last sync and any error Verify AdventureLog API key is valid: curl -s "https://travel.jeeves5454.ddns.net/api/locations?limit=1" \ -H "X-API-Key: " Expected: JSON with count field. If 403, the key is wrong. Common error — trailing slash causes 308 redirect with lost body: the bridge strips trailing slashes automatically; if seeing 400s verify the AL URL has no trailing slash in ADVENTURELOG_API_URL World Travel region not updating The bridge only marks regions when venue.location.state and venue.location.country are both present in the Foursquare data. Venues with missing location data are skipped. If a region resolves but the country/state name doesn't match AdventureLog's English names, a WARN log is emitted: Could not resolve region. Add a mapping to FOURSQUARE_COUNTRY_MAP in index.js if needed. GET /api/visitedregion can confirm which regions are already marked. OAuth callback fails (redirect URI mismatch) Ensure FOURSQUARE_REDIRECT_URI in env exactly matches the redirect URI registered in the Foursquare developer console: https://swarm.jeeves5454.ddns.net/callback Bridge container exits immediately Check logs: docker logs swarm-reitti-bridge Most common cause: missing required environment variable. The app logs all missing vars and exits with code 1. State file corruption sudo rm /home/jeeves/docker/swarmreitti/swarm-reitti-bridge/data/state.json docker restart swarm-reitti-bridge # Then re-authenticate via /auth Source Code /home/jeeves/docker/code-server/projects/swarm-reitti-bridge/ ← development /home/jeeves/docker/swarmreitti/swarm-reitti-bridge/ ← production deployment ├── index.js ← Main application ├── adventurelog-categories.js ← Foursquare category → emoji icon map ├── Dockerfile ├── docker-compose.yml ├── package.json ← version 1.1.0 ├── tests/ │ ├── integration.test.js │ └── unit.test.js └── data/ └── state.json ← Runtime state (OAuth tokens, last check-in IDs) Last Updated: 2026-06-23 AdventureLog - Travel Helper Overview Travel Helper is a Python-based sidecar service for AdventureLog. It runs on a weekly schedule and automatically links Locations (based on their Visits) to Collections, populates day-by-day itinerary entries, and adds region-level Locations as Trip Context. It is idempotent — re-running it never creates duplicates. It also exposes a status web page at https://travel-helper.home.local showing last run time, run statistics, live log output, and a manual trigger button. Why It Exists AdventureLog Collections support itinerary entries and linked Locations, but provides no automatic way to connect existing Visits to a Collection by date range. Without this service, every Collection must be linked manually, location by location. Travel Helper solves this by scanning all Collections that have a start_date and end_date, finding every Location that has a Visit falling within that range, and wiring everything together automatically. What It Does Each Run Loads all regions, locations (with embedded visits), and collections from AdventureLog. For each Collection with a date range: Finds all Locations whose Visit dates (converted to local timezone) fall within the collection's start–end range. Links those Locations to the Collection ( PATCH /api/locations/{id}). Adds a day-level itinerary entry per visit date ( POST /api/itineraries, is_global: false). Resolves the region for each matched Location (from cache → AdventureLog region field → Nominatim reverse geocode fallback). Finds or creates a region-level Location (centroid of the region). Links that region Location to the Collection and adds it as Trip Context ( POST /api/itineraries, is_global: true). Persists the region ↔ location ID cache to /data/region_cache.json so Nominatim is only called for locations not yet resolved. Access Type URL Internal https://travel-helper.home.local External Not exposed The status page auto-refreshes every 60 seconds when idle, every 10 seconds while a sync is running. Configuration Image: travel-helper:latest (built locally — see Maintenance) Source: /home/jeeves/docker/adventurelog/travel-helper/ Bind mount: /home/jeeves/docker/adventurelog/travel-helper/data → /data Key environment variables (set in Portainer stack environment table): Variable Value / Notes ADVENTURELOG_API_URLS Comma-separated, tried left-to-right. First reachable URL wins. http://adventurelog-web:3000,https://travel.home.local,https:// ADVENTURELOG_API_KEY Set in Portainer — never hardcode. Rotate here when key changes. NOMINATIM_USER_AGENT Must include a contact email per Nominatim usage policy. NOMINATIM_DELAY Seconds between Nominatim requests. Default 3.0. Do not lower. CACHE_FILE /data/region_cache.json CRON_DAY Day of week for scheduled run. Default mon. CRON_HOUR UTC hour for scheduled run. Default 3. STATUS_PORT Internal port for Flask status page. Default 80. Networks: adventurelog-internal — direct container-to-container access to AdventureLog traefik-net — exposes the status page through Traefik Traefik labels: - "traefik.enable=true" - "traefik.http.routers.travel-helper-int.rule=Host(`travel-helper.home.local`)" - "traefik.http.routers.travel-helper-int.entrypoints=websecure" - "traefik.http.routers.travel-helper-int.tls=true" - "traefik.http.routers.travel-helper-int.tls.certresolver=step-ca" - "traefik.http.services.travel-helper.loadbalancer.server.port=80" Dependencies Service Role adventurelog-web AdventureLog Next.js frontend (port 3000) — primary API target adventurelog-server Django backend — reached indirectly via Next.js proxy Nominatim (external) Reverse geocoding for region resolution. Rate-limited at 1 req/3s Traefik Routes travel-helper.home.local to the status page URL Resolution (Internal-First) On each run, Travel Helper probes ADVENTURELOG_API_URLS in order and uses the first URL that returns HTTP 200, 201, 401, or 403. This means: http://adventurelog-web:3000 is tried first (direct, never leaves host) https://travel.home.local is tried second (through Traefik internally) The external URL is a last resort only The resolved URL is shown on the status page. If the internal network is healthy, you should always see http://adventurelog-web:3000 displayed there. Nominatim Usage Nominatim is only called when a Location's region cannot be resolved from: The in-memory cache (populated from region_cache.json on startup) The region field already set on the AdventureLog Location record On first run, most locations are resolved from the existing geocode_cache.json (generated during the original Swarm import) without hitting Nominatim at all. Subsequent runs only call Nominatim for newly imported locations. Requests use accept-language=en and zoom=10 to return English region names at state/province level. Rate limiting: 3-second base delay with exponential backoff (3s → 12s → 48s) on HTTP 429. Gotchas No trailing slashes on AdventureLog API URLs. AdventureLog returns 308 Permanent Redirect for URLs ending in /. HTTP clients drop the POST body on 308 redirects, causing silent 400 Bad Request failures. All endpoints in this service omit trailing slashes. adventurelog-web listens on port 3000, not 8000. Port 8000 is exposed by adventurelog-server (Django), but API calls must go through the Next.js frontend which proxies /api/ to the backend. Collections must have both start_date and end_date to be processed. Collections with only one date or no dates are silently skipped. Visit dates are converted to local timezone before matching. A visit at 11 pm in Toronto is June 22 locally but June 23 in UTC. The visit's IANA timezone field is used for this conversion. If the field is absent, UTC is used as a fallback. loc_collections is maintained in-memory across collections in one run. This prevents overwriting a location's collection list when it matches multiple collections in the same run. The itinerary content_type for Location is 24. This is an internal AdventureLog FK constant. If a future AdventureLog update changes this, the constant LOCATION_CONTENT_TYPE = 24 in main.py must be updated. API key expiry. The AdventureLog API key can be rotated in AdventureLog under User Settings → API Tokens. Update it in Portainer's environment variable table for the AdventureLog stack — never in any file. Status Page Features Available at https://travel-helper.home.local: Section Contents Service Status Running/Idle badge, resolved AdventureLog URL, last/next run times Last Run Results Collections matched, locations linked, itinerary entries, Trip Context entries, error count Errors — Last Run Timestamped list of all ERROR-level log entries from the last run Live Log Last 80 log lines, newest first, auto-refreshes Run Now button Triggers an immediate sync in a background thread GET /health returns a JSON summary suitable for uptime monitoring. Maintenance Rebuilding the image after code changes docker build -t travel-helper:latest /home/jeeves/docker/adventurelog/travel-helper Then redeploy the AdventureLog stack in Portainer. Portainer uses the pre-built travel-helper:latest image — it does not build from source. Rotating the API key Generate a new token in AdventureLog → User Settings → API Tokens Update ADVENTURELOG_API_KEY in the AdventureLog stack's environment variable table in Portainer Redeploy the stack Clearing the region cache Delete /home/jeeves/docker/adventurelog/travel-helper/data/region_cache.json. The next run will rebuild it from AdventureLog region fields and Nominatim (expect the run to take longer and make Nominatim calls). Adjusting the schedule Change CRON_DAY and CRON_HOUR in Portainer environment variables and redeploy. Times are UTC. Checking logs outside the status page docker logs travel-helper --tail 100 -f How It Was Built Travel Helper was developed iteratively during a session in June 2026 as a companion to the Swarm → AdventureLog bulk import ( import_swarm.py). The original import created ~7,000 Locations and ~10,000 Visits but left Collections unlinked. Key design decisions: Python + APScheduler for the weekly cron, matching the import toolchain Flask for the status page, styled to match the Swarm-Reitti Bridge dashboard Internal-first URL probing to avoid unnecessary external traffic; the service lives on adventurelog-internal so direct container access is always preferred Full re-scan on every run (idempotent) rather than incremental sync, to avoid state management complexity region_cache.json pre-seeded from the Swarm import's geocode_cache.json so that ~95% of locations resolve without any Nominatim calls on first run Last Updated 2026-06-24 h07-finance 00-chapter-intro.md kstack: book: Centerpoint Home Lab chapter: Finance page: Chapter Introduction tags: [finance, subscriptions, expenses, investments, productivity] Overview This chapter covers personal finance and expense-tracking services. These tools handle subscription management, shared expense splitting, and investment portfolio tracking. Services in This Chapter Service Container(s) Purpose Wallos wallos Subscription and recurring expense tracker SplitPro splitpro, splitpro-db Shared expense splitting (Splitwise-alternative) Ghostfolio ghostfolio, ghostfolio-postgres, ghostfolio-redis Open-source wealth management and portfolio tracker Last Updated: 2026-06-17 01-wallos.md kstack: book: Centerpoint Home Lab chapter: Finance page: Wallos tags: [wallos, subscriptions, finance, sqlite] Overview Wallos is a self-hosted subscription and recurring payment tracker. It tracks monthly, annual, and custom-interval subscriptions, calculates total spending, and provides a visual dashboard of recurring costs. Data is stored in a SQLite database. Authentication is Wallos' own built-in user system — no Authentik integration. External-only route. Access Type URL Auth External https://wallos.jeevesconsults.ca GeoBlock + CrowdSec (own auth) No internal home.local route configured. Configuration Image: bellamy/wallos:latest Runtime: PHP 8.3 Traefik Labels traefik.http.routers.wallos.rule: Host(`wallos.jeevesconsults.ca`) traefik.http.routers.wallos.entrypoints: websecure traefik.http.routers.wallos.tls.certresolver: letsencrypt traefik.http.routers.wallos.middlewares: plex-geoblock@file,crowdsec-bouncer@file,wallos-headers traefik.http.middlewares.wallos-headers.headers.customrequestheaders.X-Forwarded-Proto: https traefik.http.services.wallos.loadbalancer.server.port: 80 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/wallos/db /var/www/html/db SQLite database /home/jeeves/docker/wallos/logos /var/www/html/images/uploads/logos Subscription logos Wallos stores all data in a SQLite file within the db bind mount. No external database dependency. Notes / Gotchas Wallos manages its own user accounts — no SSO or Authentik integration. Credentials are stored in the SQLite database. The X-Forwarded-Proto: https middleware is required for PHP to detect HTTPS correctly when behind a reverse proxy. Service logos are persisted in the logos bind mount. Both db/ and logos/ must be backed up together to fully restore the instance. No currency exchange rate API key is required by default — Wallos supports manual rate entry. Last Updated: 2026-06-17 02-splitpro.md kstack: book: Centerpoint Home Lab chapter: Finance page: SplitPro tags: [splitpro, expenses, splitting, authentik, postgres, step-ca] Overview SplitPro is a self-hosted shared expense splitting app — an open-source alternative to Splitwise. It tracks group expenses, calculates balances, and supports multi-currency via the Frankfurter exchange rate provider. Authentik OIDC provides authentication. Email invites are handled via Gmail SMTP. The Step-CA root certificate is injected so SplitPro can make HTTPS calls to internal *.home.local services. External-only route. Access Type URL Auth External https://splitpro.jeevesconsults.ca Authentik OIDC + GeoBlock + CrowdSec SplitPro handles its own OIDC redirect to Authentik — Traefik does not apply authentik-auth@docker ForwardAuth. Authentication is managed natively by the Next.js next-auth library. Containers Container Image Role splitpro ossapps/splitpro:latest Web application splitpro-db ossapps/postgres:17.7-trixie PostgreSQL 17 database splitpro (application) Runtime: Node.js 22 (Next.js) Key environment variables: Variable Value / Notes NEXTAUTH_URL https://splitpro.jeevesconsults.ca NEXTAUTH_SECRET REDACTED AUTHENTIK_ID xA8pjyG7s4LsnmRKp9Wn9H9v8oUpawBAMvmEF9tT AUTHENTIK_SECRET REDACTED AUTHENTIK_ISSUER https://auth.jeevesconsults.ca/application/o/splitpro OIDC_ALLOW_DANGEROUS_EMAIL_LINKING 1 DATABASE_URL REDACTED (includes PostgreSQL password) POSTGRES_USER splitpro POSTGRES_DB splitpro POSTGRES_PORT 5432 EMAIL_SERVER_HOST smtp.gmail.com EMAIL_SERVER_PORT 587 EMAIL_SERVER_USER jeeves5454@gmail.com EMAIL_SERVER_PASSWORD REDACTED FROM_EMAIL splitpro@jeevesconsults.ca ENABLE_SENDING_INVITES true DISABLE_EMAIL_SIGNUP false CURRENCY_RATE_PROVIDER frankfurter DEFAULT_HOMEPAGE /balances CACHE_RETENTION_INTERVAL 2 days CLEAR_CACHE_CRON_RULE 0 2 * * 0 (Sunday 2am) UPLOAD_MAX_FILE_SIZE_MB 10 NODE_EXTRA_CA_CERTS /etc/ssl/certs/step-ca-root.crt PORT 3000 Bind mounts: Host Path Container Path Purpose /home/jeeves/docker/splitpro/uploads /app/uploads Receipt and document uploads /home/jeeves/docker/step-ca/config/certs/root_ca.crt /etc/ssl/certs/step-ca-root.crt Step-CA root trust injection splitpro-db (PostgreSQL 17) Image: ossapps/postgres:17.7-trixie A PostgreSQL 17 image published by the SplitPro project (based on the official postgres:17 Debian image). Variable Value POSTGRES_USER splitpro POSTGRES_DB splitpro POSTGRES_PASSWORD REDACTED POSTGRES_PORT 5432 Bind mounts: Host Path Container Path /home/jeeves/docker/splitpro/db /var/lib/postgresql/data Traefik Labels traefik.http.routers.splitpro-external.rule: Host(`splitpro.jeevesconsults.ca`) traefik.http.routers.splitpro-external.entrypoints: websecure traefik.http.routers.splitpro-external.tls.certresolver: letsencrypt traefik.http.routers.splitpro-external.middlewares: plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.splitpro-external.service: splitpro-svc traefik.http.services.splitpro-svc.loadbalancer.server.port: 3000 Notes / Gotchas OIDC_ALLOW_DANGEROUS_EMAIL_LINKING: 1 allows Authentik OIDC logins to link to existing accounts by matching email address. Required if users were created before OIDC was enabled, or if local signup is still active. NODE_EXTRA_CA_CERTS injects the Step-CA root certificate into Node.js' TLS trust chain. This is required if SplitPro makes any HTTPS calls to internal *.home.local endpoints (e.g., if integrated with other internal services). DATABASE_URL in the container environment contains the PostgreSQL password in plaintext — treat this as sensitive. Currency rates are fetched from api.frankfurter.app (public, no API key required). Last Updated: 2026-06-17 03-ghostfolio.md kstack: book: Centerpoint Home Lab chapter: Finance page: Ghostfolio tags: [ghostfolio, investments, portfolio, finance, postgres, redis] Overview Ghostfolio is an open-source wealth management and investment portfolio tracker. It aggregates holdings across accounts, tracks performance, and visualises asset allocation. Internal-only access via Step-CA TLS — no external route is configured. Ghostfolio manages its own user authentication. The stack is three containers: the application, a PostgreSQL 15 database, and Redis for caching. Access Type URL Auth Internal https://ghostfolio.home.local Ghostfolio own auth (Step-CA TLS) No external route — accessible only on the LAN. Containers Container Image Role ghostfolio ghostfolio/ghostfolio:latest Web application ghostfolio-postgres postgres:15-alpine PostgreSQL 15 database ghostfolio-redis redis:alpine Cache and session store ghostfolio (application) Runtime: Node.js 22 Key environment variables: Variable Value / Notes DATABASE_URL REDACTED (PostgreSQL connection string with password) REDIS_HOST ghostfolio-redis REDIS_PORT 6379 ACCESS_TOKEN_SALT REDACTED JWT_SECRET_KEY REDACTED NODE_ENV production TZ America/Toronto No bind mounts — application state is stored entirely in PostgreSQL. ghostfolio-postgres (PostgreSQL 15) Image: postgres:15-alpine Variable Value POSTGRES_DB ghostfoliodb POSTGRES_USER ghostfoliouser POSTGRES_PASSWORD REDACTED TZ America/Toronto Bind mounts: Host Path Container Path /home/jeeves/docker/ghostfolio/postgres_data /var/lib/postgresql/data ghostfolio-redis Image: redis:alpine No authentication configured — network-isolated to the Ghostfolio internal stack network. Bind mounts: Host Path Container Path /home/jeeves/docker/ghostfolio/redis_data /data Traefik Labels traefik.http.routers.ghostfolio.rule: Host(`ghostfolio.home.local`) traefik.http.routers.ghostfolio.entrypoints: websecure traefik.http.routers.ghostfolio.tls.certresolver: step-ca traefik.http.services.ghostfolio.loadbalancer.server.port: 3333 Notes / Gotchas ACCESS_TOKEN_SALT and JWT_SECRET_KEY are critical secrets. If lost, all existing sessions and API tokens will be invalidated and cannot be recovered without reconfiguring Ghostfolio. Ghostfolio fetches market data from various external providers (Yahoo Finance, Coingecko, etc.). Market data API rate limits may apply depending on configured data sources. Portfolio data import supports CSV and JSON formats compatible with common brokerages. Check the Ghostfolio docs for the required format. Redis stores only ephemeral cache — data is not critical and does not need to be backed up. The postgres_data bind mount is the primary backup target. Port 3333 is the default Ghostfolio application port. Last Updated: 2026-06-17 h08-utilities 00-chapter-intro.md kstack: book: Centerpoint Home Lab chapter: Utilities page: Chapter Introduction tags: [utilities, tools, remote-access, file-management] Overview This chapter covers general-purpose utility services — remote access, file management, offline content, productivity tooling, and infrastructure support containers. Services in This Chapter Service Container(s) Purpose Guacamole guacamole, guacd, guacamole_db Clientless remote desktop gateway FileBrowser filebrowser Web-based file manager Kiwix kiwix Offline Wikipedia and ZIM content Draw.io drawio Self-hosted diagram editor IT Tools it-tools Developer and IT utility collection PairDrop pairdrop LAN file sharing (AirDrop-like) ConvertX convertx File format converter MinusPod minuspod AI-powered podcast manager and transcriber Docker Proxy dockerproxy Read-only Docker socket proxy Last Updated: 2026-06-17 01-guacamole.md kstack: book: Centerpoint Home Lab chapter: Utilities page: Apache Guacamole tags: [guacamole, remote-desktop, rdp, vnc, ssh, authentik, postgres] Overview Apache Guacamole is a clientless remote desktop gateway. It provides browser-based access to RDP, VNC, SSH, and Telnet sessions with no client software required. Authentication is via Authentik OpenID Connect (native Guacamole extension — not Traefik ForwardAuth). The stack is three containers: the web application, the guacd native protocol daemon, and PostgreSQL 15 for connection and user data. Access Type URL Auth External https://guac.jeeves5454.ddns.net Authentik OIDC + GeoBlock + CrowdSec Internal https://guac.home.local Authentik OIDC (Step-CA TLS) Guacamole uses its built-in openid extension with EXTENSION_PRIORITY=openid to handle the Authentik OIDC flow directly. Traefik does not apply authentik-auth@docker ForwardAuth. Containers Container Image Role guacamole guacamole/guacamole:latest Web application (Tomcat/Java) guacd guacamole/guacd:latest Native protocol daemon (RDP/VNC/SSH) guacamole_db postgres:15-alpine PostgreSQL database guacamole (application) Runtime: Apache Tomcat / Java. WEBAPP_CONTEXT=ROOT serves the app at / rather than /guacamole. Key environment variables: Variable Value / Notes GUACD_HOSTNAME guacd POSTGRESQL_HOSTNAME postgres (internal alias for guacamole_db) POSTGRESQL_DATABASE guacamole_db POSTGRESQL_USER guacamole_user POSTGRESQL_PASSWORD REDACTED WEBAPP_CONTEXT ROOT EXTENSION_PRIORITY openid OPENID_ISSUER https://auth.jeevesconsults.ca/application/o/guacamole/ OPENID_CLIENT_ID A1l7KFyrugknbC8jtFjPxm520XkcNVjGMfTFtJMt OPENID_CLIENT_SECRET REDACTED OPENID_REDIRECT_URI https://guac.jeeves5454.ddns.net/ OPENID_AUTHORIZATION_ENDPOINT https://auth.jeevesconsults.ca/application/o/authorize/ OPENID_JWKS_ENDPOINT https://auth.jeevesconsults.ca/application/o/guacamole/jwks/ OPENID_SCOPE openid email profile OPENID_USERNAME_CLAIM_TYPE preferred_username No bind mounts — app state is stored in PostgreSQL. guacd The native protocol daemon that handles the actual RDP, VNC, SSH, and Telnet protocol sessions. Guacamole app proxies all protocol traffic through guacd. Bind mounts: Host Path Container Path Purpose /home/jeeves/docker/guacamole/drive /drive Virtual drive for file transfer /home/jeeves/docker/guacamole/record /record Session recordings guacamole_db (PostgreSQL 15) Image: postgres:15-alpine Variable Value POSTGRES_DB guacamole_db POSTGRES_USER guacamole_user POSTGRES_PASSWORD REDACTED PGDATA /var/lib/postgresql/data/guacamole Bind mounts: Host Path Container Path Purpose /home/jeeves/docker/guacamole/db-data /var/lib/postgresql/data Database files /home/jeeves/docker/guacamole/init /docker-entrypoint-initdb.d Init SQL scripts Traefik Labels traefik.http.routers.guacamole-external.rule: Host(`guac.jeeves5454.ddns.net`) traefik.http.routers.guacamole-external.entrypoints: websecure traefik.http.routers.guacamole-external.tls.certresolver: letsencrypt traefik.http.routers.guacamole-external.middlewares: plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.guacamole-external.service: guac-svc traefik.http.routers.guacamole-internal.rule: Host(`guac.home.local`) traefik.http.routers.guacamole-internal.entrypoints: websecure traefik.http.routers.guacamole-internal.tls.certresolver: step-ca traefik.http.routers.guacamole-internal.service: guac-svc traefik.http.services.guac-svc.loadbalancer.server.port: 8080 traefik.docker.network: traefik-net Notes / Gotchas EXTENSION_PRIORITY=openid means Guacamole's OpenID extension takes precedence over any local user database. If Authentik is down, Guacamole login is unavailable — there is no local admin fallback while OpenID is primary. OPENID_REDIRECT_URI must exactly match the redirect URI registered in Authentik. It points to the external domain ( guac.jeeves5454.ddns.net) even when accessing internally — Authentik sends the browser there after authentication. WEBAPP_CONTEXT=ROOT removes the /guacamole path prefix. The app responds directly at /. Without this, Traefik would need to strip the prefix or all URLs would be /guacamole/.... Session recordings are stored in the record bind mount. Manage disk usage if many long sessions are recorded. The init bind mount contains the SQL schema initialisation scripts for the database. These run once on first start. Do not remove them if the container has not yet initialised. Last Updated: 2026-06-17 02-filebrowser.md kstack: book: Centerpoint Home Lab chapter: Utilities page: FileBrowser tags: [filebrowser, files, storage, authentik] Overview FileBrowser is a web-based file manager that provides browsing, uploading, downloading, and editing of files across multiple mounted host paths. The external route is protected by Authentik ForwardAuth. The internal route requires no additional middleware. Several significant host directories are exposed. Access Type URL Auth External https://files.jeeves5454.ddns.net Authentik ForwardAuth + GeoBlock + CrowdSec Internal https://files.home.local FileBrowser own auth (Step-CA TLS) Configuration Image: filebrowser/filebrowser:latest Traefik Labels traefik.http.routers.filebrowser-external.rule: Host(`files.jeeves5454.ddns.net`) traefik.http.routers.filebrowser-external.entrypoints: websecure traefik.http.routers.filebrowser-external.tls.certresolver: letsencrypt traefik.http.routers.filebrowser-external.middlewares: authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file traefik.http.routers.filebrowser-external.service: filebrowser-svc traefik.http.routers.filebrowser-internal.rule: Host(`files.home.local`) traefik.http.routers.filebrowser-internal.entrypoints: websecure traefik.http.routers.filebrowser-internal.tls.certresolver: step-ca traefik.http.routers.filebrowser-internal.service: filebrowser-svc traefik.http.services.filebrowser-svc.loadbalancer.server.port: 80 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/docker/filebrowser/config /config FileBrowser config file /home/jeeves/docker/filebrowser/database /database FileBrowser database (SQLite) /home/jeeves /srv/home Jeeves home directory /media/jeeves/1TB_Vol2/downloads /srv/downloads Download staging area (Ceph OSD) /mnt /srv/mnt NFS mounts (Multimedia, Photos, data) FileBrowser provides access to the full home directory, download staging area, and all NFS mounts. Treat external access with appropriate caution. Notes / Gotchas Authentik ForwardAuth is applied on the external route — external access requires a valid Authentik session before FileBrowser's own login is shown. FileBrowser still maintains its own user database internally. /mnt is exposed as /srv/mnt — this includes all NFS shares from UnRAID ( /mnt/Multimedia, /mnt/Photos, /mnt/data). Changes made via FileBrowser are live on the NFS shares. The SQLite database in /database stores FileBrowser users, permissions, and settings. Back it up alongside /config. PUID=1000 / GID=1000 — FileBrowser runs as the jeeves user, so file operations respect the same ownership as files on disk. Last Updated: 2026-06-17 03-kiwix.md kstack: book: Centerpoint Home Lab chapter: Utilities page: Kiwix tags: [kiwix, offline, wikipedia, zim, authentik] Overview Kiwix serves offline ZIM-format content — primarily Wikipedia snapshots and other reference material — accessible without an internet connection. ZIM files are stored on the Ceph OSD volume. The external route is gated behind Authentik ForwardAuth. The internal route has no additional auth middleware. Access Type URL Auth External https://kiwix.jeeves5454.ddns.net Authentik ForwardAuth + GeoBlock + CrowdSec Internal https://kiwix.home.local No additional auth (Step-CA TLS) Configuration Image: ghcr.io/kiwix/kiwix-serve:latest Traefik Labels traefik.http.routers.kiwix-external.rule: Host(`kiwix.jeeves5454.ddns.net`) traefik.http.routers.kiwix-external.entrypoints: websecure traefik.http.routers.kiwix-external.tls.certresolver: letsencrypt traefik.http.routers.kiwix-external.middlewares: plex-geoblock@file,crowdsec-bouncer@file,authentik-auth@docker traefik.http.routers.kiwix-external.service: kiwix-svc traefik.http.routers.kiwix-internal.rule: Host(`kiwix.home.local`) traefik.http.routers.kiwix-internal.entrypoints: websecure traefik.http.routers.kiwix-internal.tls.certresolver: step-ca traefik.http.routers.kiwix-internal.service: kiwix-svc traefik.http.services.kiwix-svc.loadbalancer.server.port: 8080 Volumes / Bind Mounts Host Path Container Path Purpose /media/jeeves/1TB_Vol1/Kiwix_Images /data ZIM content files ZIM files are stored on the second Ceph OSD volume ( nvme2n1, /media/jeeves/1TB_Vol1). kiwix-serve auto-discovers all .zim files in /data on startup and presents them in the library UI. Notes / Gotchas ZIM files can be very large (Wikipedia full English is ~100GB). Ensure adequate space on the Ceph volume before downloading new content. New ZIM files can be added by placing them in /media/jeeves/1TB_Vol1/Kiwix_Images. A container restart is required for kiwix-serve to discover them. Kiwix has no built-in authentication — the internal route is unprotected on the LAN. The external route relies entirely on Authentik ForwardAuth. ZIM files can be downloaded from library.kiwix.org and transferred to the Ceph volume via FileBrowser or SCP. Last Updated: 2026-06-17 04-drawio.md kstack: book: Centerpoint Home Lab chapter: Utilities page: Draw.io tags: [drawio, diagrams, productivity] Overview Draw.io (now diagrams.net) is a self-hosted diagramming application for creating network diagrams, flowcharts, architecture diagrams, and more. This instance is stateless — diagrams are saved locally by the browser or to connected storage (Google Drive, OneDrive, etc.), not on the server. External-only access with GeoBlock and CrowdSec. Access Type URL Auth External https://drawio.jeeves5454.ddns.net GeoBlock + CrowdSec (no auth) No authentication middleware — Draw.io is accessible to anyone who bypasses GeoBlock. The app itself has no user accounts. Configuration Image: jgraph/drawio:latest Runtime: Java 11 / Apache Tomcat Key Environment Variables Variable Value / Notes DRAWIO_BASE_URL https://drawio.jeeves5454.ddns.net PUBLIC_DNS drawio.jeeves5454.ddns.net DRAWIO_GOOGLE_CLIENT_ID jeeves5454@gmail.com (Google integration) LETS_ENCRYPT_ENABLED false (TLS handled by Traefik) Traefik Labels traefik.http.routers.drawio.rule: Host(`drawio.jeeves5454.ddns.net`) traefik.http.routers.drawio.entrypoints: websecure traefik.http.routers.drawio.tls.certresolver: letsencrypt traefik.http.routers.drawio.middlewares: plex-geoblock@file,crowdsec-bouncer@file,drawio-headers traefik.http.middlewares.drawio-headers.headers.customrequestheaders.X-Forwarded-Proto: https traefik.http.services.drawio.loadbalancer.server.port: 8080 Volumes / Bind Mounts None — Draw.io is completely stateless. All diagram data is stored client-side (browser localStorage or connected cloud storage). Notes / Gotchas LETS_ENCRYPT_ENABLED=false is correct — Traefik handles TLS termination. The Draw.io container itself serves plain HTTP on port 8080. The X-Forwarded-Proto: https header is required so Draw.io generates correct HTTPS URLs for embedded resources and exports. Diagrams are not stored on the server. To preserve diagrams, save them to a local file ( .drawio / .xml) or configure a cloud storage backend in the Draw.io UI. DRAWIO_GOOGLE_CLIENT_ID enables the Google Drive integration in the file picker. Users must authorise their own Google account in the app. Last Updated: 2026-06-17 05-it-tools.md kstack: book: Centerpoint Home Lab chapter: Utilities page: IT Tools tags: [it-tools, developer-tools, utilities] Overview IT Tools is a collection of handy online developer and IT utilities — including JWT decoders, UUID generators, hash generators, cron expression parsers, base64 encoders, regex testers, and many more. It is a static React app served by NGINX. Internal-only access. No configuration, no authentication, no persistent state. Access Type URL Auth Internal https://ittools.home.local None (LAN only) Configuration Image: corentinth/it-tools:latest Runtime: NGINX 1.26 (static SPA) Traefik Labels traefik.http.routers.ittools.rule: Host(`ittools.home.local`) traefik.http.routers.ittools.entrypoints: websecure traefik.http.routers.ittools.tls.certresolver: step-ca traefik.http.services.ittools.loadbalancer.server.port: 80 Volumes / Bind Mounts None — fully stateless static application. Notes / Gotchas No data leaves the browser — all tools run client-side in JavaScript. Safe to use for sensitive values (passwords, tokens, keys) that should not be sent to external services. The tool list expands with each release of the corentinth/it-tools image. Pull the latest tag to get new tools. Last Updated: 2026-06-17 06-pairdrop.md kstack: book: Centerpoint Home Lab chapter: Utilities page: PairDrop tags: [pairdrop, file-sharing, webrtc, lan] Overview PairDrop is a local network file sharing app inspired by Apple AirDrop. It uses WebRTC for peer-to-peer file transfers between devices on the same LAN session. No accounts, no file storage on the server — files transfer directly between browsers. Internal-only access. Access Type URL Auth Internal https://pairdrop.home.local None (LAN only) Configuration Image: lscr.io/linuxserver/pairdrop:latest Environment Variables Variable Value Purpose TZ America/Toronto Timezone PUID 1000 User ID PGID 1000 Group ID WS_FALLBACK false WebSocket fallback (disabled) RATE_LIMIT false Rate limiting (disabled for LAN) RTC_CONFIG false External STUN/TURN (not needed on LAN) DEBUG_MODE false Debug logging Traefik Labels traefik.http.routers.pairdrop.rule: Host(`pairdrop.home.local`) traefik.http.routers.pairdrop.entrypoints: websecure traefik.http.routers.pairdrop.tls.certresolver: step-ca traefik.http.services.pairdrop.loadbalancer.server.port: 3000 Volumes / Bind Mounts None — fully stateless. Files are transferred peer-to-peer and never stored on the server. Notes / Gotchas PairDrop discovers peers in the same "room" — by default, all users behind the same NAT/gateway appear to each other. On a home LAN this means all devices on the network can see each other. RTC_CONFIG=false is correct for LAN use — no external STUN/TURN server is needed when all peers are on the same network. For Tailscale peers to use PairDrop, they would need a TURN server since Tailscale creates separate subnets. Current config is LAN-only. Last Updated: 2026-06-17 07-convertx.md kstack: book: Centerpoint Home Lab chapter: Utilities page: ConvertX tags: [convertx, file-conversion, utilities] Overview ConvertX is a self-hosted file format conversion tool. It supports converting between video, audio, image, document, and other file formats using a web interface. Internal-only access. Account registration is enabled, allowing any LAN user to create an account. Access Type URL Auth Internal https://convertx.home.local ConvertX own auth (Step-CA TLS) Configuration Image: ghcr.io/c4illin/convertx Environment Variables Variable Value Purpose HTTP_ALLOWED true Allows HTTP connections (Traefik terminates TLS) ACCOUNT_REGISTRATION true Any LAN user can self-register QTWEBENGINE_CHROMIUM_FLAGS --no-sandbox Required for headless Chromium in container Traefik Labels traefik.http.routers.convertx.rule: Host(`convertx.home.local`) traefik.http.routers.convertx.entrypoints: websecure traefik.http.routers.convertx.tls.certresolver: step-ca traefik.http.services.convertx.loadbalancer.server.port: 3000 Volumes / Bind Mounts Host Path Container Path Purpose /data/compose/43/data /app/data App data and SQLite The data directory is a Portainer-managed compose path ( /data/compose//data) — this stack is managed entirely via Portainer with no local compose file. The path is determined by the Portainer stack ID (43). Notes / Gotchas ACCOUNT_REGISTRATION=true allows any user on the LAN to self-register. Disable this if access should be restricted to a single admin account. Converted files are stored temporarily in the app data directory and deleted after download. Do not use this path for long-term storage. --no-sandbox is a Chromium security flag required when running in a container (no user namespace isolation). This is standard for containerised headless browser setups. ConvertX uses FFmpeg, ImageMagick, and other converters internally — available formats depend on the image build. Last Updated: 2026-06-17 08-minuspod.md kstack: book: Centerpoint Home Lab chapter: Utilities page: MinusPod tags: [minuspod, podcasts, transcription, whisper, ai, cuda] Overview MinusPod is an AI-powered podcast manager and transcriber. It manages podcast feeds, downloads episodes, and transcribes audio using a local Whisper model. Summaries and notes are generated via an LLM (Qwen3:14b) through the Ollama API. The container runs with NVIDIA GPU access (RTX 5080) for accelerated Whisper inference. Internal-only access. Episode data is retained for 180 days. Access Type URL Auth Internal https://minuspod.home.local MinusPod own auth (Step-CA TLS) No external route — LAN access only. Configuration Image: ttlequals0/minuspod:2.1.9 Runtime: CUDA 12.9 / NVIDIA runtime (RTX 5080) Key Environment Variables Variable Value / Notes BASE_URL https://minuspod.home.local WHISPER_BACKEND local WHISPER_DEVICE cuda WHISPER_MODEL medium LLM_PROVIDER ollama OPENAI_BASE_URL https://ollama.home.local/v1 OPENAI_MODEL qwen3:14b OPENAI_API_KEY REDACTED RETENTION_PERIOD 4320 hours (180 days) MINUSPOD_TRUSTED_PROXY_COUNT 1 NVIDIA_VISIBLE_DEVICES all NVIDIA_DRIVER_CAPABILITIES compute,utility Traefik Labels traefik.http.routers.minuspod.rule: Host(`minuspod.home.local`) traefik.http.routers.minuspod.entrypoints: websecure traefik.http.routers.minuspod.tls.certresolver: step-ca traefik.http.services.minuspod.loadbalancer.server.port: 8000 Volumes / Bind Mounts Host Path Container Path Purpose /home/jeeves/dockers/minuspod/data /app/data Episode database and downloads Note: The data directory is under /home/jeeves/dockers/ (not docker/) — this is the actual bind mount path as confirmed by docker inspect. AI Integration MinusPod uses two AI components: Component Model Transport Purpose Whisper medium Local CUDA inference (RTX 5080) Audio transcription Qwen3:14b qwen3:14b Ollama API at ollama.home.local/v1 Summaries and notes OPENAI_BASE_URL points to the internal Ollama instance (which exposes an OpenAI-compatible API). The OPENAI_MODEL is qwen3:14b — this model must be present in Ollama. Verify with docker exec ollama ollama list. Notes / Gotchas The Whisper medium model on CUDA provides a good balance of speed and accuracy. Upgrade to large-v3 if accuracy is more important than speed (uses more VRAM). The RTX 5080 has 16GB GDDR7 — ample for either. RETENTION_PERIOD=4320 hours = 180 days. Episodes older than this are automatically removed. Adjust if long-term episode storage is needed. MINUSPOD_TRUSTED_PROXY_COUNT=1 tells MinusPod to trust one level of proxy headers (Traefik) for correct IP and protocol detection. The bind mount path is /home/jeeves/dockers/minuspod/data (note: dockers, not docker). This is different from the convention used by most other stacks. Ensure qwen3:14b is loaded in Ollama before enabling LLM summaries: docker exec ollama ollama pull qwen3:14b Last Updated: 2026-06-17 09-docker-proxy.md kstack: book: Centerpoint Home Lab chapter: Utilities page: Docker Socket Proxy tags: [dockerproxy, security, docker, infrastructure] Overview The Docker Socket Proxy ( dockerproxy) provides a read-only, filtered proxy to the Docker daemon socket. It exposes a limited subset of the Docker API over TCP, allowing containers like Homepage to query running container data without requiring direct access to /var/run/docker.sock. No Traefik route — internal use only. Configuration Image: tecnativa/docker-socket-proxy Environment Variables (API Permission Flags) Variable Value Permission granted CONTAINERS 1 Read container list and inspect data EVENTS 1 Subscribe to Docker events stream POST 0 Disabled — no write operations ALLOW_START 0 Cannot start containers ALLOW_STOP 0 Cannot stop containers ALLOW_RESTARTS 0 Cannot restart containers AUTH 0 No auth endpoint access BUILD 0 No build operations COMMIT 0 No commit operations CONFIGS 0 No config access DISTRIBUTION 0 No distribution endpoint access All write operations are disabled. The proxy grants read-only container metadata access only. Volumes / Bind Mounts Host Path Container Path Purpose /var/run/docker.sock /var/run/docker.sock Docker socket (read-only proxy source) Consumer The primary consumer is Homepage ( homepage.home.local), which connects to dockerproxy:2375 to discover running containers and display live service status widgets. This avoids mounting the Docker socket directly into Homepage. Notes / Gotchas The proxy listens on TCP port 2375 (standard Docker API port) inside the Docker network — it is not exposed on any host port or Traefik route. Allowing only CONTAINERS=1 and EVENTS=1 means Homepage can list containers and receive real-time status events, but cannot perform any management actions. If Homepage shows containers as offline or fails to discover services, verify dockerproxy is running and on the same Docker network as Homepage. The tecnativa/docker-socket-proxy image is the community standard for this pattern. It is based on HAProxy 3.x and routes allowed API paths only. Last Updated: 2026-06-17