Andrew Mercer
on this page

1. What CoreDNS Is

CoreDNS is a general-purpose, plugin-based DNS server written in Go, built around a single core idea: DNS behavior is assembled from a chain of plugins, each doing one job (serving zone data, forwarding, caching, rewriting, exposing metrics), rather than one monolithic daemon with a fixed feature set. It's a CNCF graduated project, and its most consequential deployment by far is as the default in-cluster DNS server for Kubernetes since v1.13 (replacing kube-dns) — if you run Kubernetes, you are almost certainly already running CoreDNS, whether you've configured it directly or not.

Where BIND and Unbound are configured with dedicated DSLs (named.conf, unbound.conf), CoreDNS is configured with a Corefile, and its behavior is entirely determined by which plugins are enabled and in what order — there's no fixed "authoritative vs. recursive" mode; a server block with the file plugin is authoritative, one with forward is a forwarder, and one with both does both, per zone.

2. The Corefile

. {
    forward . 8.8.8.8 8.8.4.4
    cache 30
    log
    errors
}

Structure: one or more server blocks, each headed by the zone(s) and port it serves, containing an ordered list of plugin directives. . means "the root zone" — i.e., "everything," the same convention as BIND's zone "." hint zone or Unbound's forward-zone name: ".".

Multiple server blocks can coexist, each scoped to different zones:

example.local:53 {
    file /etc/coredns/db.example.local
    log
}

. {
    forward . /etc/resolv.conf
    cache
}

Here, example.local is served authoritatively from a zone file, while everything else is forwarded to whatever resolvers the host itself uses.

3. Core Plugins

Plugin Purpose
forward Forward queries to upstream resolvers, with health-checked failover across multiple targets
file Serve a zone from a standard BIND-format zone file — authoritative serving
auto Like file, but automatically reloads zone files from a directory on change, no restart needed
cache Cache responses; argument is TTL cap in seconds
kubernetes Serve DNS records for Kubernetes Services and Pods directly from the API server's state — this is what makes CoreDNS the Kubernetes DNS server
hosts Serve entries from an /etc/hosts-style file, similar in spirit to dnsmasq's --addn-hosts
rewrite Rewrite query names/types/classes before further processing — used heavily for Kubernetes' internal name-mangling needs
log / errors Structured logging of queries and errors
metrics (prometheus) Expose Prometheus-format metrics on a /metrics endpoint — first-class observability, unlike BIND/Unbound where metrics require extra scraping tooling
health Liveness/readiness HTTP endpoint, used directly by Kubernetes' own liveness probes for the CoreDNS pod itself
autopath Optimizes Kubernetes' search-domain expansion, avoiding repeated NXDOMAIN round-trips for each search suffix tried
dnssec On-the-fly DNSSEC signing for authoritative answers

4. CoreDNS in Kubernetes

The most likely place you'll actually touch CoreDNS is its ConfigMap in a cluster:

kubectl -n kube-system get configmap coredns -o yaml

A typical cluster Corefile:

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

Walking this: kubernetes handles the cluster's internal namespace (cluster.local) and reverse zones, resolving Service/Pod names against live API state rather than a static zone file — a Service's ClusterIP DNS entry appears the moment the Service object exists, with no file to edit, the cluster-scale analog of dnsmasq's DHCP-to-DNS integration. Anything CoreDNS doesn't recognize as an in-cluster name falls through (fallthrough) to the forward plugin, which sends it to the node's own upstream resolvers. loop detects and breaks accidental forwarding loops (a real risk if a misconfigured cluster forwards back to itself). reload watches the Corefile/ConfigMap for changes and reloads without a pod restart.

Editing cluster DNS behavior (adding a custom stub domain, changing upstream forwarders, adding a rewrite rule for a legacy hostname) means editing this ConfigMap and letting reload pick it up, or restarting the CoreDNS deployment if reload isn't present.

5. Standalone / Non-Kubernetes Use

CoreDNS works perfectly well outside Kubernetes as a general authoritative or forwarding server — a legitimate BIND/Unbound alternative when you want plugin composability, first-class Prometheus metrics, or Go-based extensibility (writing a custom plugin is a supported, documented path, unlike patching BIND or Unbound's C codebases).

# Download a release binary, or:
docker run -p 53:53/udp -v $(pwd)/Corefile:/Corefile coredns/coredns

A small authoritative example equivalent to a BIND master zone:

example.local:53 {
    file /etc/coredns/db.example.local
    log
    errors
}

with a standard zone file (file plugin reads ordinary BIND-format zone files — SOA, NS, A, MX records and all, no CoreDNS-specific syntax needed).

6. Reloading and Zero-Downtime Changes

Two different mechanisms matter here: - reload plugin — watches the Corefile itself for changes and hot-reloads CoreDNS's configuration without dropping the listening socket. - auto plugin — for zone data specifically, watches a directory of zone files and reloads any that change, independent of the Corefile changing at all.

Without either, a CoreDNS restart (brief, since it's a fast-starting Go binary) is the fallback — in Kubernetes this is a rolling pod restart of the CoreDNS Deployment, non-disruptive as long as more than one replica is running (the default).

7. Observability

Unlike BIND/Unbound, where "statistics" means parsing log lines or querying a control socket, CoreDNS's prometheus plugin exposes counters and histograms (query counts by zone/rcode/type, forward latency, cache hit ratio) directly as scrapeable Prometheus metrics — a natural fit if the rest of your stack (as in a homelab or Kubernetes environment) is already Prometheus/Grafana-based, since there's no separate exporter to run.

8. CoreDNS vs. BIND/Unbound/dnsmasq

CoreDNS BIND Unbound dnsmasq
Config model Corefile, plugin chain named.conf unbound.conf dnsmasq.conf
Extensibility Native Go plugins Limited (C source patches) Limited Limited
Kubernetes-native Yes — the default No No No
Metrics First-class Prometheus External tooling needed unbound-control stats Minimal
Best fit Kubernetes, cloud-native, programmable DNS Authoritative hosting at scale LAN/validating resolver Small LAN + DHCP
  • CoreDNS project: https://coredns.io/
  • Plugin index: https://coredns.io/plugins/
  • Kubernetes DNS documentation: https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/