Pi-hole
Pi-hole provides DNS with network-wide ad blocking for the homelab. This page documents the in-cluster Pi-hole, which runs in k3s with a dedicated IP (192.168.1.21) assigned by kube-vip.
Role: secondary resolver (DNS2). This
192.168.1.21instance is the in-cluster secondary to the primary hardware Pi-hole at192.168.1.60. It is kept in sync (blocklists and local*.local.spaelling.xyzrecords) by a per-pod nebula-sync sidecar — see DNS redundancy for the architecture and full apply steps. Before adding it to DHCP, make sure the sync is working so it is a true equivalent of the primary.
The deployment uses the pihole-kubernetes classic manifests.
Install
Apply the kube-vip ConfigMap so Pi-hole gets its dedicated IP, then create the namespace and deploy:
kubectl apply -f kubevip-configmap.yaml
kubectl create namespace pihole
# One shared web/API password: generated once, used by both secrets so the
# nebula-sync sidecar can always authenticate against its local Pi-hole.
# (-base64 24 stays on one line; larger values wrap and embed a newline.)
PIHOLE_WEBPASSWORD=$(openssl rand -base64 24)
kubectl create secret -n pihole generic pihole-webpassword --from-literal="password=$PIHOLE_WEBPASSWORD"
# nebula-sync credentials (primary app password + localhost replica password):
kubectl create secret -n pihole generic nebula-sync-credentials --from-literal="primary=http://192.168.1.60|<PRIMARY_APP_PASSWORD>" --from-literal="replicas=http://localhost|$PIHOLE_WEBPASSWORD"
kubectl apply -f pihole.yaml
kubevip-configmap.yaml is in the k3s/kubevip directory. The generated password is stored as a Kubernetes secret — retrieve it later with kubectl get secret -n pihole pihole-webpassword -o jsonpath='{.data.password}' | base64 --decode. The nebula-sync-credentials secret and the sync design are covered in DNS redundancy.
Verify DNS is working
Once pods are running, test that Pi-hole is answering DNS queries on its dedicated IP:
tcping -f 4 -t 5 192.168.1.21 53
nslookup google.com 192.168.1.21
Both should succeed. If nslookup returns an answer from 192.168.1.21, Pi-hole is operational.
Expose the admin UI
Apply the ingress for the primary Pi-hole instance:
kubectl apply -f pihole-0-ingress.yaml
This creates a Traefik IngressRoute for the Pi-hole web UI.
Check logs
# Latest logs from the primary instance
kubectl logs pihole-0 -n pihole
# Stream logs in real time
kubectl logs -f pihole-0 -n pihole
# Logs from all replicas simultaneously
kubectl logs -l app=pihole -n pihole --all-containers
# DNS query log (inside the pod)
kubectl exec -it pihole-0 -n pihole -- tail -f /var/log/pihole/pihole.log
Check rollout status
After making changes to the StatefulSet (e.g. updating the image or config), monitor the rollout:
kubectl rollout status statefulset pihole -n pihole