How to Set Up Pi-hole with Docker Compose: Network-Wide Ad Blocking in Minutes

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.

  1. Log in to your router’s admin panel (usually 192.168.1.1 or 192.168.0.1)
  2. Find the DNS settings (often under WAN, DHCP, or Network settings)
  3. Set the primary DNS to your Pi-hole server’s local IP (e.g., 192.168.1.6)
  4. 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
  5. 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

Your email address will not be published. Required fields are marked *