I've migrated my Pi-hole from a bare metal RPi to a small x86 server running multiple docker services. As many want to expose a web interface, I decided to put Traefik, a reverse proxy, in front of them to be able to access them via https://github.com/zyedidia/micro`service1.lan`, service2.lan
, ....
For the Pi-hole container this was a bit of a challenge, as I had two requirements
- The web interface is only accessible via the traefik route
- I want to see individual clients on my Pi-hole dashboard
I tried all the docker network modes, but none met my requirements (for general advantages & disadvantages see also here):
- bridge networking mode: no individual clients on the dashboard
- host networking mode: web interface wouldn't be behind traefik and conflict with port
80
. If the web port is different than80
, access is possible only viapi.hole:PORT/admin
which looks ugly - macvlan network: the web interface is not only accessible via traefik, but also
http://macvlanIP/admin
and requires a more complex setup
What I would need would be a dockerized DNS server at the edge of my network stack which would forward the queries to Pi-hole but preserves client information.
Luckily I know such a DNS server - dnsmasq
This guide does not deal with Pi-hole as DHCP server, but should be easily adjustable using the link provided below.
The whole idea was inspired by
DHCP with docker-compose and bridge networking where DerFetzer setup a DHCP relay.
Instead of creating a new dnsmasq
image I used the slim, alpine based version from GitHub - brav0charlie/docker-dnsmasq: A simple dnsmasq container based on Alpine Linux Edge
dnsmasq:
container_name: dnsmasq
image: ghcr.io/brav0charlie/docker-dnsmasq
restart: unless-stopped
network_mode: 'host'
volumes:
- '/etc/localtime:/etc/localtime:ro'
- './dnsmasq:/etc/dnsmasq.d'
dnsmasq
runs in host
network mode and listens for incoming requests on port 53
. The magic is with the to configuration file in ./dnsmasq/dns.conf
interface=eno1
no-resolv
cache-size=10000
server=172.31.0.100
log-facility=/dev/stdout
log-async
add-subnet=32
add-mac
Substitute interface=eno1
with the appropriate setting for your host, log-facility=/dev/stdout
will log to the console so it can be seen on the host via docker logs
. server=
needs to be set to the Pi-hole container's IP (see below). And here is the magic: add-subnet=32
and add-mac
will add the IPv4 and the mac of the original client into the DNS query when it's forwarded upstream to the Pi-hole! Pi-hole can use this information to conclude which client was sending the query originally.
The docker-compose.yml
for the Pi-hole container is mostly a vanilla one, excetpt that container is part of two networks, one is the traefik backend and one is a dns backend (I use a unbound container as upstream).
depends_on:
- dnsmasq
networks:
dns_backend:
ipv4_address: 172.31.0.100
traefik:
The networks are defined at the end of the compose file
networks:
traefik:
external: true
dns_backend:
ipam:
config:
- subnet: 172.31.0.0/16
name: dns_backend
The whole compose file looks like this (including unbound
)
version: "3"
# More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/
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" # Only required if you are using Pi-hole as your DHCP server
#- "81:80/tcp"
environment:
TZ: 'EUROPE/BERLIN'
WEBPASSWORD: 'SECRETPASSWORD'
FTLCONF_LOCAL_IPV4: '10.0.1.5'
PIHOLE_DNS_: 'unbound'
# Volumes store your data between container upgrades
volumes:
- './pihole/etc-pihole:/etc/pihole'
- './pihole/etc-dnsmasq.d:/etc/dnsmasq.d'
restart: unless-stopped
depends_on:
- dnsmasq
networks:
dns_backend:
ipv4_address: 172.31.0.100
traefik:
labels:
- traefik.enable=true
- traefik.http.routers.pihole.rule=Host(`pi.hole`)
- traefik.http.services.pihole.loadbalancer.server.port=80
- traefik.http.routers.pihole.service=pihole
- traefik.docker.network=traefik
hostname: 'pihole'
unbound:
image: klutchell/unbound
container_name: unbound
restart: unless-stopped
volumes:
- './unbound/:/etc/unbound/custom.conf.d'
- '/etc/localtime:/etc/localtime:ro'
networks:
- dns_backend
cap_add:
- NET_ADMIN
healthcheck:
test: ["CMD", "dig", "-p", "53", "dnssec.works", "@127.0.0.1"]
interval: 30s
timeout: 30s
retries: 3
dnsmasq:
container_name: dnsmasq
image: ghcr.io/brav0charlie/docker-dnsmasq
restart: unless-stopped
network_mode: 'host'
volumes:
- '/etc/localtime:/etc/localtime:ro'
- './dnsmasq:/etc/dnsmasq.d'
networks:
traefik:
external: true
dns_backend:
ipam:
config:
- subnet: 172.31.0.0/16
name: dns_backend
One thing you should do to increase your privacy with this setup is to add is a new dnsmasq
config file (e.g 55-strip.conf
) within ./pihole/etc-dnsmasq.d
to remove subnet/IP and MAC information before Pi-hole forwards queries upstream
strip-mac
strip-subnet