Introduction

I’ll assume that you just have installed and configured hostapd, if not, have a look at my previous post Configure Linux as an access point with hostapd, and tunnel traffic to (NordVPN) OpenVPN server. Now I’ll only show you how to add the power of Pi-hole to protect your devices from unwanted content, without installing any client-side software.

Install Docker

To run Pi-hole we need a Docker service to be up and running, if you’re a Gentoo user like me, you can follow the official wiki for Docker, otherwise you can find tons of documentation to install and configure Docker for your favorite distro.

Enable Docker service at boot

We’ll start the docker service just some seconds after the hostapd and dnsmasq services:

# cd /etc/systemd/system
# mkdir docker.service.d
# touch docker.service.d/00hostapd.conf

put this content in /etc/systemd/system/docker.service.d/00hostapd.conf file:

# cat /etc/systemd/system/docker.service.d/00hostapd.conf 
[Unit]
After=network-online.target docker.socket firewalld.service dnsmasq-wifi.service dnsmasq-wifiguests.service

create the systemd timer /etc/systemd/system/docker.timer with the following content:

# cat /etc/systemd/system/docker.timer 
[Unit]
Description=Run docker 3.5 minutes after the boot

[Timer]
OnBootSec=210seconds

[Install]
WantedBy=timers.target

enable the timer and start the service:

# systemctl enable docker.timer
# systemctl start docker.service

Pull Pi-hole docker image

This guide is based on the latest, at the time of this writing, Pi-hole docker image, which is v5.0, but if you want to use another docker image simply check availability by curl:

curl -s https://registry.hub.docker.com/v1/repositories/pihole/pihole/tags | python -m json.tool | egrep '\"name\": \"v[0-9]'
        "name": "v4.0_aarch64"
        "name": "v4.0_amd64"
        "name": "v4.0_armhf"
        "name": "v4.2_rc1"
        "name": "v4.2_rc1_aarch64"
        "name": "v4.2_rc1_amd64"
        "name": "v4.2_rc1_armhf"
        "name": "v4.2_rc2"
        "name": "v4.2_rc2_aarch64"
        "name": "v4.2_rc2_amd64"
        "name": "v4.2_rc2_armhf"
        "name": "v4.4"
        "name": "v4.4-amd64"
        "name": "v4.4-arm64"
        "name": "v4.4-armel"
        "name": "v4.4-armhf"
        "name": "v5.0"
        "name": "v5.0-amd64"
        "name": "v5.0-arm64"
        "name": "v5.0-armhf"

pull your desired version or the latest:

$ docker pull pihole/pihole:v5.0
$ docker pull pihole/pihole:latest

Create a user docker bridge network

In order to start the container with a pre-defined IP and network, you’ll have to create a new docker network:

$ docker network create --subnet 172.20.0.0/16 piholenet

docker may have created a new bridge network with the given subnet 172.20.0.0/16, check if is present and note it’s bridge name which will be used later on:

$ ip a
...
6: br-d14f54fa58e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:61:43:0d:e1 brd ff:ff:ff:ff:ff:ff
    inet 172.20.0.1/16 brd 172.20.255.255 scope global br-d14f54fa58e0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:61ff:fe43:de1/64 scope link
       valid_lft forever preferred_lft forever
...

in my case the bridge interface name is br-d14f54fa58e0

Optional - Integrate your wifiguests routing table with Docker network

For whom of you as followed my previous post configurations for hostapd Configure Linux as an access point with hostapd, and tunnel traffic to (NordVPN) OpenVPN server you’ll need to add this new bridge interface to your wifiguests routing table:

# ip route add 172.20.0.0/16 dev br-d14f54fa58e0 table wifiguests

replace br-d14f54fa58e0 with your bridge interface name.

Create Docker container

Now we can create a new Pi-hole container pihole-v5.0 with a fixed IP 172.20.0.10 which will be used in dnsmasq dhcp option, and in iptables filter

$ docker run -d --name pihole-v5.0 --network piholenet --ip 172.20.0.10 -p 172.20.0.1:53:53/tcp -p 172.20.0.1:53:53/udp -p 172.20.0.1:80:80 -p 172.20.0.1:443:443 -e TZ="Europe/Rome" -v "${HOME}/pihole/etc-pihole/:/etc/pihole/" -v "${HOME}/pihole/etc-dnsmasq.d/:/etc/dnsmasq.d/" --dns=127.0.0.1 --dns=1.1.1.1 --restart=unless-stopped --hostname pi.hole -e VIRTUAL_HOST="pi.hole" -e PROXY_LOCATION="pi.hole" -e ServerIP="127.0.0.1" pihole/pihole:v5.0

or in a multiline command:

$ docker run -d --name pihole-v5.0 \
    --network piholenet --ip 172.20.0.10 \
    -p 172.20.0.1:53:53/tcp -p 172.20.0.1:53:53/udp \
    -p 172.20.0.1:80:80 -p 172.20.0.1:443:443 \
    -e TZ="Europe/Rome" \
    -v "${HOME}/pihole/etc-pihole/:/etc/pihole/" \
    -v "${HOME}/pihole/etc-dnsmasq.d/:/etc/dnsmasq.d/" \
    --dns=127.0.0.1 --dns=1.1.1.1 --restart=unless-stopped \
    --hostname pi.hole -e VIRTUAL_HOST="pi.hole" \
    -e PROXY_LOCATION="pi.hole" -e ServerIP="127.0.0.1" pihole/pihole:v5.0

after the first docker run keep note of the admin Pi-hole password:

$ docker logs pihole-v5.0 | grep password
+ pihole -a -p sM2a39K8 sM2a39K8
Assigning random password: sM2a39K8
Setting password: sM2a39K8
  [✓] New password set

in the above example the login password is sM2a39K8

Configure dnsmasq dhcp server

With Pi-hole running, and routing table able to reach it from wifiguests, you should pass, via dhcp option 6, a new DNS server 172.20.0.10 (Pi-hole container):

# cat /etc/dnsmasq-wifi.conf 
strict-order
interface=wlp4s0f3u2
except-interface=lo
listen-address=192.168.79.1
bind-dynamic
dhcp-range=192.168.79.50,192.168.79.150,255.255.255.0,12h
dhcp-leasefile=/var/lib/misc/dnsmasq-wlp4s0f3u2.leases
dhcp-option=6,172.20.0.10
log-dhcp
#
# systemctl restart dnsmasq-wifi.service

also for guests:

# cat /etc/dnsmasq-wifiguests.conf 
strict-order
interface=wlp2s0
except-interface=lo
listen-address=192.168.69.1
bind-dynamic
dhcp-range=192.168.69.50,192.168.69.150,255.255.255.0,12h
dhcp-leasefile=/var/lib/misc/dnsmasq-wlp2s0.leases
dhcp-option=6,172.20.0.10
log-dhcp
#
# systemctl restart dnsmasq-wifiguests.service

Filter all external DNS servers

If your client can reach your Pi-hole container at 172.168.20.10, you can disable all connections to other DNS servers using previously created iptables chain wifi-filter-clients:

# iptables -A wifi-filter-clients ! -d 172.20.0.10/32 -p tcp -m multiport --dports 53 -j REJECT
# iptables -A wifi-filter-clients ! -d 172.20.0.10/32 -p udp -m multiport --dports 53 -j REJECT
# iptables-save > /var/lib/iptables/rules-save

and also for your guests with the iptables chain wifi-filter-guests:

# iptables -A wifi-filter-guests ! -d 172.20.0.10/32 -p tcp -m multiport --dports 53 -j REJECT
# iptables -A wifi-filter-guests ! -d 172.20.0.10/32 -p udp -m multiport --dports 53 -j REJECT
#
# iptables -A wifi-filter-guests -d 172.20.0.10/32 -p tcp -m multiport --dports 80,443 -j REJECT
# iptables-save > /var/lib/iptables/rules-save

Modify systemd update-nordvpn.service service

You should add your new bridge interface into the update-nordvpn.service, in order to route traffic from guests to the docker container, simply add a new “ExecStart=-/bin/ip route add 172.20.0.0/16 dev br-d14f54fa58e0 table wifiguests”:

# cat update-nordvpn.service 
[Unit]
Description=Update nordvpn DB and configure with the desired country

[Service]
Type=oneshot
#ExecStart=/usr/sbin/update-nordvpn.sh "United States"
ExecStart=-/bin/ip route add 192.168.79.0/24 dev wlp4s0f3u2 table wifiguests
ExecStart=-/bin/ip route add 192.168.69.0/24 dev wlp2s0 table wifiguests
ExecStart=-/bin/ip route add 172.20.0.0/16 dev br-d14f54fa58e0 table wifiguests
ExecStart=/usr/sbin/update-nordvpn.sh Italy
#
# systemctl daemon-reload

Conclusion

Now you can configure your Pi-hole blackhole for your entire network, with ensuring your clients to use it. Remember to disable Firefox DNS Over Https (DOH) if you want to filter ads on it.