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.

Server Introduction

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


Last Updated: 2026-06-16

Server Introduction

Storage Layout

Overview

Centerpoint uses a three-tier storage strategy:

  1. Local NVMe — fast system and application data storage
  2. Ceph OSD block devices — two NVMe drives contributing to a distributed Ceph cluster for object/block storage across the homelab
  3. 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


Last Updated: 2026-06-16

Server Introduction

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:

DNS

Internal (*.home.local): Resolved by AdGuard Home on the LAN. A wildcard DNS record points *.home.local192.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


Last Updated: 2026-06-16

Server Introduction

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.<name>-ext.rule=Host(`<svc>.jeeves5454.ddns.net`)"
  - "traefik.http.routers.<name>-ext.entrypoints=websecure"
  - "traefik.http.routers.<name>-ext.tls.certresolver=letsencrypt"
  - "traefik.http.routers.<name>-ext.middlewares=authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file"

  # Internal route — Step-CA TLS, no extra middleware
  - "traefik.http.routers.<name>-int.rule=Host(`<svc>.home.local`)"
  - "traefik.http.routers.<name>-int.entrypoints=websecure"
  - "traefik.http.routers.<name>-int.tls.certresolver=step-ca"

  # Backend service port
  - "traefik.http.services.<name>-svc.loadbalancer.server.port=<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


Last Updated: 2026-06-16

Infrastructure & Networking

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:

  1. AdGuard Home*.home.local DNS must resolve before anything can find its neighbours
  2. Step-CA — required for Traefik to issue internal certificates on first boot
  3. Traefik — all named HTTPS routes depend on it
  4. Authentik (PostgreSQL → Server → Worker) — required before any externally-accessible service that enforces SSO
  5. CrowdSecCrowdSec Bouncer — bouncer cannot connect to LAPI until CrowdSec is healthy
  6. All other infrastructure services (Portainer, Dozzle, Uptime-Kuma, Homepage) can start in any order

Last Updated: 2026-06-16

Infrastructure & Networking

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: <redacted>
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web
  step-ca:
    acme:
      email: <redacted>
      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.<name>-ext.rule=Host(`<svc>.jeeves5454.ddns.net`)"
  - "traefik.http.routers.<name>-ext.entrypoints=websecure"
  - "traefik.http.routers.<name>-ext.tls.certresolver=letsencrypt"
  - "traefik.http.routers.<name>-ext.middlewares=authentik-auth@docker,plex-geoblock@file,crowdsec-bouncer@file"

  # Internal route — Step-CA TLS, no SSO middleware
  - "traefik.http.routers.<name>-int.rule=Host(`<svc>.home.local`)"
  - "traefik.http.routers.<name>-int.entrypoints=websecure"
  - "traefik.http.routers.<name>-int.tls.certresolver=step-ca"

  # Backend
  - "traefik.http.services.<name>-svc.loadbalancer.server.port=<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

Notes / Gotchas


Last Updated: 2026-06-16

Infrastructure & Networking

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

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


Last Updated: 2026-06-16

Infrastructure & Networking

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

Notes / Gotchas

FUTURE WORK


Last Updated: 2026-06-16

Infrastructure & Networking

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.<name>-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/<app-slug>/
JWKS URL <issuer>/.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

Notes / Gotchas


Last Updated: 2026-06-16

Infrastructure & Networking

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:

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:

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

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

FUTURE WORK


Last Updated: 2026-06-16

Infrastructure & Networking

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:

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

Notes / Gotchas


Last Updated: 2026-06-16

Infrastructure & Networking

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

Notes / Gotchas


Last Updated: 2026-06-16

Infrastructure & Networking

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:

Networks

Network Purpose
traefik-net External Traefik route
uptime-kuma_monitoring Internal per-stack network

Dependencies

Notes / Gotchas


Last Updated: 2026-06-16

Infrastructure & Networking

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: <name>) 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

Notes / Gotchas


Last Updated: 2026-06-16

AI & Automation

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

AI & Automation

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

Notes / Gotchas


Last Updated: 2026-06-16

AI & Automation

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

Notes / Gotchas


Last Updated: 2026-06-16

AI & Automation

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

Notes / Gotchas


Last Updated: 2026-06-16

AI & Automation

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

Notes / Gotchas


Last Updated: 2026-06-16

AI & Automation

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 riffadoriffado-db communication

Dependencies

Notes / Gotchas


Last Updated: 2026-06-16

AI & Automation

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

Notes / Gotchas


Last Updated: 2026-06-16

AI & Automation

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

Notes / Gotchas


Last Updated: 2026-06-16

AI & Automation

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-notebookopen-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

Notes / Gotchas


Last Updated: 2026-06-16

AI & Automation

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:

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-proxygithub-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:<port>/health"]
  interval: 30s
  timeout: 5s
  retries: 3

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 <issuer>/jwks/

Dependencies

Notes / Gotchas


Last Updated: 2026-06-16

h04-media-entertainment

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

h04-media-entertainment

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

Notes / Gotchas


Last Updated: 2026-06-16

h04-media-entertainment

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

Notes / Gotchas


Last Updated: 2026-06-16

h04-media-entertainment

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


Last Updated: 2026-06-16

h04-media-entertainment

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

Notes / Gotchas


Last Updated: 2026-06-16

h04-media-entertainment

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:

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

Notes / Gotchas


Last Updated: 2026-06-16

h04-media-entertainment

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

Notes / Gotchas


Last Updated: 2026-06-16

h04-media-entertainment

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

Notes / Gotchas


Last Updated: 2026-06-16

h04-media-entertainment

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:

  1. In Plex Settings → Live TV & DVR → Set Up Plex Tuner
  2. Enter the Threadfin HDHR URL: http://192.168.1.85:34400/device.xml
  3. Plex discovers the virtual tuner and available channels

Jellyfin:

  1. Admin Dashboard → Live TV → Add Tuner Device
  2. Select HDHomeRun and enter http://192.168.1.85:34400

Dependencies

Notes / Gotchas


Last Updated: 2026-06-16

h05-media-management

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

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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


Last Updated: 2026-06-16

h05-media-management

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:

  1. Settings → Indexers → Add Proxy
  2. Type: FlareSolverr
  3. Host: http://flaresolverr:8191
  4. 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


Last Updated: 2026-06-16

h06-documents-organization

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

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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


Last Updated: 2026-06-17

h06-documents-organization

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:

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 <REITTI_API_TOKEN>            │
│                    ↓                                    │
│             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:

  1. 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).
  2. 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 (lnglon)
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 <REITTI_API_URL>
Authorization: Bearer <REITTI_API_TOKEN>
Content-Type: application/json

AdventureLog Integration

Category Mapping

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éxicoMexico). 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

  1. Visit the auth URL:

    https://swarm.jeeves5454.ddns.net/auth
    
  2. The bridge redirects to Foursquare with the configured client_id and redirect_uri.

  3. Authenticate with your Foursquare account and authorise the app.

  4. Foursquare redirects back to https://swarm.jeeves5454.ddns.net/callback with an authorisation code.

  5. 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=<reason> 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):

  1. Visit https://swarm.jeeves5454.ddns.net/auth
  2. Log in and authorise the app
  3. 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']['<userId>'] = None
s['lastCheckinTimestamps']['<userId>'] = <tst - 1>
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

  1. Check the status dashboard at / — the Reitti card shows last push time and any error message
  2. Check logs:
    docker logs swarm-reitti-bridge | grep -E '"cat":"CHECKIN"|"level":"ERROR"'
    
  3. Verify token is connected: curl -s https://swarm.jeeves5454.ddns.net/health | jq .connectedUsers — if 0, re-authenticate via /auth
  4. 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 <token>" \
      -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

  1. Check the status dashboard / — the AdventureLog card shows last sync and any error
  2. Verify AdventureLog API key is valid:
    curl -s "https://travel.jeeves5454.ddns.net/api/locations?limit=1" \
      -H "X-API-Key: <key>"
    
    Expected: JSON with count field. If 403, the key is wrong.
  3. 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

OAuth callback fails (redirect URI mismatch)

Bridge container exits immediately

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

h06-documents-organization

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

  1. Loads all regions, locations (with embedded visits), and collections from AdventureLog.
  2. 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).
  3. 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://<external>
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:

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:

  1. http://adventurelog-web:3000 is tried first (direct, never leaves host)
  2. https://travel.home.local is tried second (through Traefik internally)
  3. 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:

  1. The in-memory cache (populated from region_cache.json on startup)
  2. 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


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

  1. Generate a new token in AdventureLog → User Settings → API Tokens
  2. Update ADVENTURELOG_API_KEY in the AdventureLog stack's environment variable table in Portainer
  3. 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:


Last Updated

2026-06-24

h07-finance

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

h07-finance

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


Last Updated: 2026-06-17

h07-finance

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


Last Updated: 2026-06-17

h07-finance

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


Last Updated: 2026-06-17

h08-utilities

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

h08-utilities

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


Last Updated: 2026-06-17

h08-utilities

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


Last Updated: 2026-06-17

h08-utilities

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


Last Updated: 2026-06-17

h08-utilities

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


Last Updated: 2026-06-17

h08-utilities

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


Last Updated: 2026-06-17

h08-utilities

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


Last Updated: 2026-06-17

h08-utilities

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/<stack-id>/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


Last Updated: 2026-06-17

h08-utilities

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


Last Updated: 2026-06-17

h08-utilities

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


Last Updated: 2026-06-17