Skip to Content

Dockerized Pi-hole for easy network-wide ad-blocking

Intro

Pretty much everyone, who is tech inclined, blocks their ads nowadays. But what if you could block all the ads on all the browsers on your whole internal network? Including those pesky mobile phones? Enter: pi-hole! Pi-hole works as a DNS server, blocking all pesky DNS queries to URLs that smell like advertisements. With the containerized version of pi-hole you are not only restricted to RPis, but you can run the setup just about everywhere you can run a container in.

I’ve used Pi-hole for some time in my network, running on a Raspberry Pi 3. For a small afternoon project I wanted to put it on a container, for portability and just for the joy of tinkering. I’ll explain the process on this blog post.

My full configuration can be found from Gitlab.

Beast in the wild

TOC

Requirements

  • A RPi. Tested on RPi 3B, but I guess it can run on older Pis also, definitely on newer
  • SSH enabled or local session where you can play around
  • A static IP configured either on the RPi or the network (your router, most probably)

Set-up

Docker

Let’s (lazily) install Docker:
curl -sSL https://get.docker.com | sh

and add it to the docker-user group to run docker without sudo:
sudo usermod -aG docker pi

aaand also docker compose, bc that’s what we are going to use:
sudo apt install docker-compose -y

Pi-hole

There is a really good docker compose example on the docker-pi-hole repo. There is also a bash script version of running plain docker, but my sympathies are on the docker compose, so that’s what we’ll use.

Mine ended up looking like this:

version: "3"
 
# https://github.com/pi-hole/docker-pi-hole/blob/master/README.md
 
services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    # For DHCP it is recommended to remove these ports and instead add: network_mode: "host"
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "67:67/udp"
      - "80:80/tcp"
      - "443:443/tcp"
    env_file:
      - WEBPASSWORD.env
    environment:
      TZ: 'Europe/Helsinki'
      # Elisa / Saunalahti DNS addresses
      DNS1: '193.229.0.40'
      DNS2: '193.229.0.42'
      ServerIP: '192.168.1.242'
      # Name "raspi" used on local network
      VIRTUAL_HOST: 'raspi'
    # Volumes store your data between container upgrades
    volumes:
      - './etc-pihole/:/etc/pihole/'
      - './etc-dnsmasq.d/:/etc/dnsmasq.d/'
      # run `touch ./var-log/pihole.log` first unless you like errors
      # - './var-log/pihole.log:/var/log/pihole.log'
    dns:
      - 127.0.0.1
      - 1.1.1.1
    # Recommended but not required (DHCP needs NET_ADMIN)
    #   https://github.com/pi-hole/docker-pi-hole#note-on-capabilities
    cap_add:
      - NET_ADMIN
    restart: unless-stopped

Basically my edits to the original were:

  • Elisa (my internet provider) DNS addresses (DNS1 & DNS2) and
  • for accessing the actual service, the local name raspi and it’s ip-address (VIRTUAL_HOST & ServerIP)
  • WEBPASSWORD-env on a separate file, so the config can be pushed to gitlab (contents of the file are just WEBPASSWORD=password)

After configuration just run

docker-compose up -d

to get your new service up and running.

Simple stuff!

About config modifications

When you first run the docker-compose up docker creates local volumes and puts all the configuration inside. In order for you to alter these settings, put the service down with docker-compose down, clean the paths and start anew with docker-compose up.

Automations

The pi-hole docker -repo gives ready made recipes for automatically updating the ad sources once a week and flushing the logs:

# Pi-hole: Update the ad sources once a week on Sunday at 01:59
#          Download any updates from the adlists
59 1    * * 7   root    PATH="$PATH:/usr/local/bin/" docker exec $DOCKER_NAME pihole updateGravity > /dev/null
 
# Pi-hole: Flush the log daily at 00:00 so it doesn't get out of control
#          Stats will be viewable in the Web interface thanks to the cron job above
00 00   * * *   root    PATH="$PATH:/usr/local/bin/" docker exec $DOCKER_NAME pihole flush > /dev/null

and as in the dockerfile it’s defined --restart=unless-stopped the container should get up on boot and/or crashes. Magics!

Putting your new DNS server into use

By itself the pi-hole does absolutely nothing other than displays a nice UI.

You might want to start by setting one workstation/appliance to use your raspberry as your DNS server and watch the blocked sites number going up. Maybe even visit your most used sites to check if the pi-hole has any negative effects on them. Personally I’ve had to whitelist only crunchyroll which didn’t work with pi-hole, otherwise the effects have been only positive.

When you are ready, go to your router’s settings and define your pi-hole as your primary DNS server for network-wide ad-blocking. If you don’t want your whole network to go down if the pi-hole does, put some secondary DNS-server as a backup, such as 1.1.1.1 or your internet service provider’s one.

Setup-wise you are now done, your life should be (more) ad-free than earlier.

Updating the pi-hole

Every now and then there is a notification on the UI of components of pi-hole that need to be updated, and we should as it’s our NDS and we want it to behave nicely. As the setup is on a container, the update consists of replacing the image with a newer one.

  1. Download the latest version of the image
docker pull pihole/pihole
  1. Throw away your container
docker rm -f pihole 
  1. Get your service back running
docker-compose up -d

Bonus Fiddling

RPi USB Boot

RPis from B onwards support USB-boot meaning that you can ditch the most fragile part of your raspberry-setup, the SDHC card, and run the setup with some leftover SSD/HDD. I just happened to have one (actually 4) SSD lying around and I got a cheap USB-SATA-adapter off Amazon, which should have worked, but of course didn’t. It did work when mounting on up-and-running raspberry but didn’t work at all on boot.

What did work though was some WW2-era Lacie USB2-HDD, which I proceeded to gut and replace the old HDD with a bit newer SSD

Gutted Lacie .. and it worked. And I was really surprised.

For the actual how-to on enabling the USB-boot just read the official documentation, it’s really good.

Windows 10 update notifier

Wouldn’t it be really cool to know when your pi-hole would be ready for updates? With Windows 10 notifications & WSL?

Well, it totally felt good at the time.

  1. Install BurntToast on the Windows 10 to have a tool to actually do notifications that appear in the notification center.

  2. Write a nice script for sending messages from WSL:

#!/bin/bash
MSG=$1
 
powershell.exe "New-BurntToastNotification -Text \"$1\""

maybe call it send-message.sh

  1. Write another nice script to do the whole “is there a new script yet”-dance
#!/bin/sh
 
if ssh raspi pi-hole/update-image.sh| grep 'Image is up to date'; then
        ALSA=1
else
        echo 'New image found'
        echo '-> notify'
        ./send-message.sh "New pi-hole image downloaded!"
fi

where raspi is the name of my pi-hole raspberry

  1. Schedule a task in Task Scheduler to run your check periodically, with path to your wsl.exe on the program and the path of your checker-script as the argument Gutted Lacie

Note: do yourself a favor and create a new folder for your Custom Jobs so you can find them afterwards.

Things getting out of hand

At this point I decided that it would be really good if I could update my pi-hole automatically and get notifications of those actions on Telegram, instead of Windows, and it would be really beneficial to know if the pi-hole goes down at some point.

So I needed an infrastructure monitor on my LAN. And that’s the beef of my next blog post.