Pi-hole is one of the most popular self-hosted tools in the homelab community — and for good reason. It blocks ads, trackers, and malicious domains at the network level, protecting every device on your network without installing anything on individual devices. Smart TVs, smartphones, game consoles — they all benefit automatically.
In this guide, you’ll learn how to run Pi-hole in Docker, configure it properly, set up custom DNS records, and squeeze every last bit of blocking power out of it.
What Is Pi-hole?
Pi-hole is a DNS sinkhole that runs on your network. Instead of blocking ads in a browser (like uBlock Origin), it intercepts DNS queries before they resolve. When a device on your network tries to reach ads.doubleclick.net, Pi-hole returns a blank response — the ad never loads.
Why Docker? Running Pi-hole in Docker gives you:
- Easy updates — swap the container, keep your data
- Isolation — Pi-hole doesn’t touch your host system
- Portability — move it to any machine in minutes
- Simple backups — just copy the volume directory
Prerequisites
Before you start:
- A Linux server or NAS (Raspberry Pi, mini PC, VM — anything works)
- Docker and Docker Compose installed
- A static IP address for your server
- Basic command-line familiarity
If you don’t have a mini PC or Raspberry Pi yet, a Raspberry Pi 5 or an Intel N100 mini PC are both excellent choices for running Pi-hole 24/7.
Step 1: Assign a Static IP to Your Server
Pi-hole needs a stable IP address because you’re going to point your router’s DNS setting at it. If the IP changes, DNS breaks for your whole network.
Option A: Static IP on the server itself
Edit /etc/netplan/01-netcfg.yaml (Ubuntu/Debian with Netplan):
| |
Apply it:
| |
Option B: DHCP reservation on your router
Most routers let you assign a fixed IP to a specific MAC address. This is cleaner because your server stays on DHCP but always gets the same IP. Check your router’s admin panel — look for “DHCP Reservations” or “Static Leases.”
Step 2: Disable the Local DNS Resolver (If Needed)
On Ubuntu/Debian, systemd-resolved listens on port 53 by default. This conflicts with Pi-hole. Check if it’s running:
| |
If you see systemd-resolved bound to 0.0.0.0:53, disable it:
| |
This stops the conflict and gives your server working DNS while Pi-hole takes over.
Step 3: Create the Docker Compose File
Create a directory for Pi-hole:
| |
Now create docker-compose.yml:
| |
Key settings explained:
| Setting | What It Does |
|---|---|
network_mode: host | Pi-hole uses your host’s network stack directly — required for DNS to work properly |
WEBPASSWORD | Password for the web UI — change this! |
PIHOLE_DNS_1/2 | Upstream DNS servers Pi-hole queries for non-blocked domains |
DNSSEC | Validates DNS responses to prevent spoofing |
WEB_PORT: 8080 | Moves the web UI to port 8080 (port 80 is often in use) |
cap_add: NET_ADMIN | Required for Pi-hole to manage network interfaces |
Timezone: Find your timezone string at en.wikipedia.org/wiki/List_of_tz_database_time_zones
Step 4: Start Pi-hole
| |
Watch the logs to confirm it starts cleanly:
| |
You should see Pi-hole starting its DNS and web servers. Once it’s up, open your browser:
http://YOUR_SERVER_IP:8080/admin
Log in with the password you set in WEBPASSWORD.
Step 5: Add Blocklists
Out of the box, Pi-hole includes the default blocklist (~900K domains). You can do much better.
Go to Lists in the Pi-hole admin → Adlists and add these:
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/multi.txt
https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt
https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt
https://raw.githubusercontent.com/nicehash/nicehash-malware-blocklist/master/nicehash-malware-blocklist.txt
After adding them, click Tools → Update Gravity to download and process all lists. This rebuilds Pi-hole’s block database.
For even more lists, check out Firebog.net — a curated collection of high-quality blocklists with a tick-mark system indicating reliability. The “Ticked” lists are safe to use without causing false positives.
Step 6: Configure Your Router to Use Pi-hole
This is the step that protects every device on your network. Go to your router’s admin panel and change the DNS server to your Pi-hole’s IP address.
Where to find it:
- Most routers: LAN Settings → DHCP → DNS Server
- Some routers have separate IPv4 and IPv6 DNS settings — set both
- Set the Primary DNS to your Pi-hole IP (e.g.,
192.168.1.10) - Set Secondary DNS to
1.1.1.1as a fallback (so DNS still works if Pi-hole is down)
After saving, devices that renew their DHCP lease will start using Pi-hole. Force a renewal on your computer:
| |
Then check your Pi-hole dashboard — you should see queries starting to appear.
Step 7: Set Up Custom Local DNS
One of Pi-hole’s underrated features is local DNS resolution. Instead of memorizing IP addresses, you can resolve names like nas.home or grafana.home.
Go to Local DNS → DNS Records in the admin panel and add entries like:
| Domain | IP Address |
|---|---|
nas.home | 192.168.1.20 |
pihole.home | 192.168.1.10 |
grafana.home | 192.168.1.15 |
Now ping nas.home works from any device on your network. Combine this with a reverse proxy like Traefik or Caddy and you get clean, named URLs for all your self-hosted services.
You can also add CNAME records (aliases): Go to Local DNS → CNAME Records and point www.nas.home → nas.home.
Step 8: Configure DHCP (Optional)
Pi-hole can also act as your network’s DHCP server, replacing your router’s DHCP. This is optional but has a big advantage: Pi-hole sees the actual hostnames of devices, giving you much more readable query logs (you’ll see iphone-christian instead of 192.168.1.45).
To enable it:
- Disable DHCP on your router first
- Go to Settings → DHCP in Pi-hole
- Enable DHCP, set your range (e.g.,
192.168.1.100to192.168.1.200) - Set your gateway (router IP)
Warning: Disable DHCP on your router before enabling it in Pi-hole to avoid conflicts.
Step 9: Configure Upstream DNS (Go Further)
The default upstream DNS servers (1.1.1.1, 8.8.8.8) work fine but send your DNS queries to third parties. For better privacy, consider:
Option A: DNS-over-HTTPS with Cloudflared
Add Cloudflared alongside Pi-hole:
| |
This routes all Pi-hole queries through Cloudflared’s DoH proxy — your ISP can no longer snoop on your DNS traffic.
Option B: Unbound (Full Recursive Resolver)
For maximum privacy, run Unbound — a local DNS resolver that goes directly to the root DNS servers without relying on any third party. This is the gold standard for homelab DNS privacy.
Create etc-unbound/unbound.conf:
server:
verbosity: 0
interface: 127.0.0.1
port: 5335
do-ip4: yes
do-udp: yes
do-tcp: yes
do-ip6: no
prefer-ip6: no
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: no
edns-buffer-size: 1232
prefetch: yes
num-threads: 1
so-rcvbuf: 1m
private-address: 192.168.0.0/16
private-address: 169.254.0.0/16
private-address: 172.16.0.0/12
private-address: 10.0.0.0/8
private-address: fd00::/8
private-address: fe80::/10
And add to your docker-compose.yml:
| |
Then set PIHOLE_DNS_1: "127.0.0.1#5335" in Pi-hole.
Step 10: Whitelist and Blacklist Management
Pi-hole occasionally blocks legitimate services. Here’s how to handle it:
Find what’s being blocked:
Check the Query Log in the admin panel. Filter by “Blocked” to see what’s getting caught. If a website stops working, check here first.
Whitelist a domain:
| |
Or via the web UI: Whitelist → Add a domain.
Blacklist a domain (add your own blocks):
| |
Useful whitelist entries that are commonly blocked by strict lists:
| |
Useful Pi-hole Commands
| |
Backup Pi-hole
Your Pi-hole config lives in two Docker volumes:
| |
Back them up with a simple script:
| |
Add this to cron to run nightly:
| |
Updating Pi-hole
Updating Pi-hole in Docker is straightforward:
| |
Or use Watchtower to update Pi-hole automatically.
Monitoring Pi-hole Performance
Once Pi-hole is running, keep an eye on the dashboard’s top metrics:
- Queries today — total DNS queries processed
- Ads blocked — percentage blocked (healthy range: 10–30%)
- Domains on blocklist — number of blocked domains
- DNS queries/sec — watch for spikes that indicate issues
A block rate under 5% might mean Pi-hole isn’t being used as the DNS server. Over 50% might mean your blocklists are too aggressive and you’re getting false positives.
Troubleshooting Common Issues
Pi-hole isn’t blocking anything:
- Verify your router’s DNS is set to Pi-hole’s IP
- Run
nslookup pi.hole YOUR_PIHOLE_IP— should return Pi-hole’s admin page IP - Check if your ISP router has DNS override (some ISPs force their DNS)
Websites are broken after enabling Pi-hole:
- Check the Query Log for false positives
- Temporarily disable Pi-hole (
pihole disable) to confirm it’s the cause - Add the broken domain to your whitelist
Port 53 conflict:
- Run
sudo ss -lptn 'sport = :53'to find what’s using port 53 - Usually
systemd-resolved— disable it as shown in Step 2
Web UI not loading:
- Check if the container is running:
docker ps | grep pihole - Check logs:
docker compose logs pihole - Confirm the WEB_PORT setting in your compose file
“FTL not running” in dashboard:
- Restart Pi-hole:
docker compose restart pihole - Check for port conflicts on port 4711 (Pi-hole’s FTL API port)
Pi-hole vs AdGuard Home
Pi-hole isn’t the only game in town. AdGuard Home is a newer alternative with a polished UI and built-in DoH/DoT support. Here’s a quick comparison:
| Feature | Pi-hole | AdGuard Home |
|---|---|---|
| Blocklist support | ✅ Excellent | ✅ Excellent |
| Web UI | Good | Excellent |
| DoH/DoT built-in | ❌ (needs Cloudflared) | ✅ Built-in |
| DHCP server | ✅ | ✅ |
| Custom DNS rules | ✅ | ✅ |
| Community size | Huge | Growing |
| Docker support | ✅ | ✅ |
If you want DoH without setting up Cloudflared separately, AdGuard Home is worth a look. But Pi-hole’s larger community and extensive documentation make it the safer choice for most users.
Final Thoughts
Pi-hole is one of those tools that pays for itself immediately. The moment it’s running, every device on your network gets cleaner browsing, fewer trackers, and faster page loads (fewer ad requests = faster DNS resolution = faster pages).
Running it in Docker makes management trivial — updates are a one-liner, backups are just copying two directories, and you can migrate to a new machine in minutes.
The biggest win is that it works on devices you can’t install browser extensions on: smart TVs, IoT devices, game consoles, and phones. Pi-hole protects them all.
If you want to go further:
- Add Unbound for full DNS privacy (no third-party DNS resolver)
- Combine with Tailscale to protect your devices when you’re away from home
- Pair with a reverse proxy (Traefik or Caddy) to access Pi-hole’s UI via a clean domain name
Questions? Drop them in the comments below. Happy blocking! 🚫📺