PiHole, Docker Compose v2, Ubuntu, DNS resolution and Bridge Networks

In the last week, I updated a server with Docker Compose on it and immediately, name resolution started to behave weirdly.

For a moment, I forgot that old saying: "If you cannot find the problem reported online, then the problem only exists with you". After lots of reading, I discovered something that might not be new, but certainly did not affect me before.

According to Docker documentation:

"Containers on the default bridge network can only access each other by IP addresses, unless you use the --link option, which is considered legacy. On a user-defined bridge network, containers can resolve each other by name or alias."

I have no idea when this came about, but I have never had to make a custom network before, so this post is not so much about my problem but how this kept me up at night and maybe it can help some other casual user, such as myself.

I think many people who want a simple install in a small environment simply copy a basic pi-hole docker compose configuration and then go about their lives, so those people (like me) will have this problem, soon, if not already.

My config looked like this for some years:

version: "3.8"
services:
  nginx:
    container_name: nginx
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "${ROOT}/nginx/config:/etc/nginx/conf.d:ro"
      - "${ROOT}/nginx/logs:/var/log/nginx"
      - "${ROOT}/nginx/html:/usr/share/nginx/html"
      - "/opt/sslupdate/certificates:/certificates"
    restart: unless-stopped
  heimdall:
    image: lscr.io/linuxserver/heimdall:latest
    container_name: heimdall
    environment:
      - "PUID=${PUID}"
      - "PGID=${PGID}"
      - "TZ=${TIMEZONE}"
    volumes:
      - "${ROOT}/heimdall/config/:/config/"
    ports:
      - "8000:80"
      - "8001:443"
    restart: unless-stopped
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    hostname: "home"
    environment:
      - "TZ=${TIMEZONE}"
      - "WEBPASSWORD=${PASSWORD}"
      - "PIHOLE_DNS_=${CLOUDFLARE_DNS1};${CLOUDFLARE_DNS2}"
      - "DNS_BOGUS_PRIV=true"
      - "DNS_FQDN_REQUIRED=true"
      - "FTLCONF_REPLY_ADDR4=172.16.30.4"
      - "PIHOLE_DOMAIN=${DOMAIN}"
      - "WEBTHEME=default_dark"
    volumes:
      - "${ROOT}/pihole/etc/pihole:/etc/pihole"
      - "${ROOT}/pihole/etc/dnsmasq.d:/etc/dnsmasq.d"
      - "${ROOT}/pihole/log:/var/log/pihole"
      - "/opt/sslupdate/certificates:/certificates"
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8002:80"
      - "8003:443"
    restart: unless-stopped

In the above config, the server IP is 172.16.30.4. My env file looks like this:

TIMEZONE="Europe/Berlin"
PASSWORD="YourCrazyPassword"
CLOUDFLARE_DNS1="1.1.1.2"
CLOUDFLARE_DNS2="1.0.0.2"
ROUTER_DNS="172.16.30.1"
SERVER_IP="172.16.30.4"
PUID="1000"
PGID="1000"
ROOT="/opt/docker"
DOMAIN="fritz.box"
CERTIFICATES="/opt/sslupdate/certificates"

Contributing factors are that I use Ubuntu, so I have to do that little trick where the port 53 is used by something else and needs to be freed first.

I discovered and tested many times, that since recently, it is now necessary to specify the IP of the host in the Pihole config, without this, nothing on the LAN gets name resolution.

    ports:
      - ${SERVER_IP}:53:53/udp
      - ${SERVER_IP}:53:53/tcp

So that part means the devices on the LAN can now resolve, but I found containers in the docker-compose stack also could not resolve. To test this, I used docker exec to get a BASH prompt inside Heimdall (because it still has some IP tools in the image and a BASH shell):

 docker exec -it heimdall bash

From inside this container, it is possible to use ping, nslookup and dig to test that resolution is not working.
After reading the above link about bridge networks on the docker page, my config now looks like this:

version: "3"
services:
  nginx:
    container_name: nginx
    image: nginx:latest
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TIMEZONE}
    volumes:
      - ${ROOT}/nginx/config:/etc/nginx/conf.d:ro
      - ${ROOT}/nginx/logs:/var/log/nginx
      - ${ROOT}/nginx/html:/usr/share/nginx/html
      - ${CERTIFICATES}:/certificates
    networks:
      - backend
    ports:
      - 80:80
      - 443:443
    restart: unless-stopped
  heimdall:
    container_name: heimdall
    image: lscr.io/linuxserver/heimdall:latest
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TIMEZONE}
    volumes:
      - ${ROOT}/heimdall/config/:/config/
      - ${CERTIFICATES}:/certificates
    networks:
      - backend
    ports:
      - 8000:80
      - 8001:443
    restart: unless-stopped
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    hostname: home
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TIMEZONE}
      - "WEBPASSWORD=${PASSWORD}"
      - "PIHOLE_DNS_=${CLOUDFLARE_DNS1};${CLOUDFLARE_DNS2}"
      - "DNS_BOGUS_PRIV=true"
      - "DNS_FQDN_REQUIRED=true"
      - "FTLCONF_REPLY_ADDR4=${SERVER_IP}"
      - "PIHOLE_DOMAIN=${DOMAIN}"
      - "WEBTHEME=default_dark"
    volumes:
      - ${ROOT}/pihole/etc/pihole:/etc/pihole
      - ${ROOT}/pihole/etc/dnsmasq.d:/etc/dnsmasq.d
      - ${CERTIFICATES}:/certificates
    networks:
      - backend
    ports:
      - ${SERVER_IP}:53:53/udp
      - ${SERVER_IP}:53:53/tcp
      - 8002:80
      - 8003:443
    restart: unless-stopped
networks:
  backend:
    driver: bridge

As can be seen, a basic custom bridge network called "backend" is created. Once this was in play, along with specifying the IP address with Pi-hole ports, everything works as expected and the solution still looks somewhat elegant.

I have a sudo user as uid/gid 1000, which is typically the first user created in a basic Ubuntu installation. It is not necessary to be sudo, but the file system should be setup that 1000:1000 has 755, at least.

As a sidenote, I have a FritzBox modem and there is no way to change the domain name, so its fritz.box and I don't really care about that on a small private network.

Some forums suggest that each part of the compose stack needs a "dns" key, but that to me seems tacky and more like a band-aid than progress.

I realise from my work-life that Docker and containerisation in general are fast-moving technologies, so I kind of forgot that from time to time, my casual approach to my home network is often one-step behind.

I did not find any clear answers to this problem online, so I hope this post is enough for somebody else.

Keywords for searching this post: ubuntu docker-compose-v2 name resolution bridge

Good luck.