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 theacme.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 foracme.sh
between container restarts. - The mapping for
docker.sock
allowsacme.sh
to restart pihole if necessary.
Looking at environments
, these values are defined in .env
in the same directory as docker-compose.yaml
:
- DOMAIN is the hostname that pihole can be loaded at, and will be used as the subject name for the certificate.
- CF_Token is required by
acme.sh
, and is an API token generated by Cloudflare for your specific domain. See more at https://developers.cloudflare.com/fundamentals/api/get-started/create-token/. - CF_Email is the Cloudflare account email.
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