

Homelab from Zero #4: Pi-hole, network-wide ad blocking
This is part 4 of Homelab from Zero. In part 3 you installed Docker and ran your first service. Today the whole household benefits: we’ll run Pi-hole, an ad blocker that works at the network level, so every phone, laptop, and smart TV gets cleaner pages with nothing installed on them.
How blocking the whole network works
Every time a device loads a page, it first asks a DNS server “what’s the
address for ads.example.com?” Normally your router forwards that question to
your ISP and gets an answer. Pi-hole sits in the middle as your DNS server: for
normal sites it answers truthfully, but for known ad and tracker domains it
answers “nowhere,” so the ad never loads.
Because every device already asks DNS for everything, you block ads everywhere by changing one setting, no per-device apps, no browser extensions.
Step 1: Free up port 53 (Ubuntu only)
Pi-hole needs port 53, the DNS port. On Ubuntu Server a background service
called systemd-resolved is already sitting on it, so we move it aside first:
sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.confsudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.confsudo systemctl restart systemd-resolvedOn a Raspberry Pi running Raspberry Pi OS you can skip this step, port 53 is already free.
Step 2: Run Pi-hole
Same docker run shape as part 3, with a few more lines. Pick a real password
where it says changeme:
docker run -d \ --name pihole \ -p 53:53/tcp -p 53:53/udp \ -p 8081:80 \ -e TZ=Etc/UTC \ -e FTLCONF_webserver_api_password=changeme \ -e FTLCONF_dns_listeningMode=ALL \ -v ~/pihole:/etc/pihole \ --restart unless-stopped \ pihole/pihole:latestWhat the new pieces do:
| Piece | Plain English |
|---|---|
-p 53:53/tcp -p 53:53/udp |
The DNS port: this is what devices will ask |
-p 8081:80 |
The admin web page (part 3’s Heimdall already has 8080) |
-e TZ=Etc/UTC |
Your timezone, so the graphs read right: set your own |
FTLCONF_webserver_api_password |
The password for the admin page |
FTLCONF_dns_listeningMode=ALL |
Answer queries coming from other devices |
Step 3: Open the dashboard
On your normal computer, visit the server’s address from part 2 with the admin
port and /admin on the end:
http://192.168.1.73:8081/adminLog in with the password you set. You’ll see a dashboard with a queries graph, empty for now, because nothing is using Pi-hole yet. Let’s fix that.
Step 4: Point your network at it
The easiest way to cover every device at once is to set Pi-hole as the DNS server on your router. Log into your router’s admin page (the same one from part 2), find the DNS or DHCP settings, and set the primary DNS server to your Pi-hole’s address:
192.168.1.73Leave the secondary blank, or set it to the same address, if you list your ISP’s DNS as backup, devices will quietly use it and skip the blocking.
Save, then reconnect a device to the network (toggle Wi-Fi off and on). Reload a few pages and watch the queries graph on the dashboard start to climb. That’s your whole house flowing through Pi-hole.
Can’t change the router? Some ISP routers don’t allow it. As a fallback, set the DNS manually on individual devices instead, same address, in each device’s network settings.
The commands you’ll actually use
| Command | What it does |
|---|---|
docker logs pihole |
What is Pi-hole saying? (start here if DNS breaks) |
docker exec pihole pihole -g |
Update the block lists |
docker exec -it pihole pihole setpassword |
Change the admin password |
docker restart pihole |
Turn it off and on again |
docker stop pihole |
Stop it: the network falls back to the router’s DNS |
One safety note: if you ever delete the container while the router points at it, the house loses DNS until you change the router back. Stopping Pi-hole is fine, just set the router’s DNS back to automatic first if you’re removing it for good.
What you have now
Network-wide ad and tracker blocking from a single container, covering every
device that touches your network, and a dashboard that quietly shows how much
junk it’s swatting. The pattern is the same one from part 3: one docker run, a
port, a config folder.
In part 5 we make the lab tell us when something’s wrong: Uptime Kuma, monitoring with alerts, so you find out a service is down before the household does.
← Back to blog