What it is
This isn't a service in its own right — it's the routing pattern that makes every internal hostname in this catalog work. Two pieces of infrastructure cooperate: AdGuard Home resolves the name, and Nginx Proxy Manager routes the request.
Why I run it this way
I wanted the cost of adding a new service to the catalog to be one entry in one place. Two-step adds (a DNS record and a proxy host) drift over time — half-deleted DNS entries that no longer match any proxy host, or proxy hosts pointed at hostnames that don't resolve. The wildcard collapses both.
How it works
The whole chain for a request to (say) homepage.lab:
- The browser asks DNS for
homepage.lab. - AdGuard Home has a single wildcard rewrite,
*.lab → <NPM IP>. It returns that IP. - The browser opens an HTTP connection to the NPM IP, sending
Host: homepage.labin the request. - NPM matches the host header against its proxy host list and forwards to the upstream — in this case, my Homepage dashboard.
AdGuard resolves the name. NPM does the routing based on the host header. Both have to be configured for a .lab hostname to work — but thanks to the wildcard, AdGuard automatically covers any new .lab name without per-service edits. Only NPM needs a new entry per service.
Setup notes
- Wildcard rewrite:
*.lab → <NPM container IP>, configured on both AdGuard instances so failover preserves the routing. - Container DNS: Docker containers don't use AdGuard by default. To resolve
.labfrom inside a container, add explicitdns:entries to the compose file. In practice I usually monitor direct IPs from inside containers instead — more accurate signal, decoupled from DNS health. - The
.jhinx.devmigration: as of May 2026, new internal routes use real Let's Encrypt-backed*.jhinx.devcerts. The.labwildcard stays as a compatibility namespace, but the routing model is identical — just a different wildcard pattern in a different DNS zone.
Runbook
- Healthy looks like: any
.labhostname resolves to NPM's IP from the workstation, and a request to that hostname lands on the right upstream. .labworks on phone but not from container: container DNS isn't pointed at AdGuard. Add it to the compose file.- One hostname doesn't resolve while others do: check for an override entry below the wildcard pointing somewhere stale.
- Both AdGuards diverge: there's no automatic sync between primary and secondary. The mirror is manual — change on secondary first to validate, then mirror to primary. I treat drift as my responsibility, not a tooling problem.