Why Pi-hole?
Every device on your network — phones, smart TVs, IoT gadgets, even your fridge — is quietly phoning home to advertisers and trackers. Browser extensions can’t help your Roku. VPNs don’t block ads at the DNS level. What if you could block all of it, for every device, with a single setup?
That’s Pi-hole. It’s a DNS sinkhole that acts as your network’s DNS server, intercepting requests to known ad-serving and tracking domains before they ever resolve. Think of it as a bouncer for your network traffic — it checks the guest list and turns away the riffraff.
And the best part? You can have it running in under 10 minutes with Docker Compose.
What You’ll Need
- A Linux server, Raspberry Pi, or any machine that runs Docker (Ubuntu 22.04+ recommended)
- Docker and Docker Compose installed
- Port 53 (DNS) and port 80 (web admin) available on the host
- Basic comfort with the terminal
- Access to your router’s admin panel (for the final step)
Step 1: Install Docker and Docker Compose
If you haven’t already, get Docker installed. On Ubuntu:
# Add Docker's official GPG key
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Add your user to the docker group (so you don't need sudo every time)
sudo usermod -aG docker $USER
newgrp docker
Verify everything works:
docker --version
docker compose version
Step 2: Create the Docker Compose File
Create a directory for your Pi-hole setup and drop in a docker-compose.yml:
mkdir -p ~/pihole && cd ~/pihole
nano docker-compose.yml
Here’s a production-ready Compose file with sensible defaults:
version: "3.8"
services:
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80/tcp"
environment:
TZ: "America/New_York"
WEBPASSWORD: "your-secure-password-here"
DNSMASQ_LISTENING: "all"
PIHOLE_DNS_: "1.1.1.1;1.0.0.1"
FTLCONF_LOCAL_IPV4: "192.168.1.6"
volumes:
- ./etc-pihole:/etc/pihole
- ./etc-dnsmasq.d:/etc/dnsmasq.d
cap_add:
- NET_ADMIN
networks:
- pihole-net
networks:
pihole-net:
driver: bridge
A few things to customize before you launch:
- TZ: Set your timezone (e.g.,
Europe/London,Asia/Tokyo) - WEBPASSWORD: Pick a strong password for the admin dashboard
- FTLCONF_LOCAL_IPV4: Set this to your server’s local IP address — this is critical for the admin dashboard to work correctly
- PIHOLE_DNS_: Upstream DNS servers. Cloudflare (1.1.1.1) is fast and privacy-respecting. You can also use Google (8.8.8.8), Quad9 (9.9.9.9), or your own recursive resolver
Step 3: Launch Pi-hole
docker compose up -d
Check that it’s running:
docker compose ps
docker compose logs -f pihole
You should see Pi-hole’s startup messages, including the line Pi-hole blocking is enabled. If you see that, you’re golden.
Step 4: Access the Admin Dashboard
Open your browser and navigate to:
http://YOUR_SERVER_IP/admin
Log in with the WEBPASSWORD you set in the Compose file. You’ll be greeted by the Pi-hole dashboard — a beautiful overview of your network’s DNS queries, blocked domains, and query types.
At this point, Pi-hole is running but your network isn’t using it yet. That’s the next step.
Step 5: Point Your Router at Pi-hole
This is the magic step. You need to tell your router to use Pi-hole as the DNS server for your entire network.
- Log in to your router’s admin panel (usually
192.168.1.1or192.168.0.1) - Find the DNS settings (often under WAN, DHCP, or Network settings)
- Set the primary DNS to your Pi-hole server’s local IP (e.g.,
192.168.1.6) - Optionally set a secondary DNS (like
1.1.1.1) as a fallback — though purists will tell you to leave it blank so all queries go through Pi-hole - Save and reboot your router (or just release/renew DHCP leases on your devices)
Alternatively, if your router doesn’t let you change the DNS server, you can configure individual devices to use Pi-hole’s IP as their DNS. But the router-level approach is the real power move — it covers everything, including devices that don’t let you configure DNS manually.
Step 6: Verify It’s Working
After your devices renew their DHCP leases (or you reboot them), check the Pi-hole dashboard. You should start seeing queries flowing in within minutes.
For a quick test from any device on your network:
nslookup doubleclick.net
If Pi-hole is working, this should resolve to 0.0.0.0 (or your Pi-hole’s IP), meaning the ad domain was blocked. Try the same lookup for a normal domain like example.com — that should resolve normally.
Going Further: Customization and Pro Tips
Add Custom Blocklists
Pi-hole comes with a solid default blocklist, but you can add more. In the admin dashboard, go to Group Management → Adlists and add URLs like:
# Steven Black's unified list
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
# OISD (balanced, low false-positive)
https://big.oisd.nl/
# NoTracking blocklist
https://raw.githubusercontent.com/notracking/hosts-blocklists/master/hostnames.txt
After adding lists, go to Tools → Update Gravity to pull them in. OISD’s “big” list alone blocks over 400,000 domains and is a great starting point.
Whitelist Domains
Sometimes Pi-hole is too good at its job. If a website or service breaks, check the Query Log in the dashboard to see what got blocked, then add it to the whitelist (Domain Management → Whitelist). Common culprits include certain Microsoft domains, some banking apps, and weather widgets.
Set Up Conditional Forwarding
If you want Pi-hole to resolve local hostnames (so you can see “living-room-tv” instead of “192.168.1.42” in your dashboard), enable conditional forwarding in Settings → DNS. Set the router IP and local domain (usually local or lan).
Enable DNS-over-HTTPS (DoH) Upstream
For extra privacy, you can route Pi-hole’s upstream queries through DNS-over-HTTPS using cloudflared as a sidecar container. This prevents your ISP from seeing your DNS queries even though Pi-hole is blocking the ads:
# Add to docker-compose.yml
cloudflared:
image: visibilityspots/cloudflared
container_name: cloudflared
restart: unless-stopped
ports:
- "5053:5053/udp"
networks:
- pihole-net
Then change PIHOLE_DNS_ to 172.20.0.2#5053 (the cloudflared container’s IP on the Docker network).
Run Pi-hole on a Raspberry Pi
This setup works beautifully on a Raspberry Pi 3B+ or newer. Just make sure you’re running a 64-bit OS (like Raspberry Pi OS 64-bit) for the best Docker compatibility. A Pi 4 with 2GB RAM is more than enough — Pi-hole uses surprisingly little resources. On idle, mine uses about 50MB of RAM and barely touches the CPU.
Common Pitfalls and Troubleshooting
Port 53 Already in Use
On some systems, systemd-resolved listens on port 53. You’ll see an error like bind: address already in use. Fix it:
# Disable systemd-resolved's stub listener
sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf
sudo systemctl restart systemd-resolved
Admin Dashboard Shows “No DNS Queries”
Double-check that your devices are actually using Pi-hole as their DNS. Run nslookup google.com and verify the server listed is your Pi-hole’s IP. If not, your router might not have applied the DNS change, or your device might be using DNS-over-HTTPS internally (Firefox and Chrome both do this by default — you’ll need to disable DoH in the browser settings for full network-wide blocking).
Docker Container Keeps Restarting
Check the logs: docker compose logs pihole. The most common cause is a permission issue with the mounted volumes. Make sure the directories exist:
mkdir -p ~/pihole/etc-pihole ~/pihole/etc-dnsmasq.d
IPv6 Leaks
If your network supports IPv6, devices might bypass Pi-hole by using IPv6 DNS. Either disable IPv6 on your router or configure Pi-hole to handle IPv6 as well (add ::1 to the upstream DNS and ensure the container has IPv6 enabled).
Why This Matters
Running your own DNS resolver isn’t just about blocking ads — it’s about owning a piece of your digital infrastructure. Every DNS query your devices make is a data point about your habits, interests, and routines. When you use Google’s or your ISP’s DNS, you’re handing them a detailed log of everything you do online.
Pi-hole gives you back that control. It’s fast (cached responses are nearly instant), private (queries stay on your network), and satisfying in a way that few infrastructure projects are. There’s something deeply gratifying about watching the dashboard light up with blocked queries — knowing that your smart TV just tried to phone home to five different trackers and got nowhere.
And once you’ve experienced an ad-free smart TV, you’ll never go back.
Quick Reference
| Action | Command |
|---|---|
| Start Pi-hole | docker compose up -d |
| Stop Pi-hole | docker compose down |
| View logs | docker compose logs -f pihole |
| Update Pi-hole | docker compose pull && docker compose up -d |
| Admin dashboard | http://YOUR_IP/admin |
| Temporarily disable blocking | Dashboard → Disable → Pick a duration |
Happy blocking!
Leave a Reply