The 2026-10-19 reboot left every franksdank.cloud name NXDOMAIN for LAN clients while external names kept answering from cache; the file was mounted and readable and dns.hosts was intact, and 'pihole restartdns reload' restored every name in seconds. Mechanism not established. Instrument and remedy now on the owner page, plus a failure-mode row for the FTL rate-limit case seen beside it (10.0.2.x = a pasta container's forwarder).
7.5 KiB
type, description, tags, timestamp
| type | description | tags | timestamp | ||
|---|---|---|---|---|---|
| reference | Pi-hole is the LAN DNS server for the homelab. |
|
2026-10-19 |
Pi-hole
Pi-hole is the LAN DNS server for the homelab. It runs as a quadlet container on Dankclaw (192.168.0.143). It answers the flat franksdank.cloud / franksdank.casa service names and filters ad traffic on the LAN. It retired the dnsmasq quadlet on 2026-08-24. See LAN-DNS for the name-resolution flow and the router hand-off.
Components
| Component | Where | Role |
|---|---|---|
pihole quadlet |
dankclaw/pihole/quadlet/pihole.container |
Boot-persistent container unit |
| FTL | docker.io/pihole/pihole:latest |
DNS engine (a dnsmasq fork) + ad blocking |
addn-hosts |
dankclaw/pihole/addn-hosts |
One A record per Caddy vhost |
| sysctl drop-in | dankclaw/pihole/sysctl/99-dnsmasq.conf (repo) → /etc/sysctl.d/ |
Rootless port-53 threshold, applied at boot |
| env file | /home/dank/Containers/pihole/env (host) |
ServerIP, TZ, DNS1, DNS2, WEBPASSWORD |
| Web UI | pihole.franksdank.cloud (Caddy → 8083) |
Dashboard + admin, family login |
Quadlet
- Install path on Dankclaw:
~/.config/containers/systemd/pihole.container(unitpihole.service) - Network:
slirp4netns(the same pattern as open-webui and gitea) - Published ports: 53/udp + 53/tcp (DNS), 8083/tcp (web UI)
- Mounts:
/home/dank/Containers/pihole/etc-pihole→/etc/pihole(config + state, preserved across restarts)/home/dank/Containers/pihole/etc-dockers→/etc/dockers(blocklists)/home/dank/Containers/pihole/addn-hosts→/etc/pihole/hosts/addn-hosts(read-only; the record file from this repo)
- Restart:
Restart=always,RestartSec=5 - Deploy:
./scripts/deploy-pihole-quadlet.sh dankclaw(idempotent; preserves the data dirs)
Records
FTL does not read /etc/dnsmasq.d/ — its generated dnsmasq.conf has no conf-dir. The only file-based local-record mechanism is hostsdir=/etc/pihole/hosts: FTL parses every file in that directory as an /etc/hosts file. The quadlet therefore mounts the repo record file there, read-only:
/home/dank/Containers/pihole/addn-hosts -> /etc/pihole/hosts/addn-hosts
The file format is hosts-style (<IP> <name> [<name> ...]), one A record per Caddy vhost, all pointing at 192.168.0.143. The file stays in sync with the Caddyfile (tests/pihole.bats cross-checks the two). Add a name there and in Porkbun, then deploy. See LAN-DNS for the two-place rule.
Pitfall (2026-08-24): the first quadlet mounted the file into /etc/dnsmasq.d/. FTL never read it; names only resolved from the query cache. A fresh name returned NXDOMAIN. Mount into /etc/pihole/hosts/, not /etc/dnsmasq.d/.
Pitfall (2026-10-19): FTL can come up with the record file mounted and still serve none of it. After a Dankclaw restart every franksdank.cloud name went NXDOMAIN for LAN clients while external names kept answering — from FTL's cache, which is exactly what makes the failure read like a client or an upstream problem. The instrument is on the box: getent hosts grafana.franksdank.cloud (or dig +short @192.168.0.143 <name>) answering empty means FTL is holding no local records at all; here the file was present and readable inside the container (-rw-rw-r-- … /etc/pihole/hosts/addn-hosts, 44 records) and dns.hosts in pihole.toml was intact. podman exec pihole pihole restartdns reload had every name answering within seconds. The mechanism is not established — the same mounts and the same config read fine before and after the reload — so treat it as a boot-time load failure of the record sources, not as a lost or unmounted file. What it cost: the whole Playwright gate, 18 specs across every vhost failing ERR_NAME_NOT_RESOLVED while the fleet itself was healthy (E2E Suite).
Upstream
The slirp netns has no useful resolv.conf. The env file pins the upstreams: DNS1=1.1.1.1, DNS2=1.0.0.1 (mirrors the retired dnsmasq.conf server= lines). Every query that is not a local record goes to those resolvers. The zone apex and unknown subdomains fall through to Porkbun.
Web UI and API
- URL:
https://pihole.franksdank.cloud/(Caddy vhost, family basicauth → 8083) - Password:
WEBPASSWORDin the env file (the shared family password, the same credential asCADDY_BASICAUTH_HASH) - API:
https://pihole.franksdank.cloud/api/...— authenticate with the session cookie, then query the blocklist, the local DNS records, and the query log - Port on the LAN:
http://192.168.0.143:8083/(direct, no Caddy)
Host prerequisites
net.ipv4.ip_unprivileged_port_start=53, persisted in/etc/sysctl.d/99-dnsmasq.conf— rootless Podman may then publish port 53. The repo filedankclaw/pihole/sysctl/99-dnsmasq.confis the single source of truth:deploy_piholeinstalls it, stages any drop-in that sets a value above 53 in the home trash, comments out a stale active line in/etc/sysctl.conf, and applies everything withsysctl --system(the same code path boot uses via systemd-sysctl). Incident (2026-08-26): the value was reset to 80 by two sources — a later-sorted drop-in (99-rootless-ports.conf) and a stale line in/etc/sysctl.conf, which systemd-sysctl processes after all ofsysctl.d/and therefore overrides every drop-in. The runtime override from an earlier manualsysctl -wdied with a reboot, andpihole.servicecrash-looped onbind: permission deniedfor port 53 (restart counter 1645) until both conflicts were removed.DNSStubListener=noin/etc/systemd/resolved.conf— the resolved stub would otherwise hold port 53 inside the container netns.
Both prerequisites survive the dnsmasq → Pi-hole swap. Do not remove them.
Failure modes
| Symptom | First check |
|---|---|
dig @192.168.0.143 fails entirely |
Quadlet down: systemctl --user status pihole |
| A name does not resolve for a new client | The client's resolv.conf does not show 192.168.0.143 — the G36 hand-off is missing. See LAN-DNS |
| The web UI asks for a password loop | WEBPASSWORD in the env file is the placeholder changeme — fill the real value, restart the unit |
| Upstream queries fail (local names still answer) | DNS1/DNS2 in the env file are missing or wrong |
Port 53 bind: permission denied crash loop after a reboot |
sysctl conflict: the value must be 53 at boot, and remember /etc/sysctl.conf is applied after all of sysctl.d/. Redeploy pihole — deploy_pihole reinstalls the drop-in, neutralizes conflicting drop-ins, and comments out a stale /etc/sysctl.conf line |
Single-label name (for example dankclaw) answers 127.0.1.1 |
The rootless container /etc/hosts carries the host entry (127.0.1.1 <host>), and FTL serves it. Use the FQDN, or add an /etc/hosts entry on the client |
Every franksdank.cloud name is NXDOMAIN for LAN clients while external names still answer |
FTL holds no local records: getent hosts <name> on Dankclaw comes back empty → podman exec pihole pihole restartdns reload (the 2026-10-19 pitfall under Records) |
| One client gets nothing while every other client is fine | FTL rate-limiting that client: podman exec pihole grep Rate-limiting /var/log/pihole/FTL.log. A 10.0.2.x client address is a pasta container's DNS forwarder, not a machine |
See Also
- LAN-DNS — name resolution flow, router hand-off, split DNS
- Caddy — the
pihole.franksdank.cloudvhost - Dankclaw Stack — the host that carries Pi-hole