Docker - Pihole Container

This blog post will detail how to create a custom pihole container with a pre-loaded script to update our block and allow lists automatically.

To have this custom container we will have to create our own custom docker image.

First let's create our custom Dockerfile:

# Use the official pihole image as a base.
FROM pihole/pihole:latest

# Install custom software.
RUN apt-get update && apt-get install -Vy php-cli php-sqlite3 php-intl php-curl python3 vim

# Install the scripts to update our pihole automatically.
RUN wget -O - https://raw.githubusercontent.com/jacklul/pihole-updatelists/master/install.sh | bash

The Dockerfile above uses the base pihole image as base and a few custom software are added. I have installed python3 and vim to help with future automation and manipulation of config files inside the container.

Let's create our image :

root@home-svr1:~/docker-projects/pihole# docker build --no-cache --pull --network=macvlan10 -t "pihole:Dockerfile" .
Sending build context to Docker daemon  30.46MB
Step 1/3 : FROM pihole/pihole:latest
latest: Pulling from pihole/pihole
Digest: sha256:3a39992f3e0879a4705d87d0b059513af0749e6ea2579744653fe54ceae360a0
Status: Image is up to date for pihole/pihole:latest
 ---> eb777ee00e0c
Step 2/3 : RUN apt update && apt install -Vy php-cli php-sqlite3 php-intl php-curl python3 vim
 ---> Running in 53c8cf3d6772
 ---> 5c1a0664a6ad
Step 3/3 : RUN wget -O - https://raw.githubusercontent.com/jacklul/pihole-updatelists/master/install.sh | bash
 ---> Running in 1e83d9d23a6e
Removing intermediate container 1e83d9d23a6e
 ---> 45d9d94fd232
Successfully built 45d9d94fd232
Successfully tagged pihole:Dockerfile

root@home-svr1:~/docker-projects/pihole# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
pihole              Dockerfile          45d9d94fd232        15 minutes ago      432MB
pihole/pihole       latest              eb777ee00e0c        6 weeks ago         333MB
I have struggled badly to have the image connecting to the internet and pulling the updates. After hours of research, I have finally stumbled upon this article and with the help of the docker's documentation, the image has finally been created.
We need to point a working network to the docker build command for RUN to work.

We can now create our docker container configuration file :

root@home-svr1:~/docker-projects/pihole# touch docker-compose.yml

And, add the below configuration to it :

version: "3"

services:
  pihole:
    container_name: pihole
    image: pihole:Dockerfile
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "67:67/udp"
      - "80:80/tcp"
      - "443:443/tcp"
    environment:
      TZ: 'Europe/London'
    volumes:
      - './etc-pihole/:/etc/pihole/'
      - './etc-dnsmasq.d/:/etc/dnsmasq.d/'
      - './etc-pihole-updatelists/:/etc/pihole-updatelists/'
    hostname: pihole
    dns:
      - 192.168.6.1
    networks:
            macvlan6:
                    ipv4_address: 192.168.6.10
    cap_add:
      - NET_ADMIN
    restart: unless-stopped

networks:
  macvlan6:
          external: true
docker-compose.yml

Two options that are interesting to be aware of are :

image: This option tells the docker-compose which image to use and in this case, we are point to our newly created image.

network:  In the service section we defined the name of the network to be used as long with the IP address we want our container to use and in the global configuration, we used the external flag to tell docker-compose to use an already created network.

dns: This option had to be added otherwise my container was trying to use the macvlan10 gateway as its DNS server. It seems that if not specified docker is going to use the host DNS server instead of the macvlan gateway.

Before running docker-compose we are going to test our config for any errors with the command :

root@home-svr1:~/docker-projects/pihole# docker-compose config
networks:
  macvlan6:
    external: true
services:
  pihole:
    cap_add:
    - NET_ADMIN
    container_name: pihole
    dns:
    - 192.168.6.1
    environment:
      TZ: Europe/London
    hostname: pihole
    image: pihole/pihole:latest
    networks:
      macvlan6:
        ipv4_address: 192.168.6.10
    ports:
    - 53:53/tcp
    - 53:53/udp
    - 67:67/udp
    - 80:80/tcp
    - 443:443/tcp
    restart: unless-stopped
    volumes:
    - /root/docker-projects/pihole/etc-pihole:/etc/pihole:rw
    - /root/docker-projects/pihole/etc-dnsmasq.d:/etc/dnsmasq.d:rw
    - /root/docker-projects/pihole/etc-pihole-updatelists:/etc/pihole-updatelists:rw
version: '3.0'

root@home-svr1:~/docker-projects/pihole#

Our configuration passed without any errors and we are ready to create our container.

root@home-svr1:~/docker-projects/pihole# docker-compose up --detach
Starting pihole ... done

We shall see our container running :

root@home-svr1:~/docker-projects/pihole# docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                        PORTS               NAMES
2ec52935049b        pihole:Dockerfile   "/s6-init"          About a minute ago   Up About a minute (healthy)                       pihole

Let's connect to it and confirm that the update script is running as expected and change the default generated password :

root@home-svr1:~/docker-projects/pihole# docker exec -ti pihole bash
root@pihole:/# pihole-updatelists

Pi-hole's Lists Updater by Jack'lul     
 https://github.com/jacklul/pihole-updatelists

Opened gravity database: /etc/pihole/gravity.db (4.36 MB)

No remote lists are set in the configuration - this is required for the script to do it's job!
See README for instructions.

Updating Pi-hole's gravity...
  [i] Neutrino emissions detected...
  [✓] Pulling blocklist source list into range

  [✓] Preparing new gravity database
  [i] Using libz compression

  [i] Target: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
  [✓] Status: Retrieval successful
  [i] Received 77608 domains

  [✓] Storing downloaded domains in new gravity database
  [✓] Building tree
  [✓] Swapping databases
  [i] Number of gravity domains: 77608 (77608 unique domains)
  [i] Number of exact blacklisted domains: 0
  [i] Number of regex blacklist filters: 0
  [i] Number of exact whitelisted domains: 0
  [i] Number of regex whitelist filters: 0
  [✓] Cleaning up stray matter

  [✓] DNS service is listening
     [✓] UDP (IPv4)
     [✓] TCP (IPv4)
     [✓] UDP (IPv6)
     [✓] TCP (IPv6)

  [✓] Pi-hole blocking is enabled

Finished with 1 error(s) in 3.35 seconds.

root@pihole:/# sudo pihole -a -p
Enter New Password (Blank for no password): 
Confirm Password: 
  [✓] New password set
root@pihole:/#

The update script is working, however, it seems that there is a small bug with it. Instead of using the mapped folder, it is using the configuration file at /etc/pihole-updatelists.conf as seen below :

root@pihole:/etc/cron.d# cat /usr/local/sbin/pihole-updatelists | grep lists.conf
        'CONFIG_FILE'             => '/etc/pihole-updatelists.conf',

I have changed the line as seen on the image above and a bug report is going to be opened with the developer.


As you might have noticed we now have three new folders inside our docker project directory. These folders are mapped to our pihole container and should be used to back up our configs before an upgrade and to edit the lists and configurations of the script running in our container.

root@home-svr1:~/docker-projects/pihole# ll
total 28
drwxr-xr-x 5 root             root             4096 Apr  2 11:24 ./
drwxr-xr-x 3 root             root             4096 Mar 31 21:30 ../
-rw-r--r-- 1 root             root              640 Apr  2 11:23 docker-compose.yml
-rw-r--r-- 1 root             root              212 Apr  2 00:08 Dockerfile
drwxr-xr-x 2 root             root             4096 Apr  2 11:24 etc-dnsmasq.d/
drwxrwxr-x 3 systemd-coredump systemd-coredump 4096 Apr  2 11:43 etc-pihole/
drwxr-xr-x 2 root             root             4096 Apr  2 11:43 etc-pihole-updatelists/

The last step is going to be to set up our custom lists editing the file pihole-updatelists.conf inside our container mapped folder as below :

; Remote list URL containing list of adlists to import
ADLISTS_URL="https://v.firebog.net/hosts/lists.php?type=tick"

; Remote list URL containing exact domains to whitelist
WHITELIST_URL="https://raw.githubusercontent.com/anudeepND/whitelist/master/domains/whitelist.txt"

; Remote list URL containing regex rules for blacklisting
REGEX_BLACKLIST_URL="https://raw.githubusercontent.com/mmotti/pihole-regex/master/regex.list"

; Comment string used to know which entries were created by the script
; You can still add your own comments to individual entries as long
; you keep this string intact
COMMENT="Managed by pihole-updatelists"

; Maximum time in seconds one list download can take before giving up
; You should increase this when downloads fail because of timeout
DOWNLOAD_TIMEOUT=180

I am using a conservative set of blocking rules, but you can check the external links at the end of the article for more aggressive blocking lists.

Let's apply our lists :

root@home-svr1:~/docker-projects/pihole# docker exec -ti pihole bash
root@pihole:/# pihole-updatelists 

      Pi-hole's Lists Updater by Jack'lul     
 https://github.com/jacklul/pihole-updatelists

Opened gravity database: /etc/pihole/gravity.db (4.36 MB)

Fetching ADLISTS from 'https://v.firebog.net/hosts/lists.php?type=tick'... done (30 entries)
Processing... 30 inserted

Fetching WHITELIST from 'https://raw.githubusercontent.com/anudeepND/whitelist/master/domains/whitelist.txt'... done (191 entries)
Processing... 191 inserted

Fetching REGEX_BLACKLIST from 'https://raw.githubusercontent.com/mmotti/pihole-regex/master/regex.list'... done (14 entries)
Processing... 14 inserted

Updating Pi-hole's gravity...

  [✓] Storing downloaded domains in new gravity database
  [✓] Building tree
  [✓] Swapping databases
  [i] Number of gravity domains: 373804 (271031 unique domains)
  [i] Number of exact blacklisted domains: 0
  [i] Number of regex blacklist filters: 14
  [i] Number of exact whitelisted domains: 191
  [i] Number of regex whitelist filters: 0
  [✓] Cleaning up stray matter

  [✓] DNS service is listening
     [✓] UDP (IPv4)
     [✓] TCP (IPv4)
     [✓] UDP (IPv6)
     [✓] TCP (IPv6)

  [✓] Pi-hole blocking is enabled

Finished successfully in 24.22 seconds.

Before the conclusion of our set up let's change the upstream DNS servers pihole is using to resolve the queries. It comes set with google by default, but I prefer to use the more privacy-focused quad9 servers.

Although our pihole is serving DNS to our network if you are not "hijacking" the DNS traffic and forwarding it to your pihole hosts inside your network can bypass your pihole and go to the internet to query its servers.  

Let's imagine our smart TV trying to query a domain to serve ads and it failing with your network's DNS then a hardcoded DNS server is queried instead like google and since your network is not redirecting DNS traffic to your pihole the ad is going to be served and of course that's not the behaviour that we expect from the devices inside our network, for this reason in a future article we are going to be forwarding all DNS traffic to our pihole preventing hosts bypassing it.


At this point, your pihole should be serving DNS to your network like mine is. I hope that my guide helps you with your set up also I would like to thank my colleague Geraldo for pointing me in the right direction when selecting blocking lists.

Below there are a set of handful links used as the source for this article :

Firebog - Block Lists

Update Script recommend by the Firebog

Docker Hub
Pihole docker hub page.
compose-spec/compose-spec
The Compose specification. Contribute to compose-spec/compose-spec development by creating an account on GitHub.
Docker Basics: How to Use Dockerfiles - The New Stack
Ths tutorial will walk you through the process of crafting a Dockerfile. I will demonstrate by using the latest Ubuntu image, update and upgrade that image, and then install the build-essential package. This will be a fairly basic Dockerfile, but one you can easily build upon.
Quad9 | A public and free DNS service for a better security and privacy
A public and free DNS service for a better security and privacy
Attaching Networks to Docker Build
The docker build command is a vital element in the DevOps process. Unlocking the full potential of docker build opens up new use cases and prepares us for the future Buildx.
docker image build
docker image build: Build an image from a Dockerfile