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.

💡 This article contains affiliate links. If you buy through them, we earn a small commission at no extra cost to you. Learn more.

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):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: no
      addresses:
        - 192.168.1.10/24
      routes:
        - to: default
          via: 192.168.1.1
      nameservers:
        addresses: [1.1.1.1, 8.8.8.8]

Apply it:

1
sudo netplan apply

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:

1
sudo ss -lptn 'sport = :53'

If you see systemd-resolved bound to 0.0.0.0:53, disable it:

1
2
3
sudo systemctl disable --now systemd-resolved
sudo rm /etc/resolv.conf
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf

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:

1
mkdir -p ~/pihole && cd ~/pihole

Now create docker-compose.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
services:
  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    restart: unless-stopped
    network_mode: host
    environment:
      TZ: "America/New_York"          # Set your timezone
      WEBPASSWORD: "your-secure-password-here"
      PIHOLE_DNS_1: "1.1.1.1"
      PIHOLE_DNS_2: "8.8.8.8"
      DNSSEC: "true"
      DNSMASQ_LISTENING: "all"
      VIRTUAL_HOST: "pihole.local"
      WEB_PORT: "8080"
    volumes:
      - ./etc-pihole:/etc/pihole
      - ./etc-dnsmasq.d:/etc/dnsmasq.d
    cap_add:
      - NET_ADMIN

Key settings explained:

SettingWhat It Does
network_mode: hostPi-hole uses your host’s network stack directly — required for DNS to work properly
WEBPASSWORDPassword for the web UI — change this!
PIHOLE_DNS_1/2Upstream DNS servers Pi-hole queries for non-blocked domains
DNSSECValidates DNS responses to prevent spoofing
WEB_PORT: 8080Moves the web UI to port 8080 (port 80 is often in use)
cap_add: NET_ADMINRequired 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

1
docker compose up -d

Watch the logs to confirm it starts cleanly:

1
docker compose logs -f pihole

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.1 as 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:

1
2
3
4
5
6
7
8
# Linux
sudo dhclient -r && sudo dhclient

# Windows
ipconfig /release && ipconfig /renew

# macOS
sudo ipconfig set en0 DHCP

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:

DomainIP Address
nas.home192.168.1.20
pihole.home192.168.1.10
grafana.home192.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.homenas.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:

  1. Disable DHCP on your router first
  2. Go to Settings → DHCP in Pi-hole
  3. Enable DHCP, set your range (e.g., 192.168.1.100 to 192.168.1.200)
  4. 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    network_mode: host
    command: proxy-dns --port 5053 --upstream https://1.1.1.1/dns-query --upstream https://1.0.0.1/dns-query

  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    restart: unless-stopped
    network_mode: host
    environment:
      TZ: "America/New_York"
      WEBPASSWORD: "your-secure-password-here"
      PIHOLE_DNS_1: "127.0.0.1#5053"   # Use cloudflared
      PIHOLE_DNS_2: "8.8.8.8"           # Fallback
      DNSSEC: "false"                    # DoH handles this
      DNSMASQ_LISTENING: "all"
      WEB_PORT: "8080"
    volumes:
      - ./etc-pihole:/etc/pihole
      - ./etc-dnsmasq.d:/etc/dnsmasq.d
    cap_add:
      - NET_ADMIN
    depends_on:
      - cloudflared

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:

1
2
3
4
5
6
7
  unbound:
    image: klutchell/unbound:latest
    container_name: unbound
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./etc-unbound:/etc/unbound/custom.conf.d

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:

1
docker exec pihole pihole whitelist example.com

Or via the web UI: Whitelist → Add a domain.

Blacklist a domain (add your own blocks):

1
docker exec pihole pihole blacklist tracking-domain.com

Useful whitelist entries that are commonly blocked by strict lists:

1
2
3
docker exec pihole pihole whitelist clients3.google.com
docker exec pihole pihole whitelist clientservices.googleapis.com
docker exec pihole pihole whitelist fonts.gstatic.com

Useful Pi-hole Commands

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Check Pi-hole status
docker exec pihole pihole status

# Update Pi-hole's gravity (blocklist database)
docker exec pihole pihole updateGravity

# Temporarily disable blocking for 30 minutes
docker exec pihole pihole disable 30m

# Re-enable blocking
docker exec pihole pihole enable

# View recent queries in real-time
docker exec pihole pihole tail

# Flush the query log
docker exec pihole pihole flush

Backup Pi-hole

Your Pi-hole config lives in two Docker volumes:

1
2
~/pihole/etc-pihole/      # Main config, blocklists, whitelists
~/pihole/etc-dnsmasq.d/   # DNS configuration

Back them up with a simple script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/bash
BACKUP_DIR="/backups/pihole"
DATE=$(date +%Y-%m-%d)

mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/pihole-$DATE.tar.gz" \
    ~/pihole/etc-pihole \
    ~/pihole/etc-dnsmasq.d

echo "Pi-hole backed up to $BACKUP_DIR/pihole-$DATE.tar.gz"

# Keep only the last 30 days
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +30 -delete

Add this to cron to run nightly:

1
2
3
crontab -e
# Add this line:
0 3 * * * /home/user/pihole-backup.sh >> /var/log/pihole-backup.log 2>&1

Updating Pi-hole

Updating Pi-hole in Docker is straightforward:

1
2
3
cd ~/pihole
docker compose pull
docker compose up -d

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:

FeaturePi-holeAdGuard Home
Blocklist support✅ Excellent✅ Excellent
Web UIGoodExcellent
DoH/DoT built-in❌ (needs Cloudflared)✅ Built-in
DHCP server
Custom DNS rules
Community sizeHugeGrowing
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! 🚫📺