Running Pi-hole on Kubernetes with Helm and MetalLB


estimated read time: 4 minutes

Running Pi-hole on Kubernetes with Helm and MetalLB

Why Pi-hole in Kubernetes?

Pi-hole is the standard homelab DNS server — it blocks ads and trackers at the network level and lets you define internal A records so services like proxmox.home.lan resolve to real IPs.

Most guides run Pi-hole directly on a Raspberry Pi or as a Docker container. Running it in Kubernetes adds a bit of complexity upfront but pays dividends: it restarts automatically if it crashes, deploys consistently from version-controlled config, and sits alongside your other cluster workloads in Grafana and Loki.

The one thing Kubernetes Pi-hole needs that a bare-metal Pi deployment does not is a stable IP address. Kubernetes service IPs are ephemeral by default — but with MetalLB already running in the cluster, you can assign a real IP from your LAN range and point your router’s DHCP at it.

Source Code

The Helm chart and Kubernetes manifests are at dfoulkes/pihole-kubernetes. Clone it and customise the values.yaml for your network before deploying.

Deployment

The chart is deployed with Helm. The package is located at ~/source/pi-cluster/pihole in the cluster repo:

helm install pihole ./pihole \
  -n pihole \
  --create-namespace \
  -f values.yaml

Verify the pod is running:

kubectl get pods -n pihole
kubectl get svc -n pihole

The service should have a EXTERNAL-IP assigned from your MetalLB pool — this is the IP you will use as your DNS server.

Network-Specific Configuration

The values.yaml contains network-specific settings that need to be adjusted for your environment. Two sections matter most:

1. Whitelisted Domains

Some legitimate services get caught by Pi-hole’s blocklists. Add them to the whitelist in values.yaml:

whitelist:
  - sdk.split.io # Required by some sites for A/B testing
  - p.typekit.net # Adobe Typekit fonts
  - os.ss2.us
  - insideruser.microsoft.com
  - login.microsoftonline-p.com
  - secure.aadcdn.microsoftonline-p.com
  - auth.gfx.ms
  - windows.net
  - passport.net

The Microsoft entries are needed if you use Power Automate or Azure AD sign-in anywhere on your network.

2. Internal DNS A Records

This is where Pi-hole earns its place as a homelab DNS server. Define internal hostnames that resolve to your services’ MetalLB IPs:

customDnsEntries:
  - "plex.home.lan=10.1.1.12"
  - "proxmox.home.lan=10.1.1.13"
  - "nas.home.lan=10.1.1.14"
  - "work.home.lan=10.1.1.15"

Replace home.lan with your actual internal domain and the IPs with your actual service IPs. With these in place, every device on the network resolves these hostnames without needing a hosts file entry or a local DNS override in each browser.

Point Your Router at Pi-hole

The final step is telling your router to use the Pi-hole IP as the DNS server for DHCP clients. The exact setting varies by router, but you are looking for the primary DNS server in the DHCP or LAN settings.

Set it to the EXTERNAL-IP assigned by MetalLB. Every device on your network will now route DNS through Pi-hole automatically.

Verify It’s Working

From any device on the network:

nslookup proxmox.domain.cloud

You should get back the IP you configured in the A records. For ad blocking:

nslookup doubleclick.net

Pi-hole should return 0.0.0.0 or the Pi-hole’s own IP, indicating the domain is blocked.

The Pi-hole admin interface is accessible at http://<metallb-ip>/admin — it shows live query logs, blocked domain counts, and the top clients on your network.

How It Fits with the Cluster Recovery Runbook

One important operational note: the Pi-hole Helm chart in this repo uses a MySQL sidecar to persist query logs and blocklist state across pod restarts. That MySQL instance runs on the same node as the Pi-hole pod. After a power outage, if that node is slow to come back, Pi-hole will appear to start but DNS resolution may degrade once the in-memory cache expires. Always verify the Pi-hole pod is fully Running (not just Pending) before relying on it for network DNS.