Home Lab Fleet and Ingress

The facts every service on this network is built around: which hosts exist, how their names resolve, and the single way public traffic gets in. Checked on 2026-10-10 against the jakemon repository (CLAUDE.md, Fleet topology), which tracks the fleet, and against the hosts themselves.

If you are an agent

Fleet

HostLAN addressRole
cloudflare192.168.5.50The ingress (cloudflared). Also static sites (nginx :8000), the SSH jump host, and Pi-hole for LAN DNS. See HostCloudflare.
docker1192.168.5.51Production Wikantik (wiki.wikantik.com, :8080) and its PostgreSQL. See HostDocker1.
docker2192.168.5.52Application host: CrafterCMS (jakefear.com, maiiavorobiova.com) and Roller. See HostDocker2.
inference192.168.5.53Since 2026-10-09: jakemon's central stack (Prometheus, Loki, Grafana, Alertmanager), plus ollama and openwebui on the GPU. See HostInference and JakemonHub.
nas192.168.5.54NAS, nas.lan; Wikantik's backup target.
minisforum192.168.5.250Personal workstation.

The LAN has been 192.168.5.0/24 since 2026-10-07; notes that say 192.168.0.x predate the renumbering. The addresses are for reference only; configuration names hosts.

LAN DNS

Only the Pi-hole on cloudflare knows the <host>.lan names. The ISP gateway hands out its own resolvers and cannot be pointed at the Pi-hole. Instead, each Ubuntu server carries a systemd-resolved drop-in that sends *.lan to 192.168.5.50 and makes lan the search domain.

Write the full .lan name in configuration anyway. A container gets the search domain only if it was created after the drop-in, and a lookup that depends on a search list fails in confusing ways. jakemon's CLAUDE.md has the full detail, including the NAS's dnsmasq quirk.

Ingress: cloudflared on cloudflare

Every public hostname is a route in a Cloudflare Tunnel whose connector, cloudflared, runs on host cloudflare. The connector forwards each request to an origin on the LAN over plain HTTP (http://<host>.lan:<port>), so origin hosts open no internet-facing port at all. A second tunnel, ssh-workstation, carries SSH; leave it alone.

Building a service for this network

  1. Pick the port. Choose a free host port at or above 8080 on the target host, serving plain HTTP. Bind it where cloudflare can reach it: all interfaces, or the LAN address. Check with ss -ltn on the host, then add the port to the host page's table. 80 and 443 stay free.
  2. No TLS and no HTTPS redirect at the origin. Cloudflare terminates TLS. An origin that redirects HTTP to HTTPS sends every request back to the edge, which forwards it as HTTP again: a loop.
  3. Build public URLs from configuration. Absolute links in feeds, sitemaps and emailed links should come from a configured https:// base URL, not from the incoming request.
  4. Trust the connector, and only it. If the service sits behind its own reverse proxy, configure that proxy to trust 192.168.5.50 for X-Forwarded-For and X-Forwarded-Proto.
    • Caddy discards both headers from any peer it does not trust (checked 2026-10-10 on Caddy 2.11.4). The app then sees 192.168.5.50 as every reader's address, and http as every request's scheme. That collapses per-client rate limits onto a single key.
    • In Caddy, put trusted_proxies static 192.168.5.50 and trusted_proxies_strict in the global servers block. Strict reads X-Forwarded-For right to left, which matters because Cloudflare appends the reader's address to any value the reader sent.
    • Trust that one address, never private_ranges. Docker's userland proxy can make an outside client appear to come from a private address.
    • Host and CF-Connecting-IP pass through Caddy either way. A service that reads CF-Connecting-IP (Umami does) sees the reader without any trust setting. Any LAN client can forge that header, though, so base no security decision on it.
  5. Register monitoring. Add the service to jakemon in hosts/<host>/apps.alloy, as a scrape target or a blackbox probe, then redeploy that host's agent. Any alert you add needs a runbook: Incident<Name> page. Container logs are collected automatically. See JakemonHub.
  6. Add the route. Add the public hostname to the tunnel, pointing at http://<host>.lan:<port>.

Example: Roller on docker2

Roller's Caddy publishes host port 8080 (SITE_HTTP_PORT=8080, with SITE_DOMAIN=:80) and trusts only the connector (TRUSTED_PROXIES=192.168.5.50, Roller 0.1.12 and later). Its tunnel route is to point at http://docker2.lan:8080. See RollerOnDocker and HostDocker2.

Sources