Every device I own spends part of its day downloading advertisements so it can throw most of them away. The phone, the laptops, the TV, the little boxes in the lab — each one resolves ad and tracker domains, fetches the payload, and discards it. I wanted to stop paying for that traffic once, centrally, for everything at the same time. So I set up Pi-hole. It runs on Kubernetes. There is no Pi.
This is a build log for a thing that is, as I write it, about forty-five minutes old and has answered twenty-one DNS queries in its entire life. I am getting the honest state out of the way early.
Block it at DNS, not in the browser
An ad-blocker in your browser blocks ads in that browser. It does nothing for the phone app showing you a banner, the smart TV phoning home, or the device you cannot install an extension on at all. The one layer where you can catch all of it at once is DNS — the name lookup every device does before it can load anything. If the name never resolves, the ad never loads.
Pi-hole is a DNS server that answers normally for real domains and refuses to resolve known advertising and tracking ones. Point a device's DNS at it and that device goes quiet. Point a whole network's DNS at it and the whole network goes quiet. I started it with a single blocklist: 96,767 domains it will simply decline to look up. No regex rules, no per-device policies, nothing clever. One list, ninety-six thousand names, off.
Why there is no Pi
Pi-hole is named for the Raspberry Pi, and the canonical setup is a Pi sitting on your home LAN, handed out as the DNS server by your router's DHCP. That works when your network is a home LAN. Mine isn't.
My network is a Tailscale tailnet — the boxes in my house plus the laptops and the phone that leave the house every day. A Raspberry Pi plugged into my router can block ads for the devices sitting at home; it can do nothing for my phone on cellular three states away. If the ad-blocking is going to follow me, it has to live on the tailnet, not on the LAN.
So Pi-hole runs as a container on a Kubernetes cluster I already keep running in the lab, in its own isolated namespace, with its configuration and that 96,767-domain blocklist parked on a small persistent volume so a pod restart does not send it re-downloading everything from scratch. The Raspberry Pi the software is named after is nowhere in the picture.
The trick is a Tailscale sidecar
The interesting part is how the pod reaches the tailnet. Normally you expose something on Kubernetes with a load balancer or an ingress — you give it a way in from the network. I did not want a way in from any network. I wanted the opposite.
The Pi-hole pod runs two containers: Pi-hole itself, and a Tailscale container beside it. The sidecar joins the pod to the tailnet directly, as its own node with its own tailnet name, and keeps that identity on a one-gigabyte volume so it stays the same node across restarts. Nothing is port-forwarded. Nothing is exposed to the public internet — the same rule the rest of the lab lives by. The pod is reachable by every device on the tailnet and by nothing off it.
Then you tell the tailnet to use that node as its DNS resolver, and every device inherits the ad-blocking automatically — at home, on cellular, wherever — without touching a single device's settings by hand.

The ergonomic payoff is the part I like most: a new device needs no configuration at all. It joins the tailnet, it uses the tailnet's DNS, and it is ad-blocked before I have done anything to it. The blocking became a property of the network instead of a checklist I run on every gadget.
DNS on Kubernetes is a small minefield
A few things bite you, and all of them are the sort you only learn by walking into them:
- The cluster already runs its own DNS. Kubernetes has CoreDNS answering on port 53 for internal cluster names, and Pi-hole also wants port 53. The two have to coexist without fighting over the pod's networking.
- Pi-hole must not resolve through itself. If the pod's own upstream lookups get pointed back at Pi-hole, you get a DNS loop — it asks itself the question it was just asked and never reaches an answer. The upstream resolver has to be set to something real and external, on purpose.
- State has to persist or it isn't real. The config and the gravity blocklist live on that volume deliberately. Without it, every reschedule is a fresh, empty Pi-hole that has forgotten all 96,767 domains and blocks nothing until it rebuilds them.
The honest state
It works, and it is barely doing anything. Twenty-one queries answered, because I have only hand-pointed a couple of devices at it so far and have not yet cut the whole tailnet's DNS over to it. It is one default blocklist. There is no dashboard I actually watch, no tuned rules, no allowlist for the things a blocklist inevitably breaks. Ninety-six thousand domains it refuses to resolve, and a network that has barely thought to ask it anything yet.
The journey, so far, is mostly the decision: DNS is the right layer, the tailnet is the right network, and a container with its own tailnet identity beats a Raspberry Pi that can only ever cover the couch. The part where it quietly saves every device a few thousand ad lookups a day is still ahead of it.
The honest read
There is a familiar shape here. The last thing I wrote about, the homelab, ended on the fact that nearly everything I run leans on a single machine with no redundancy. This is the same trade, one layer down. The entire point of Pi-hole is to be the one DNS resolver every device asks. The moment I actually cut the tailnet over to it, this forty-five-minute-old pod becomes something that, if it falls over, takes name resolution down for every device I own at once — no browsing, no apps, nothing, until it comes back.
I traded 'every device downloads ads' for 'every device depends on one pod.' That is the homelab bargain yet again: I keep buying a new capability with a new single point of failure and telling myself the backups make it even. The Pi-hole with no Pi is just one more box I would have to grab first in a fire — except this one the whole network is now listening to.
