Secure DNS-based ad filtering with Pihole and Stubby via Docker

I’ve been running a Pihole DNS server internally for a while. It’s great! It’s like adblock, except for your whole network - all your devices, TVs, phones, tablets, and computers - get ad filtering.

Recently I wanted to see if I could get my Pihole to play nice with Stubby. With the imminent advent of DNS-over-HTTPS and the subsequent ISP freakout over the loss of their sweet, sweet data mining, I figured now was as good a time as any to add an extra layer to my network.

I use and love Docker. I’ve worked up this simple Bash script which will wire up Stubby (pointed to Cloudflare’s DNS-over-TLS servers), and then point the Pihole to Stubby. This means that the Pihole does all its name resolution away from the prying eyes of my ISP (yes, it gives it to Cloudflare, though Cloudflare has a better stance on privacy than most ISPs), and I get DNS filtered. Best of both worlds!

Here’s the script. I run it on an Ubuntu 16.04 server, but you should be able to run it on anything that can run Docker.

#!/bin/bash
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 123.123.123.123 password"
    echo "Specify your host's LAN IP and the desired Pihole password"
    exit 1
fi

IP=$1
PASSWORD=$2

docker run -d -it --restart always --name stubby -p 8053:8053/udp mvance/stubby:latest
# We need a network for the pihole to be able to talk to stubby
docker network create --subnet 172.18.0.0/16 dns
# Attach stubby to the network, specifying the IP it should use
docker network connect dns stubby --ip 172.18.0.3

docker run -d \
    --name pihole \
    -p $IP:53:53/tcp -p $IP:53:53/udp \
    -p 67:67/udp \
    -p 80:80 \
    -p 443:443 \
    -v pihole:/etc/pihole/ \
    -v dnsmasq.d:/etc/dnsmasq.d/ \
    -e ServerIP="${IP}" \
    -e DNSMASQ_LISTENING=${IP} \
    -e WEBPASSWORD="${PASSWORD}" \
    --restart=unless-stopped \
    --cap-add=NET_ADMIN \
    --dns=172.18.0.3 \
    pihole/pihole:latest

echo -n "Your password for https://${IP}/admin/ is "
docker logs pihole 2> /dev/null | grep 'password:'
# Attach pihole to the network, specifying the IP it should use
docker network connect dns pihole --ip 172.18.0.2

You can go to http://your-lan-ip/admin to log into the Pihole dashboard (and you should, it’s neat), or just resolve a request against it. Try this:

export LAN_IP=123.123.123.123 # replace 123.123.123.123 with your server's LAN IP

dig @$LAN_IP google.com                   # normal request - should succeed
dig @$LAN_IP analytics.google.com         # pihole-filtered request - should fail
dig @$LAN_IP sigok.verteiltesysteme.net   # dnssec test - should succeed
dig @$LAN_IP sigfail.verteiltesysteme.net # dnssec test - should fail

You could do this with a docker-compose file pretty easily, too, but I appreciate the straightforwardness of Bash scripts. :)

And that’s it! Just update your DHCP server (probably your router) to hand out your LAN IP as the DNS server for devices on your network, and DNS on your network is secured and you’ll notice a lot of ads, trackers, and other annoyances no longer exist.