How to Set Up Pi-hole on Docker: Network-Wide Ad Blocking in 15 Minutes

Here’s a scene that should feel familiar: you’re watching a YouTube video, and before it even starts, you sit through two unskippable ads. Then three banner ads clutter the sidebar. Your phone serves ads in every free app. Your smart TV tracks what you watch so it can sell you more things you don’t need.

What if you could make most of that — most — just disappear? Not just in your browser, but on every device on your network? Your phone. Your tablet. Your IoT gadgets that don’t even have an ad blocker?

That’s Pi-hole. And today, you’re going to set it up.

What Is Pi-hole?

Pi-hole is a DNS sinkhole — a fancy way of saying it sits between your devices and the internet, and when some app or website tries to phone home to an advertiser or tracker, Pi-hole says “no” and returns nothing. The request never goes through. The ad never loads. The tracker never connects.

It was originally designed for the Raspberry Pi (hence the name), but it runs beautifully in Docker, which is how we’ll do it today.

Why DNS-Based Blocking?

You might be thinking: “I already have uBlock Origin in my browser. Isn’t that enough?” It’s a great start, but browser extensions only work in that browser. Pi-hole works at the DNS level, which means:

  • Every device on your network is protected — phones, tablets, smart TVs, IoT devices
  • Ads in mobile apps get blocked too
  • Tracking and telemetry domains get silenced
  • You get a single dashboard to see everything trying to call home on your network

It’s not perfect — some ads are served from the same domain as content, which Pi-hole can’t block without breaking the site. But it eliminates a staggering amount of noise.

What You’ll Need

  • A Linux server, NAS, or Raspberry Pi running Docker and Docker Compose
  • Basic familiarity with the terminal (you’ll copy-paste a few commands)
  • Access to your router’s admin panel (to change DNS settings)
  • Port 53 (DNS) and port 80 (web UI) available on the host

Step 1: Create a Docker Compose File

SSH into your server and create a directory for Pi-hole:

mkdir -p ~/docker/pihole
cd ~/docker/pihole

Create the docker-compose.yml file:

nano docker-compose.yml

Paste the following:

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"
    volumes:
      - ./etc-pihole:/etc/pihole
      - ./etc-dnsmasq.d:/etc/dnsmasq.d
    cap_add:
      - NET_ADMIN

A few important notes about this configuration:

  • Replace America/New_York with your timezone. Find yours with timedatectl list-timezones | grep YourCity.
  • Replace your-secure-password-here with a real password. You’ll need this to log into the Pi-hole admin dashboard.
  • DNSMASQ_LISTENING: "all" allows Pi-hole to respond to requests from any interface. If you’re running this on a server with multiple network interfaces, this is what you want.
  • NET_ADMIN capability is required for Pi-hole to manage DNS properly.
  • The volume mounts ensure your blocklists and settings survive container restarts and updates.

Step 2: Launch Pi-hole

Start it up:

docker compose up -d

Check that it’s running:

docker compose ps

You should see the Pi-hole container with a status of “Up”.

Take a quick look at the logs to make sure everything initialized cleanly:

docker compose logs --tail=20 pihole

You should see something like DNS service is running and Pi-hole blocking is enabled. If you see errors about port 53 already being in use, check for systemd-resolved — we’ll cover that in troubleshooting below.

Step 3: Access the Admin Dashboard

Open a browser and navigate to:

http://YOUR_SERVER_IP/admin

Log in with the password you set in WEBPASSWORD. You’ll be greeted by the Pi-hole dashboard — a beautiful overview of DNS queries, blocked domains, and blocklist statistics.

Right now it probably shows zero queries, because we haven’t pointed any devices at it yet. Let’s fix that.

Step 4: 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 into your router’s admin panel (usually at 192.168.1.1 or 192.168.0.1), find the DNS settings (often under DHCP or WAN settings), and set the primary DNS to your server’s IP address.

The exact location varies wildly by router:

  • Most consumer routers: Look under LAN → DHCP Server → DNS
  • Ubiquiti/UniFi: Settings → Networks → [Your Network] → DHCP Name Server
  • pfSense/OPNsense: Services → DHCP Server → DNS Servers
  • ASUS/Netgear: Under WAN or Advanced Settings → DNS

After saving the new DNS setting, devices on your network will start using Pi-hole for DNS resolution. Some devices will pick up the change immediately; others may need to reconnect to Wi-Fi or renew their DHCP lease.

To force a DHCP lease renewal on a device:

  • Linux/macOS: sudo dhclient -r && sudo dhclient
  • Windows: ipconfig /release && ipconfig /renew in Command Prompt
  • Phones: Toggle Wi-Fi off and back on

Step 5: Watch It Work

Within a few minutes, your Pi-hole dashboard will start lighting up. You’ll see:

  • Total queries vs. blocked queries
  • Top domains being queried (and blocked)
  • Top clients — every device making DNS requests
  • A live query log you can watch in real time

It’s genuinely eye-opening to see how many domains your smart TV or phone talks to in the background. That awareness alone is worth the setup.

Step 6: Tune Your Blocklists

Pi-hole comes with a default blocklist, but you can add more. In the admin panel, go to Group Management → Adlists.

Here are some well-maintained lists to consider:

https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
https://mirror1.malwaredomains.com/files/justdomains
https://s3.amazonaws.com/lists.disconnect.me/simple_malvertising.txt

After adding lists, go to Tools → Update Gravity (or run docker exec -it pihole pihole -g from the terminal) to pull in the new blocklists.

A word of caution: more blocklists isn’t always better. Aggressive lists can break legitimate websites, especially streaming services, banking sites, and some online games. Start conservative, add lists gradually, and whitelist anything that breaks.

Common Pitfalls & Troubleshooting

Port 53 Already in Use (systemd-resolved)

On many modern Linux distributions (Ubuntu 20.04+, Debian 10+), systemd-resolved listens on port 53 locally. You’ll see an error like address already in use in your Pi-hole logs.

Fix it by disabling the stub listener:

sudo nano /etc/systemd/resolved.conf

Change or add this line:

[Resolve]
DNSStubListener=no

Then restart:

sudo systemctl restart systemd-resolved
sudo systemctl restart docker

The Internet Is “Broken” — Some Sites Won’t Load

This almost always means Pi-hole is blocking a domain that the site needs. Check the query log for recently blocked entries related to the broken site, click on it, and hit “Whitelist”. Refresh the page — it should work again.

Clients Bypass Pi-hole

Some devices (Android phones in particular) use DNS-over-HTTPS (DoH) to bypass the network’s DNS settings entirely. If you notice a device that should be using Pi-hole but isn’t showing up in the client list:

  • On Android: Settings → Network → Private DNS → set to “Off”
  • On Firefox: Settings → enable/disable DNS over HTTPS
  • On Chrome: Settings → Security → use secure DNS → Off

For a more robust fix, you can use your router’s firewall rules to redirect all DNS traffic (port 53) to Pi-hole, preventing devices from using their own DNS servers.

Pi-hole Web UI Uses Port 80 — I Need That for Something Else

Change the port mapping in your docker-compose.yml:

ports:
  - "53:53/tcp"
  - "53:53/udp"
  - "8080:80/tcp"

Then access the dashboard at http://YOUR_SERVER_IP:8080/admin.

Keeping Pi-hole Updated

To update Pi-hole to the latest version:

cd ~/docker/pihole
docker compose pull
docker compose up -d

Your settings and blocklists are stored in the mounted volumes, so they’ll survive the update.

What You’ve Built

In about 15 minutes, you’ve set up a network-wide ad blocker that protects every device on your network. No browser extensions needed. No per-device configuration. Just one server quietly saying “no” to thousands of advertising and tracking domains.

It’s one of those rare pieces of infrastructure that pays for itself immediately — not in money, but in the quiet satisfaction of a cleaner, faster, more private internet experience.

And the next time someone on your network complains that “the internet feels faster now,” you’ll know why. You just won’t tell them it was this easy.

Leave a Reply

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