DNS Server for Homelab
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:
- docker-compose --build -d (starts the build process with detached mode so that it runs in the background
- docker-compose.yml (contains the metadata for building the container)
- Dockerfile (Docker image to run, includes extra command for pointing to the database file to use)
- Corefile (Configuration file for zones and listening server, more info here
- 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