Install Pi-hole and manage local DNS with Ansible

ansible homelab networking pihole

In this article we will install Pi-hole as our DNS server, and create local hosts and DNS records.

Prerequisites

Install Pi-hole

Disable network resolution:

- name: disable systemd-resolved
  systemd:
    name: systemd-resolved.service
    state: stopped
    enabled: no
  become: yes

We are going to bind two configuration folders from the host to docker: one for Pi-hole settings and the other for dnsmasq settings. Let’s define them as variables:

pihole_conf_dir: "{{ ansible_user_dir }}/pihole_data/etc-pihole"
pihole_dnsmasq_dir: "{{ ansible_user_dir }}/pihole_data/etc-dnsmasq.d"

Create the required directories:

- name: create data directories
  file:
    path: "{{ item }}"
    state: directory
  loop:
    - "{{ pihole_conf_dir }}"
    - "{{ pihole_dnsmasq_dir }}"

Before running the container creation, set the resolver namespaces to Cloudflare ones temporarily:

- name: set temporary dns server
  replace:
    path: /etc/resolv.conf
    regexp: "^nameserver .*$"
    replace: "nameserver 1.1.1.1"
  become: yes

Run Pi-hole container:

- name: run pihole
  docker_container:
    name: pihole
    state: started
    image: pihole/pihole:latest
    restart_policy: unless-stopped
    hostname: pi.hole
    env:
      TZ: "{{ lookup('pipe', 'cat /etc/timezone') }}"
      VIRTUAL_HOST: "pi.hole"
      PROXY_LOCATION: "pi.hole"
      ServerIP: "127.0.0.1"
    dns_servers:
      - "127.0.0.1"
      - "1.1.1.1"
    ports:
      - "53:53"
      - "53:53/udp"
      - "8001:80"
    volumes:
      - "{{ pihole_conf_dir }}/:/etc/pihole/"
      - "{{ pihole_dnsmasq_dir }}/:/etc/dnsmasq.d/"

Now, replace the Cloudflare DNS resolver to 127.0.0.1:

- name: set localhost dns server
  replace:
    path: /etc/resolv.conf
    regexp: "^nameserver .*$"
    replace: "nameserver 127.0.0.1"
  become: yes

Configure Pi-hole

Now, we can go to http://{host_ip}:8001/admin and log in with our password (in the docker container logs). There, we go to Settings > DNS and enable conditional forwarding, pointint to our DHCP (our router in most of the cases)

Then, in our DHCP configuration, we need to add the host IP as our first DNS server. If we deploy more Pi-hole instances for redundancy, we’ll need to add thir IPs there as well. In Unifi, we can go to networks, select our LAN network and edit the DHCP DNS server.

At this point, we may need to reconnect to our network or forget the network and connect again.

DNS configuration

In Pi-hole, if we go to Local DNS / DNS Records, we can see that our hosts list will be read from /etc/hosts and /etc/pihole/custom.list. We mounted pihole_conf_dir at /etc/pihole/, so we can create the hosts template (templates/hosts.j2) as:

{% for item in pihole_hosts %}
{{ item.ip }} {{ item.domain }}
{% endfor %}

Define our hosts, for example:

pihole_hosts:
  - { domain: rpi, ip: 192.168.1.16 }
  - { domain: mgmt, ip: 192.168.1.19 }

And create our hosts file:

- name: create pihole hosts
  template:
    src: templates/hosts.j2
    dest: "{{ pihole_conf_dir }}/custom.list"
  become: yes

Our CNAME records are displayed in Local DNS / CNAME Records. We can add CNAME records by creating a file in our dnsmasq directory (pihole_dnsmasq_dir) usint the template (templates/cnames.j2):

{% for item in pihole_cnames %}
cname={{ item.domain }},{{ item.target_domain }}
{% endfor %}

Define our CNAME records, for example:

pihole_cnames:
  - { domain: homeassistant, target_domain: rpi}
  - { domain: nginx, target_domain: mgmt}

And create our dnsmasq configuration file:

- name: create pihole cname records
  template:
    src: templates/cnames.j2
    dest: "{{ pihole_dnsmasq_dir }}/02-custom-cnames.conf"
  become: yes

The hosts file (DNS Records in Pi-hole) don’t act as a DNS records, since wildcard domains are not supported. To add wildcard domains we can use the following template (templates/domains.js):

{% for item in pihole_domains %}
address=/{{ item.domain }}/{{ item.ip }}
{% endfor %}

Let’s say we want to create a local domain called homelab.local with subdomains allowed, so we can use pihole.homelab.local pointing to our Pi-hole host, say 192.168.1.23:

pihole_domains:
  - { domain: homelab.local, ip: 192.168.1.23 }

Then, create another dnsmasq configuration file:

- name: create pihole domain records
  template:
    src: templates/domains.j2
    dest: "{{ pihole_dnsmasq_dir }}/03-custom-domains.conf"
  become: yes