Pi-hole on Raspberry Pi Lab:

Deploying with Docker & Portainer, then Using Pi-hole Daily

Why Pi-hole?

Pi-hole acts as a network-wide DNS sinkhole that blocks ads, trackers, and malicious domains before they reach client devices. I built a hands-on lab on a Raspberry Pi using Docker and Portainer to learn deployment and daily operations end-to-end.

Lab Setup Overview

  • Hardware: Raspberry Pi 4, microSD (16 GB+), reliable power, Ethernet to home LAN.
  • Network plan: Reserve a static IP for the Pi (e.g., 192.168.1.2); Pi-hole will query DNS requests.
  • Software: Raspberry Pi OS Lite (64-bit recommended), Docker Engine, Portainer CE, Pi-hole container.
Network plan diagram for Pi-hole on a home LAN
Simple LAN: router ⇄ Raspberry Pi (Pi‑hole) ⇄ clients.

Install OS, Docker, and Portainer

1) Raspberry Pi OS

  1. Flash Raspberry Pi OS Lite to microSD (Raspberry Pi Imager).
  2. Boot, SSH in (ssh pi@raspberrypi.local), then update: sudo apt update && sudo apt full-upgrade -y.
  3. Set a static IP via router.

2) Install Docker Engine

# Install Docker on Raspberry Pi OS
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out/in or reboot to apply group membership

3) Install Portainer CE

sudo docker volume create portainer_data
sudo docker run -d \
  -p 8000:8000 -p 9443:9443 \
  --name portainer \
  --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:latest
# Browse to https://<pi-ip>:9443 to set admin password and add the local Docker environment

Deploy Pi-hole via Portainer

In Portainer, I prefer a Stack (Compose) for tidy volumes and repeatability.

# portainer stack: pihole
version: "3.8"
services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "80:80/tcp"      # admin UI
      # - "67:67/udp"    # enable if you will use Pi-hole as DHCP server
    environment:
      TZ: "America/Los_Angeles"
      WEBPASSWORD: "CHANGE_ME_SECURELY"
      FTLCONF_LOCAL_IPV4: "192.168.1.2"   # Pi's LAN IP
    volumes:
      - ./etc-pihole:/etc/pihole
      - ./etc-dnsmasq.d:/etc/dnsmasq.d
    cap_add:
      - NET_ADMIN        # required if using DHCP
    restart: unless-stopped
  1. Portainer → StacksAdd stack, paste the YAML, then Deploy the stack.
  2. Browse to http://<pi-ip>/admin and log in with your WEBPASSWORD.

Note: Ensure nothing else on the Pi is using port 53. On Raspberry Pi OS this is typically free; if not, stop any conflicting resolver.

Portainer stack showing Pi-hole
Pi-hole running as a Portainer stack with persistent volumes.

Point Your Network/Devices to Pi-hole

  • Router-level (recommended): Set primary DNS to the Pi-hole IP (192.168.1.2). Keep a secondary upstream only if your router won’t force clients to use the primary (to avoid bypassing).
  • Per-device: On a test laptop/phone, set DNS to the Pi-hole IP to validate before changing the whole network.

Using Pi-hole Day to Day

Dashboard

The dashboard summarizes total queries, blocked domains, top permitted/blocked clients, and upstream response times. I verify activity immediately after pointing a test device to Pi-hole.

Pi-hole dashboard overview
High-level view to confirm traffic is flowing through Pi-hole.

Query Log: Investigate & Act

  • Open Query Log, filter by client or domain to see live lookups.
  • Click a domain to allow or block it instantly. Use this for rapid testing when a site/app breaks.
  • Patterns of telemetry/ads become candidates for permanent blocklists or regex rules.

Allow/Block: Whitelist & Blacklist

  • Whitelist (Allowlist): Add domains that must always resolve (e.g., a SaaS auth endpoint).
  • Blacklist (Denylist): Add domains you always want blocked (e.g., aggressive trackers).
  • Use comments when adding entries to document the reason and owner.

Blocklists (Adlists) & Gravity

  • Under Group Management → Adlists, add curated lists (e.g., community-maintained). Avoid piling on too many; quality beats quantity.
  • Run Tools → Update Gravity after changing lists to rebuild the blocking database.
  • Periodically review Top Blocked Domains to prune noisy or conflicting lists.

Per-Client Rules & Groups

  • Create groups like Default, Kids, IoT.
  • Assign clients by IP/MAC and map groups to specific adlists, blocklists, or regexes.
  • Example: stricter rules for Kids, looser for Work, and a minimal set for IoT.

DHCP (Optional)

If your router’s DNS settings are limited, enabling Pi-hole’s DHCP server ensures every client uses Pi-hole. Steps:

  1. In Pi-hole, go to Settings → DHCP, enable DHCP, set the correct range (e.g., 192.168.1.50–192.168.1.200), and the router/gateway IP.
  2. Recreate the container with UDP 67 exposed (see Compose comment) and NET_ADMIN capability.

Conditional Forwarding (Local Hostnames)

To resolve local device names (e.g., nas.local.lan), enable Settings → DNS → Conditional forwarding and point to your router’s IP and local domain (if set).

Upstream DNS & Privacy

  • Under Settings → DNS, choose upstream providers (e.g., Cloudflare, Quad9) and enable DNSSEC if supported.
  • For maximum privacy, you can add a local recursive resolver (e.g., Unbound) later and point Pi-hole to it.

Backup & Maintenance

  • Use Settings → Teleporter to export/import configuration (adlists, groups, domains, clients).
  • Keep the container updated: pull the latest image and redeploy the stack; volumes preserve your settings.
  • Check Tools → Tail pihole-FTL.log for troubleshooting DNS behavior.