Pihole 6 and HTTPS

I run two PiHole's in my home network - one as a container on my NAS, the other also as a container on it's own dedicated VM.

The idea, in theory, is that as long as the NAS is up, I should have some sort of DNS services around. It's kinda one of the staples of the network. Switching, routing is up first.. then the NAS.

PiHole 6 came out recently, so it was time to revisit the container configuration across the board.

Problems

  • PiHole tends to crash every so often. I would be seeing resource issues for /dev/shm.
  • I want PiHole to stay up
  • Connectivity should be via HTTPS
  • DoH should be working

Fixes

DoH

Getting DoH working was already in play. I'm using the https://github.com/crazy-max/docker-cloudflared container, which simply proxies DNS connections to Cloudflare DoH's services. This operates on local port 5053.

SHM Memory Issues

PiHole's SHM issues are addressed by adding shm_size: '256M' to the docker's service configuration. I have not seen the errors occur since adding this, but monitoring continues.

Autoheal

Keeping PiHole running, I'm using https://github.com/willfarrell/docker-autoheal. This container monitors all (or selected) containers and restarts them if necessary. This required a bit of tuning, because the PiHole container can take a few to start, especially after the container has been running a while. More on this later.

HTTPS

This took a bit of work, but I think should hold. Leaning on https://gist.github.com/kaczmar2/027fd6f64f4e4e7ebbb0c75cb3409787 and https://github.com/acmesh-official/acme.sh as resources, I wind up with the following service configuration in docker-compose.yaml:

 1  acme-sh:
 2    restart: always
 3    image: neilpang/acme.sh
 4    container_name: acme-sh
 5    stdin_open: true
 6    tty: true
 7    volumes:
 8      - "/volume1/docker/pihole/pihole:/etc-pihole"
 9      - "/volume1/docker/pihole/acme.sh:/acme.sh"
10      - "/var/run/docker.sock:/var/run/docker.sock"
11    environment:
12      - "DOMAIN=${DOMAIN}"
13      - "CF_Token=${CF_Token}"
14      - "CF_Email=${CF_Email}"
15    command: >
16      sh -c "
17      apk add docker &&
18      echo 'Starting up. Domain: $DOMAIN' &&
19      acme.sh --register-account -m $CF_Email &&
20      acme.sh --issue --dns dns_cf -d $DOMAIN --fullchain-file /tmp/fullchain.cer --key-file /tmp/domain.key --reloadcmd 'rm -f /etc-pihole/tls* && cat /tmp/fullchain.cer /tmp/domain.key | tee /etc-pihole/tls.pem && rm /tmp/* && docker restart pihole' ;
21      crond -n -s -m off
22      "

There's a bit to unpack here, but we'll start at volumes:

  • The mapping to /etc-pihole - this allows the service to make changes to pihole's certificates. This is probably a bit .. uh, liberal, since the acme.sh service doesn't need access to all of pihole's configurations, but .. yeah
  • The mapping to /acme.sh allows us to retain key material and configurations for acme.sh between container restarts.
  • The mapping for docker.sock allows acme.sh to restart pihole if necessary.

Looking at environments, these values are defined in .env in the same directory as docker-compose.yaml:

Finally, the command:

  • Adds docker to the running image. I chose to add docker at runtime, instead of making my own image, beacuse I figured the level of customization was low enough to not warrant reinventing the wheel.
  • Registers my email address with the SSL provider. By default this is ZeroSSL, but can be changed.
  • Finally, issues a new certificate, and outputs the key material in /tmp
  • Generates a reload command that will concatenate the key material in /tmp to pihole's tls.pem file
  • then restarts Pihole

Pihole should only restart if the acme.sh --issue command returns 0 (with &&), so if the cert is still valid at container startup - nothing happens.

Finally, we run crond -n -s -m off, which is the daemon mode for acme.sh (see https://github.com/acmesh-official/acme.sh/blob/master/Dockerfile). Note the preceeding ;! We want crond .. to run regarldess if the renewal command succeeded or not.

PiHole Fixes

Last bit.. PiHole on the NAS tends to choke at startup if it's been running for a while. I believe this has to do with the size of pihole-FTL.db, an SQLLite DB that retains the query history.

In pihole.toml, find the [Database] section - somewhere around line 640. I have set maxDBdays to 30.

Don't forget to restart the container after you've made your changes.

docker-compose.yaml

Final docker manifest looks a bit like the following:

 1version: "3"
 2
 3services:
 4  cloudflared:
 5    container_name: cloudflared
 6    image: crazymax/cloudflared
 7    restart: unless-stopped
 8    environment:
 9      - "TS=America/Edmonton"
10    ports:
11      - "5053:5053/udp"
12      - "5053:5053/tcp"
13
14  pihole:
15    shm_size: '256M'
16    image: pihole/pihole:latest
17    container_name: pihole
18    hostname: milner-pihole
19    depends_on:
20      - cloudflared
21    environment:
22      TZ: "America/Edmonton"
23      FTLCONF_dns_upstreams: "cloudflared#5053"
24    ports:
25      - "8080:80/tcp"
26      - "53:53/tcp"
27      - "53:53/udp"
28      - "8443:443/tcp"
29    volumes:
30      - "/volume1/docker/pihole/dnsmasq.d/:/etc/dnsmasq.d"
31      - "/volume1/docker/pihole/pihole/:/etc/pihole/"
32    restart: unless-stopped
33    cap_add:
34      - CAP_SYS_NICE
35
36  acme-sh:
37    restart: always
38    image: neilpang/acme.sh
39    container_name: acme-sh
40    stdin_open: true
41    tty: true
42    volumes:
43      - "/volume1/docker/pihole/pihole:/etc-pihole"
44      - "/volume1/docker/pihole/acme.sh:/acme.sh"
45      - "/var/run/docker.sock:/var/run/docker.sock"
46    environment:
47      - "DOMAIN=${DOMAIN}"
48      - "CF_Token=${CF_Token}"
49      - "CF_Email=${CF_Email}"
50    command: >
51      sh -c "
52      apk add docker &&
53      echo 'Starting up. Domain: $DOMAIN' &&
54      acme.sh --register-account -m $CF_Email &&
55      acme.sh --issue --dns dns_cf -d $DOMAIN --fullchain-file /tmp/fullchain.cer --key-file /tmp/domain.key --reloadcmd 'rm -f /etc-pihole/tls* && cat /tmp/fullchain.cer /tmp/domain.key | tee /etc-pihole/tls.pem && rm /tmp/* && docker restart pihole' ;
56      crond -n -s -m off
57      "
58
59  autoheal:
60    restart: always
61    image: willfarrell/autoheal
62    environment:
63      - AUTOHEAL_CONTAINER_LABEL=all
64      - AUTOHEAL_INTERVAL=60
65      - AUTOHEAL_START_PERIOD=60
66    volumes:
67      - /var/run/docker.sock:/var/run/docker.sock