DNS Server for Homelab

·

4 min read

Introduction

DNS is an important part of a homelab environment and will make it a lot easier to keep track of the various services that you are hosting. For my current homelab setup, I have an external DNS hosted by Dreamhost as part of a shared website hosting which hosts a CNAME for this blog as well as the main host record for an "about me" style website

External DNS

This is managed by Dreamhost and contains minimal entries, and only those that are accessible from the internet to reduce the risk of intrusion

As I use Traefik with Let's Encrypt certificates to enable free and easy automatic deployment I needed to ensure that the DNS provider was on thislist of providers for a DNS based ACME challenge (this means that a CNAME record will be added to your DNS records to prove ownership of the domain).

Dreamhost was one of the providers listed here so I was able to proceed with that and add in the required configuration to Traefik

Internal DNS

My internal DNS is a Docker image that is running CoreDNS as a very lightweight DNS server that uses a configuration file to list all of the record information

My current setup has all DNS requests for the local network pointing to the CoreDNS docker container, which has a wildcard A record for *.abowden.net (my domain) to point to the Docker host/Traefik instance.

This means that inside of my LAN, any requests to any sub-domains will automatically be directed to Traefik and do not need to be manually added for each new service that is setup

Any requests to domains other than the wildcard listed above will be sent to the forwarding address, I currently have mine setup to point to a Pihole instance that is used to block ads on the LAN but it can be forwarded to any other public DNS server if you do not have an internal service

Diagram

With this in mind, here is a flow of internal/external DNS requests:

External

DNS Request (*.abowden.net) -> Dreamhost -> Result returned if record available

Internal

DNS Request () -> CoreDNS (responds to .abowden.net) -> PiHole (blocks requests for ad servers) -> Public DNS (8.8.8.8) for all other requests

Setup of Docker containers

An overview of this container's execution is:

  1. docker-compose --build -d (starts the build process with detached mode so that it runs in the background
  2. docker-compose.yml (contains the metadata for building the container)
  3. Dockerfile (Docker image to run, includes extra command for pointing to the database file to use)
  4. Corefile (Configuration file for zones and listening server, more info here
  5. abowden.db (Database file used to list the DNS records and values)

Docker Compose

The Docker container is setup with this docker-compose.yml file:

version: '3.3'
services:
  coredns:
    build: .
    container_name: coredns
    restart: unless-stopped
    expose:
      - '53'
      - '53/udp'
    ports:
      - '53:53'
      - '53:53/udp'
    labels:
      - coredns
    volumes:
      - ./config:/etc/coredns
    networks:
      web:
      coredns:
        ipv4_address: 172.10.10.100

networks:
  web:
    external: true
  coredns:
    name: coredns
    ipam:
      config:
       - subnet: 172.10.10.0/24

In this file, the Docker host is explicitly bound to a static IP address on an internal Docker network (coredns) that is only shared by itself and the PiHole container; and exposes the ports to the external network (web) that is available directly from the Docker host

Dockerfile

Also to note in this file that it does not use a specific Docker image, but build the Dockerfile located in the same folder (this is done as some arguments are needed to be passed to the executed command:

FROM coredns/coredns:latest

EXPOSE 53 53/udp
VOLUME ["/etc/coredns"]
ENTRYPOINT ["/coredns"]
CMD ["-conf", "/etc/coredns/Corefile"]

This Dockerfile will pull the latest image from coredns/coredns and then set the running command as required to point directly to the Corefile to load in with the configuration

Corefile

This file contains basic information about the ports to listen on and where the database of DNS records is located. It also indicates where to forward requests for zones that can't be found (to the PiHole server at 172.10.10.101)

.:53 {
    forward . 172.10.10.101
    log
    errors
    rewrite stop type AAAA A
    cache 3600
    loop
    loadbalance
    reload
}

abowden.net:53 {
    file /etc/coredns/abowden.db
    log
    errors
}

abowden.db

This db file is a plaintext file that contains the actual DNS records and the values:

abowden.net.        IN    SOA    dns.abowden.net    admin.abowden.net    20201118    7200    3600    1209600    3600
blog.abowden.net.   IN  CNAME   hashnode.network.
*.abowden.net.    IN    A    192.168.0.50

In this case you can see the CNAME that points to the hashnode network for this blog site to resolve internally, and that *.abowden.net should be pointed to the Docker host (192.168.0.50)

Final Thoughts

With the way that this DNS server is setup, it allows for a set once and forget implementation (apart from updating the image from time to time)

I had originally set this up with the goal of being able to move to Kubernetes for the DNS hosting, so that is why I chose to use CoreDNS to host it as well as the fact that it is very simple to implement