Skip to content

Creating a Pi Hole instance with Ansible and Docker

Published: at 08:00 PM

Table of contents

Open Table of contents

Preface

Having been given a Raspberry Pi 3B+ for my birthday many months ago, I had flirted with a few ideas of what to do with it. Attaching it to a drone, doing some facial recognition and firing some foam darts at my flat mates was high up on my list … But as they had given it to me it seemed a little bit harsh. Around New Years I came across a few “self hosting” videos and I decided I would play around with something in that realm. I quite liked the idea of a NAS or password manager but didn’t quite want to jump that deep into “self hosting”, so instead I decided to go with Pi Hole advert blocking and trying to work in some automatic setup using Ansible and some best practices.

Pi Hole Ansible Self Hosting video example

Version 1

Starting with the basics, I went out and found a github repo that would do some security tweaks on top of a default Raspbian OS install. The repo is pretty old now but seemed like a good place to start from and to modify further as I wanted to run the whole thing in Docker. Taking this, I started my own github repo, disabled a few of the features that would get in the way at the beginning (like ssh keys) and added a few new UFW rules that the Pi Hole documentation recommends. Doing this was actually pretty straight forward, in fact the much harder bit was setting up a VM of Raspbian to test with.

- name: Pi hole open port 80 tcp
        ufw:
          rule: allow
          port: '80'
          proto: tcp

Ultimately, I thought it would be easier while I was making small changes to my Ansible setups to have a VM snapshot that I could role back. Once I got this setup using Virtual Box it worked quite well for both Pi Hole and the security/firewall/permission changes. But it didn’t for Docker. The version of Docker on the VM and the one for the Pi were different so when Ansible would try to install it, it wouldn’t find a corresponding version. In the end, I bit the bullet and had to do the process of taking out the memory card after each run of my script and then wiping it, to test new changes to my setup.

At this point, I took a little break from the mini-project as I had managed to get my script to install Docker and Pi Hole (with configurations), then modify the user and settings of the Pi to make it (seemingly?) more secure and instead decided to use my slightly less advert heavy internet for a week or so (I also bricked my router trying to get it to use the Pi as the DNS server).

Version 2

However, I still had this nagging feeling I hadn’t quite finished. I still wanted to get it to update more gracefully and I felt like I could make the Pi Hole install a bit more complete because I hadn’t got the DNS stuff to work with my router. I then came across this video, describing Unbound and why and how to use it with Pi Hole. This seemed like a good addition to the project and didn’t seem like it would be too difficult to do either…

The updates issue was easy enough to solve, using another container called Watchtower which will search for updates for each of the running containers in Docker based on a schedule. In this case I set it up using CRON. Helpfully the site I was using https://crontab.guru/ and Watchtower have a slightly different syntax. The latter starts with a seconds column but the site does not, so it took me a hot second to diagnose why my CRON jobs weren’t running.

# CRON job to run every saturday morning at 2am, note the seconds coloumn at the beginning (left most column).
# Sites like: https://crontab.guru/ don't include it !
WATCHTOWER_CRON: "0 0 2 * * SAT"

The Unbound recursive DNS resolver on the other hand was actually quite hard to set up in Docker AND to get working with Pi Hole. Having taken heavy inspiration from other repositories so far, I couldn’t quite work out the settings and configuration that Ansible and Docker needed to get both Pi Hole and Unbound to interact and co-operate. In the end I found a blog post which prompted me to change my Docker networking and ultimately I found a few errors in the logs about the incorrect type of network adapters specified with other options I was trying to use. That lead me to use network mode: host which worked with both Pi Hole and Unbound. Retrospectively now seeing the Pi Hole documentation while writing this post I noticed this setting is recommended when installing Pi Hole via Docker, which if I had seen it earlier, would have saved me a fair amount of time.

- name: Start/Update pihole container
  docker_container:
    name: pihole
    image: "{{ pihole_image }}"
    pull: yes
    restart_policy: unless-stopped
    env:
      TZ: "{{ timezone }}"
      WEBPASSWORD: "{{ pihole_webpassword }}"
      PIHOLE_DNS_: "{{ pihole_dns }}"
      ServerIP: "{{ pihole_ip }}"
      hostname: "pi.hole"
    network_mode: host
    volumes:
      - "/home/{{ ansible_user }}/pihole/pihole/:/etc/pihole/"
      - "/home/{{ ansible_user }}/pihole/dnsmasq.d/:/etc/dnsmasq.d/"

Summary

Having finished setting up the Pi Hole, it now seems to be working as intended as can be seen below, so I think I am pretty much done with this project unless I come across something that would improve the security or should really be changed (configuration wise).

Pi-hole Dashboard

Features:

Things that maybe still could be improved?

Lots of inspiration was taken from other repositories as well as blog posts that should all be found in the Readme in my Github Repository along with all of the code for this project.