Pihole Docker Swarm Configuration with Nginx Proxy Manager
As you guys know, I have been a huge proponent of Docker Swarm. I think it is still a very viable option as a middle ground between standalone Docker hosts and Kubernetes when you need more availability than a single host can provide but you don’t want the complexity of Kubernetes. One extremely popular solution to run in the home lab is Pi-Hole. Pihole is a network-wide ad-blocker that also does DNS resolution. While it is fairly easy to get up and running on a standalone Docker host, there are a few nuances to be aware of with Docker Swarm. Let’s look at Pihole Docker Swarm configuration with Nginx Proxy Manager.
Table of contents
Brief overview of Pihole
Pi-hole is a network wide ad-blocker. It uses DNS sinkholing technology to blacklist traffic to known telemetry, ad related, and even malware domains. When a client requests a resource from a domain on a blocklist, it simply “sinkholes” the request and it isn’t returned.
This not only is a great security solution to add, it also saves on network bandwidth since your clients aren’t having to pull down resources that you don’t knowingly request, like ads.
Brief overview of Docker Swarm
Docker Swarm is Docker on steroids. In other words, think of it like adding multiple docker hosts together to host your containers as a single entity rather than multiple standalone hosts. Once you add them to the Docker Swarm cluster, they are managed as a cluster and your containers are managed as services.
You need to have shared storage to pull this off. But using the open source solution Ceph, you can easily do this. Read my tutorial here on how to use Microceph to spin up a shared storage environment for docker swarm: Try Microceph for an Easy Ceph Install.
For a video walkthrough, take a look at my complete walkthrough on the best Docker setup using Docker Swarm here:
Pihole Docker Swarm configuration
Take a look at the Pihole Docker swarm configuration below. In this config, we tell Docker Swarm how we want to publish the ports for DNS and DHCP on the “host” interface. Usually you will have issues when trying to use a locally-scoped network in Swarm. It squawks and wants you to use a network that is Swarm compliant like the overlay network type.
Host networking is an exception to Swarm’s restriction on local-scoped networks. Docker Swarm allows network_mode: host
because it bypasses Docker’s networking stack entirely and uses the host’s networking interface connection directly.
Considerations: When you use host networking, you run the chance of having ports conflict with one another. So, with publishing the ports that we are going to do for Pihole this way, make sure you have port 53 and 67 open on the host and you don’t already have another solution listening on these.
Why are we doing this? Well, it has to do with how Pihole behaves when you just place it on the normal overlay network that Docker Swarm creates. This causes Pihole to only show only the service ip on the connected clients list. This in turn leads to problems in Pihole. For instance, if you have exceptions for specific IPs on your network, these will no longer work as the clients won’t be appearing to Pi-hole as the IP they are on the network.
We are using the long-form method of publishing the ports. When we publish ports using the long format port mapping, we can set DNS ports 53 and DHCP port 67 bound directly to the host network. Again, this bypasses the ingress/overlay network.
Below is the Docker Compose code for the Pihole service running in Docker Swarm. I have pasted in my configuration and bolded the items you likely want to change, including the volume bind mounts.
Note: The cool thing with the below is that we are actually using a mix of networking connections. Publishing the ports on host networking for ports 53 and 67 uses the host connection, but then as you notice we are still including the network at the bottom of the code for our Nginx Proxy manager network. This means that the other ports (web admin) will be available on this network. This makes for the perfect combination of having things work for Pihole DNS and showing clients correctly and still having the ability to terminal SSL using Nginx Proxy Manager.
version: '3.8'
services:
pihole:
image: pihole/pihole:latest
ports:
- target: 53
published: 53
protocol: tcp
mode: host
- target: 53
published: 53
protocol: udp
mode: host
- target: 67
published: 67
protocol: udp
mode: host
environment:
TZ: 'America/Chicago'
WEBPASSWORD: 'password'
PIHOLE_DNS_: 1.1.1.1;9.9.9.9
DNSSEC: 'false'
WEBTHEME: default-dark
PIHOLE_DOMAIN: lan
DNSMASQ_LISTENING: all
volumes:
- /mnt/cephfs/pihole/pihole:/etc/pihole
- /mnt/cephfs/pihole/dnsmasq.d:/etc/dnsmasq.d
networks:
- npm-stack_nginxproxy
deploy:
replicas: 1
restart_policy:
condition: any
delay: 5s
max_attempts: 3
networks:
npm-stack_nginxproxy:
external: true
Save this as docker-compose.yml
. Once you have the file saved, then run:
docker-compose up -d
Verifying the Pihole container and troubleshooting if needed
Make sure the container is up and running, which you can see with the output of:
docker ps
If you don’t see it running, see if it is stopped:
docker ps -a
If it is not running and exited, you can check the logs:
docker logs pihole
Nginx Proxy Manager configuration
Now, we can look at the Nginx Proxy Manager configuration. Nginx Proxy Manager as you may already know is an easy way to have SSL termination, a GUI interface for Nginx config and automated SSL certificate renewal all in one.
The installation and configuration of Nginx Proxy manager is outside the scope of this blog. However, I have written about this in detail here:
Once you have Nginx Proxy Manager configured, you can simply setup a new proxy host for your Pi-hole instance like the following:
- Set your domain name (needs to match your public domain if you are doing proper Let’s Encrypt certs)
- Set the scheme to http
- Hostname or IP set to pihole
- Forward port set to 80
- I normally flag on block common exploits and websockets support
Viewing clients in Pihole
Now that you have the pihole container running and have Nginx Proxy Manager configuration set, as long as you have your DNS configured correctly, you should be able to browse out to the DNS name and not see a certificate error (if using Let’s Encrypt).
Now we see clients that are on my home lab networks, and not the internal services IP of the service in Docker Swarm. The subnets 192.168.1.0/24 and 10.1.149.0/24 are subnets outside of the cluster which is what I want to see.
Wrapping up
Running Pihole in Docker Swarm is not too difficult. It does require the little tweak of publishing ports using the host network instead of simply allowing these to come up on the overlay network that is internal to Docker Swarm. Pihole will work without doing this in Docker Swarm, but you will lose all visibility to which client is doing what and your whitelists based on clients won’t work. Let me know in the comments if you are running this config in your home lab.