Caddy left compose.yaml for a quadlet unit (caddy.service): joins dankclaw_default for container-name upstreams, Pull=never for the local-only image, Porkbun credentials via /home/dank/Containers/caddy/env written by deploy from .env, bare-IP-catch-all health check. deploy_caddy installs, enables, and starts the unit after retiring the bare container (state is in host volumes). Pi-hole: the repo now owns the sysctl drop-in (dankclaw/pihole/sysctl/99-dnsmasq.conf, ip_unprivileged_port_start=53); deploy_pihole installs it to /etc/sysctl.d/, stages conflicting later-sorted drop-ins in the home trash, and applies 'sysctl --system' (the boot code path). This fixes the 2026-08-26 crash loop (port-53 bind denied, restart counter 1645) caused by a second drop-in resetting the value to 80. Tests: new caddy_quadlet.bats seam; caddyfile/deployment_flow assertions repointed from the removed compose service to the quadlet; pihole.bats pins the sysctl drop-in and its deployment; test-changed.sh routes dankclaw/caddy/ and the pihole subtree to their seams. Docs: Caddy, Pi-hole, Dankclaw Stack, work log.
5.5 KiB
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/.
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 later-sorted drop-in that resets the value in the home trash, and applies it withsysctl --system(the same code path boot uses via systemd-sysctl). Incident (2026-08-26): a second drop-in (99-rootless-ports.conf, sorted after) reset the value to 80. The runtime override from an earlier manualsysctl -wdied with a reboot, andpihole.servicecrash-looped onbind: permission deniedfor port 53 (restart counter 1645) until the conflict was 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: exactly one file under /etc/sysctl.d/ may set ip_unprivileged_port_start, and it must say 53. Redeploy pihole — deploy_pihole reinstalls the drop-in and neutralizes conflicts |
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 |
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