Over the last month or two, we had been noticing random difficulties with our home network - mostly, it manifested itself as a “pause”. Depending on the device, it could seem like the internet was down for those few moments. On my personal laptop, I would often have either a long wait while the browser searched out the page (several seconds), or more recently, the browser would show a “site not found” error page. I would eventually be able to get the page to load after 10 to 20 seconds, but it was obvious (and frustrating) that the problem was getting worse. I spent a while suspecting the WiFi router was dying, until a couple days ago - I ssh’d to my PiHole, which has been running on a Raspberry Pi 3 for a year or two now, and logging in took a long time, as did navigating around the OS. My suspicion now was that the Pi itself (well, the SD card most likely) was having problems, manifesting as internet interruptions.

I threw together the Pi-hole on a whim a while back, to see if it was something that would work for us here at home. It’s serving both DNS and DHCP, and I never took the time to go back and make sure it was set up properly (like moving the OS to a USB drive for longevity) - it was just running happy-as-can-be on the SD card. Looking back, really, I’m just lucky the no-name brand SD card hadn’t already failed on me!

So, today was as good of a day as ever to migrate my Pi-hole installation to something a little more resiliant. I decided to set up Docker on a spare server and run Pi-hole from there.

Setting up the server

I installed the latest version of Debian (Bullseye) from a USB drive - that worked flawlessly and went quickly. The only trouble I ran into was that I didn’t de-select the GNOME desktop environment, so about 20 minutes after getting the server up and running, it went to sleep… while I was still ssh’d to the server. Turns out, suspension and hybernation are enabled when you have a desktop environment installed. Disabling suspend and uninstalling the desktop environment fixed that right up.

Installing Docker was simple - Docker has some excellent documentation to follow.

Setting up Pi-hole

Configuring the Pi-hole docker container took a little more time. Pi-hole provides instructions to follow, but it turns out there’s a caveat when you want to use DHCP and Docker - because of the way Docker has a separate network, you have to do some work to get the container exposed correctly to the network.

I chose to set up a Macvlan network in Docker - this allows the container to get an IP on the network, like it’s a totally separate device, and still allows me to use Docker for other containers as well. I was able to follow Tony Lawrence’s instructions for setting up a docker compose file, combined with Docker’s instructions, to create my own that seems to work well (and correctly persists settings between container rebuilds!).

On the old Pi-hole install, I created a backup by going to Settings -> Teleporter -> Backup. Then, after building the container and logging in to the new Pi-hole’s WebUI, I restored that backup on the same tab. All of my settings were loaded up without issue - most importantly, all of my DHCP reservations. I turned the DHCP server off on the old Pi-Hole, and made the new Pi-hole was turned on.

I renewed the DHCP leases manually on a couple devices (ipconfig /renew on my Windows laptop, and toggled the WiFi switch on my iPhone), and they immediately popped up with a new lease, under their correct reserved IP.

Everything seems to be working smoothly, and I expect all of my devices will show up in the new Pi-hole over the next 24 hours, as their old DHCP leases expire.

Docker compose yaml

This is the compose yaml I’m currently using:

version: "2"

networks:
  pihole_network:
    driver: macvlan
    driver_opts:
      parent: eno1		# Set this to your server's network interface
    ipam:
      config:
        - subnet: 192.168.0.0/24 
          gateway: 192.168.0.1
          ip_range: 192.168.0.192/28

services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    hostname: pihole
    cap_add:
      - NET_ADMIN   # This is required for version 4.1 onwards
    networks:
      - pihole_network
    dns:
      - 75.75.75.75 
      - 75.75.76.76
    ports:
      - 433/tcp
      - 53/tcp
      - 53/udp
      - 67/udp
      - 80/tcp
    environment:
      ServerIP: 192.168.1.66
      WEBPASSWORD: "xxxxxxx"
      TX: 'America/Denver'
      PIHOLE_DNS_: '75.75.75.75;75.75.76.76'   # My external ISP DNS servers
      DHCP_ACTIVE: "true"
      DHCP_START: '192.168.0.2'
      DHCP_END: '192.168.0.251'
      DHCP_ROUTER: '192.168.0.1'
    volumes:
      - './etc-pihole:/etc/pihole'
      - './etc-dnsmasq.d:/etc/dnsmasq.d'
    restart: unless-stopped

Update - 11 Dec 2023

This has been up and running for a week, and I discovered an issue today - I could no longer ssh to the host server, and the Portainer page wouldn’t load, but Pi-Hole was up and running without issue! I hooked up a monitor and keyboard (it’s a headless server) and started digging in.

Running ip a, I quickly found that my network interface didn’t have an IP address assigned any longer (there was no inet line).

So I grabbed the connection information from nmcli:

$ nmcli connection show
NAME                UUID                                  TYPE      DEVICE
Wired connection 1  2749e7d8-e9a7-43a7-bbdb-f50ec24a38c2  ethernet  eno1
docker0             9f35765f-86c1-404d-aef1-479f226403a4  bridge    docker0
lo                  4abcf5f4-5797-468f-8c1e-dd6025d144ed  loopback  lo

Then I set the IP address, set it to static (manual), and then set the gateway address:

$ nmcli connection modify "Wired connection 1" ipv4.address 192.168.1.66/24
$ nmcli connection modify "Wired connection 1" ipv4.method manual
$ nmcli connection modify "Wired connection 1" ipv4.gateway "192.168.1.1"

And then I set the DNS servers:

$ nmcli connection modify "Wired connection 1" ipv4.dns "75.75.75.75 75.75.76.76"

And lastly restarted NetworkManager:

$ systemctl restart NetworkManager

You’ll notice that I used my ISP’s DNS servers, rather than the Pi-Hole I spent all this time setting up - for reasons I don’t understand yet, I am unable to reach the IP address of the Pi-Hole container (or any container running on this host). I’m not the first person to run into an issue like this, but it’s going to take some more research to figure out how to correct my networking assignment in Docker.