I recently decided to set up PiHole in a Podman container for network-level blocking of DNS requests to some of the nasty stuff on the internet. For a long time, I’ve never bothered with such a thing, but these days I’m sharing an internet connection with my family, who use the home internet connection from a whole range of devices.
To manage Linux containers on my humble home server, I prefer Podman instead of Docker because it’s daemon-less and doesn’t need to be run as root. It’s great! Most of the time I can run a container from Docker Hub with the EXACT same instructions on Podman, and it just works.
That said, the PiHole Docker container took a few little tweaks to get working properly with Podman on Fedora Linux. In this post I’ll explain how I got it working.
There is an example docker run
command in the documentation on GitHub.
https://github.com/pi-hole/docker-pi-hole/blob/master/docker_run.sh
I initially tried taking this command and replacing docker
with podman
and ran into four issues.
- Rootless Podman container wouldn’t run PiHole without network capability
- Rootless Podman container couldn’t use privileged network ports
- When PiHole runs, it was logging all request as coming from the container host, instead the IP address of devices on the network
- PiHole software was not recognizing a network interface
To solve the first problem I simply added --cap-add=NET_ADMIN
as an option to the podman run
command. Podman will run containers as unprivileged by default, so we need to use this option to add the NET_ADMIN
capability to the container. This allows the container to start, so we’re getting somewhere.
The second issue was that the unprivileged Podman container could not map to privileged network ports on the host system ( port numbers <= 1024 ). I worked around that by mapping the containers DNS port 53
to an unprivileged host port 1053
. This is achieved by easily adding these options to the podman run
command:
...
-p 1053:53/tcp \
-p 1053:53/udp \
...
Then, using some simple firewalld
rules, forward the port 53/tcp
and 53/udp
to 1053/tcp
and 1053/udp
on the host.
sudo firewall-cmd --zone=public --add-forward-port=port=53:proto=tcp:toport=1053:toaddr= --permanent
sudo firewall-cmd --zone=public --add-forward-port=port=53:proto=udp:toport=1053:toaddr= --permanent
The next problem was a doozy. PiHole was running, but all of the requests that hit it were logged as coming from localhost instead of other IP addresses on the network. I did some searching and found these issues on GitHub:
https://github.com/containers/podman/issues/9089
https://github.com/containers/podman/issues/8193
In short, the new default port handler of Podman ( rootlessport
) has an issue of seeing only “127.0.0.1” as the remote address for requests reaching rootless containers via published host ports. We can work around this by specifying an alternate port handler with another podman run
command option
...
--net=slirp4netns:port_handler=slirp4netns` \
...
Using slirp4netns
introduced another issue. Pi-Hole itself couldn’t detect a network interface to use. Pi-Hole gave me an error about the eth0
interface not existing. When reading some more about slirp4net
I noticed I saw this:
slirp4netns allows connecting a network namespace to the Internet in a completely unprivileged way, by connecting a TAP device in a network namespace to the usermode TCP/IP stack (“slirp”).
So slirp4netns
uses a TAP
interface? Well it turns out that there’s an environment variable that we can pass to the PiHole container to tell the PiHole the name of its network interface.
...
-e INTERFACE="tap0" \
...
So with those small changes to the podman run
command, we get something that looks like this:
podman run -d \
--cap-add=NET_ADMIN \
--net=slirp4netns:port_handler=slirp4netns \
--name pihole \
-p 1053:53/tcp \
-p 1053:53/udp \
-p 8080:80 \
-e TZ="Australia/Sydney" \
-v "./etc-pihole:/etc/pihole" \
-v "./etc-dnsmasq.d:/etc/dnsmasq.d" \
--dns=8.8.8.8 \
--dns=8.8.4.4 \
--restart=unless-stopped \
--hostname pi.hole \
-e VIRTUAL_HOST="pi.hole" \
-e PROXY_LOCATION="pi.hole" \
-e ServerIP="192.168.1.137" \
-e INTERFACE="tap0" \
--security-opt label=disable \
pihole/pihole:latest
If all goes well, we change our default DNS server in our home router and we’re all set
I hope this helps someone out!