CoreDNS, the de facto standard DNS server for Kubernetes, can be configured to serve zones directly from files. This is surprisingly powerful for scenarios where you need fine-grained control over your DNS records without relying on external DNS providers or complex integrations, especially for internal, non-public domains.

Let’s see CoreDNS serve a zone from a file. Imagine we want example.com to be served from a local file.

First, create a zone file, say /etc/coredns/zones/db.example.com:

$TTL 600
@       IN      SOA     ns1.example.com. admin.example.com. (
                        2023010101 ; Serial
                        3600       ; Refresh
                        1800       ; Retry
                        604800     ; Expire
                        60         ; Minimum TTL
                        )

; Name Servers
@       IN      NS      ns1.example.com.
@       IN      NS      ns2.example.com.

; Mail Exchanger
@       IN      MX      10 mail.example.com.

; A Records for hosts
@       IN      A       192.168.1.10
ns1     IN      A       192.168.1.1
ns2     IN      A       192.168.1.2
www     IN      A       192.168.1.10
mail    IN      A       192.168.1.20

; CNAME Records
ftp     IN      CNAME   www.example.com.

; TXT Records
@       IN      TXT     "v=spf1 mx a -all"

Now, configure CoreDNS to use this file. In your Corefile (typically located at /etc/coredns/Corefile in Kubernetes or /etc/coredns/Corefile on a standalone install), you’d add a block like this:

.:53 {
    errors
    health {
       lameduck 5s
    }
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       fallthrough in-addr.arpa ip6.arpa
    }
    prometheus :9153
    forward . /etc/resolv.conf
    cache 30
    # Serve example.com from a local file
    file /etc/coredns/zones/db.example.com example.com {
        transfer to 192.168.1.0/24 # Optional: Allow zone transfers to specific IPs
    }
}

The file plugin tells CoreDNS to load the zone example.com from the file /etc/coredns/zones/db.example.com. The transfer directive is crucial if you plan to have secondary DNS servers for this zone; it specifies which IP addresses are allowed to request a zone transfer (AXFR). Without it, zone transfers would be denied.

Let’s break down how this works. When CoreDNS receives a query for a name within example.com, it checks if it has any plugins configured to handle that domain. If the file plugin is configured for example.com, CoreDNS will not consult other plugins (like kubernetes or forward) for that specific query. Instead, it will parse the specified zone file and return the corresponding record. The SOA record in the zone file provides essential information about the zone, including its serial number, which is critical for zone transfers and dynamic updates. The NS records define the authoritative name servers for the zone.

The most surprising thing about this setup is how robust it is for internal use cases. You can manage a complete, authoritative DNS namespace for your private infrastructure — internal services, development environments, or even specific application domains — with just a text file and CoreDNS. It bypasses the need for external DNS management for these specific, non-publicly resolvable domains, simplifying operations significantly.

Consider a scenario where you’ve updated the serial number in your db.example.com file. CoreDNS, by default, periodically reloads zone files. However, for immediate effect or to trigger zone transfers to secondary servers, you can send a SIGHUP signal to the CoreDNS process: kill -HUP <coredns_pid>. This forces CoreDNS to re-read its configuration and zone files, applying the changes without a full restart.

If you need to dynamically update records without manual file edits and SIGHUP signals, the next step would be exploring CoreDNS’s etcd or kubernetes plugins for more integrated dynamic DNS management.

Want structured learning?

Take the full Coredns course →