CoreDNS can serve DNS zones without explicitly defining them in the Corefile, which is a wild departure from traditional DNS server configuration.

Let’s see CoreDNS in action with a simple setup. Imagine you have a cluster where pods are assigned IPs from 10.244.0.0/16. By default, CoreDNS will already know how to resolve *.cluster.local and cluster.local to those IPs, even if you’ve never explicitly told it about the cluster.local zone.

Here’s a snippet of what a minimal Corefile might look like, often managed by Kubernetes itself:

.:53 {
    errors
    health {
       lameduck 5s
    }
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       fallthrough in-addr.arpa ip6.arpa
    }
    prometheus :9153
    forward . /etc/resolv.conf
    cache 30
    loop
    reload
    loadbalance
}

In this minimal example, the kubernetes plugin is doing the heavy lifting. It’s automatically aware of the cluster’s IP ranges and the cluster.local domain. When a DNS query for, say, my-service.my-namespace.svc.cluster.local arrives, the kubernetes plugin intercepts it. It looks up the corresponding service or pod IP within the cluster’s network and returns the answer. It doesn’t need a pre-defined zone "cluster.local" { ... } block like you’d find in BIND.

The mental model for CoreDNS revolves around a pipeline of plugins. When a DNS request comes in, it’s processed sequentially by the plugins listed within a specific "block." These blocks are typically associated with a DNS zone, but as we saw, some plugins (like kubernetes) can operate implicitly on certain zones without explicit configuration.

The .:53 at the top signifies the "default" block, which applies to all zones not explicitly defined elsewhere. The kubernetes plugin is configured to handle cluster.local, in-addr.arpa, and ip6.arpa. The pods insecure directive means it won’t strictly enforce DNSSEC for pod hostnames, and fallthrough tells it to pass queries for in-addr.arpa and ip6.arpa to the next plugin if it can’t resolve them itself. forward . /etc/resolv.conf is a catch-all: if no other plugin handles the query, it forwards it to the upstream DNS servers defined in /etc/resolv.conf. cache caches responses, loop detects and prevents infinite query loops, reload allows the Corefile to be reloaded without restarting the server, and loadbalance distributes queries across multiple backend servers if they are configured.

A key concept is chaining. Plugins are executed in the order they appear. If a plugin handles a request and returns a response, the processing for that request stops. If a plugin doesn’t handle the request (e.g., it’s for a zone it’s not responsible for, or it can’t find an answer), it "falls through" to the next plugin in the chain. This is why the order matters immensely. For instance, placing kubernetes before forward ensures that internal cluster queries are resolved locally before any attempt is made to query external DNS servers.

What most people don’t realize is that the kubernetes plugin doesn’t just resolve service names. It also handles headless services, stateful sets, and even pod IP address resolution for in-addr.arpa lookups (reverse DNS). When you query 10.244.1.5.in-addr.arpa, the kubernetes plugin, configured with in-addr.arpa and fallthrough, will correctly map that IP back to the pod or service it belongs to within the cluster. This automatic reverse DNS mapping is crucial for many applications and debugging scenarios.

The next hurdle you’ll encounter is understanding how to manage custom zones and override the default behavior for specific domains.

Want structured learning?

Take the full Coredns course →