Open-source project
coredns/coredns avatar
coredns/coredns

CoreDNS: a DNS server built from chained plugins

Project brief: CoreDNS is a DNS server that chains plugins. CoreDNS is a DNS server/forwarder, written in Go, that chains plugins.

14,348 stars2,534 forksGoApache-2.0

At a glance

What is it?
CoreDNS is a Go DNS server and forwarder whose behaviour comes entirely from the plugins listed in a Corefile. It is the right tool when you need to shape DNS answers programmatically, and the wrong one when you want a recursive resolver with a web UI.
Who is it for?
Adopt CoreDNS when DNS answers must be computed from a source of truth you already run, such as Kubernetes objects, etcd keys or a signed zone file, and when you are willing to own a Corefile. Do not adopt it if you want a recursive resolver with a graphical interface; nothing in the repository provides one, and the plugin set is aimed at serving and forwarding, not at being a consumer resolver with a UI.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem CoreDNS solves: DNS answers that are computed, not just stored

A conventional authoritative server answers from a zone file, and a conventional forwarder passes queries upstream. CoreDNS treats both as plugins and lets you place them in a chain, so the answer to a query can be produced by whichever plugin claims it first. The README describes the server as one that "chains plugins", and each plugin performs a DNS function. That single design decision is what makes the project useful beyond the zone-file case.

The audience is infrastructure engineers who already have a source of truth for names. If your records live in Kubernetes, etcd, Route 53 or a signed zone file on disk, CoreDNS has a plugin that reads from it. If your records live in a spreadsheet and change twice a year, a smaller authoritative server will do the job with less configuration surface.

The project is a Cloud Native Computing Foundation graduated project, which matters less for the code than for the fact that its governance and release process are documented in the repository through GOVERNANCE.md and CODEOWNERS. The last push to master was on 2026-08-19, and the most recent release listed is v1.14.7 on the same date.

How the plugin chain actually resolves a query

Configuration lives in a file named Corefile. When CoreDNS starts it looks for Corefile in the current working directory. The file is organised into server blocks, each beginning with a zone and an optional port, such as .:53 or example.org:1053. Inside a block, directives name plugins, and the order in which plugins execute is not the order you typed them: the repository ships plugin.cfg, and directives_generate.go turns that file into generated Go source under core/plugin and core/dnsserver. The Makefile shows this explicitly, with the check target regenerating core/plugin/zplugin.go and core/dnsserver/zdirectives.go whenever plugin.cfg changes.

That generated chain is the mechanism. A query arrives, the server walks the compiled plugin list, and the first plugin whose configuration matches the zone and query type gets a chance to handle it. A plugin can write a response and stop the chain, or pass the query onward. This is why the README example that serves example.org and forwards everything else works: the more specific block handles its zone, and the . block catches the rest.

Because the chain is compiled in, plugin order is a build-time property, not a runtime one. Adding a plugin means editing plugin.cfg and rebuilding, or setting the COREDNS_PLUGINS environment variable to a comma-separated list in the same format as plugin.cfg, which the README notes as the way to enable extra plugins during a build. There is no runtime plugin loader.

Building CoreDNS from source and running a first Corefile

The README assumes a working Go setup and states that the Go version must be 1.25.0 or higher for go mod support. The repository's go.mod declares go 1.26.0 and notes that CoreDNS supports the last two Go versions, following upstream Go policy. Clone and build with make:

bash
git clone https://github.com/coredns/coredns
cd coredns
make

This yields a coredns binary in the repository root. If you would rather not install Go, the README gives a Docker-based build that mounts the working directory into the golang:1.25 image and runs the generation and build targets inside it. The command is long but self-contained, and it produces the same binary.

With the binary in hand, write a minimal Corefile that forwards everything to a public resolver and logs each query:

bash
cat > Corefile <<EOF
.:53 {
    forward . 8.8.8.8
    log
}
EOF

Run it with the -conf flag pointing at that file, then query it with dig against 127.0.0.1. The README's quick start uses exactly this sequence, and the expected result is that the query is forwarded to 8.8.8.8 and the response comes back, with a line appearing in the log on standard output for each query.

If port 53 is already taken by a system resolver, the README offers two ways out: change the port in the server block to .:1053, or leave the block as .:53 and pass -dns.port 1053 on the command line. Starting CoreDNS with no configuration at all loads the whoami and log plugins and listens on port 53, which is a quick way to confirm the binary runs before you write anything.

Transports, zone serving and the parts that are genuinely limited

CoreDNS listens on more than plain DNS. The README lists UDP and TCP, TLS for DoT, DNS over HTTP/2 for DoH, DNS over HTTP/3 for DoH3, DNS over QUIC for DoQ, and gRPC, which the README flags as not a standard. If you need encrypted DNS on the wire, the server side is covered without extra software.

On the serving side, the file and auto plugins serve zone data from disk, with DNSSEC support described as NSEC only. That parenthetical is the limitation worth reading twice: NSEC3 is not what the README claims, so a zone that requires NSEC3 denial of existence is not served correctly by the file plugin as documented. The secondary plugin retrieves zone data from primaries but the README says AXFR only, so incremental transfer is out. The dnssec plugin signs zone data on the fly, and transfer, combined with file, lets CoreDNS act as a primary.

There is no built-in recursion. The forward plugin sends queries to another recursive nameserver, and the README's description of the project is a server and forwarder, not a resolver. If you point CoreDNS at nothing and ask it for a name it does not serve, you get a failure, not a recursive lookup. That is a deliberate boundary, and it is the most common source of confusion for people who expect a drop-in replacement for a full resolver.

Caching is a plugin, not a core behaviour. The cache plugin is listed among the capabilities, and it applies to the server block where you enable it. A block without cache will forward every query upstream.

What the Kubernetes story adds, and what it costs

The kubernetes plugin is the reason many teams encounter CoreDNS at all. It makes the server answer from cluster objects rather than from a zone file, which is why the plugin appears in the README's list of backends alongside etcd. The README does not document the Kubernetes deployment itself; that lives in the CoreDNS site and in the distribution you are running, so treat any cluster-specific default as belonging to your platform rather than to this repository.

The cost of that model is that the Corefile becomes a piece of cluster infrastructure you must version and review. Plugin order is fixed at build time, so a change to plugin.cfg is a change to the binary, and a change to the Corefile is a change to behaviour at startup. Neither is hot-reloaded in the way an operator might assume from a configuration file.

The container image has its own constraints. The Dockerfile builds on debian:stable-slim, applies setcap cap_net_bind_service to the binary so it can bind port 53 without root, and then copies it into gcr.io/distroless/static-debian12:nonroot. The final image runs as USER 65532:65532, and the comment in the Dockerfile explains why the numeric uid is used rather than the named user: Kubernetes rejects a named user at admission when runAsNonRoot is set, with the message that it cannot verify the user is non-root. The image exposes 53 and 53/udp and sets ENTRYPOINT ["/coredns"]. If your platform injects a different security context, that numeric uid is the value to expect.

How CoreDNS differs from BIND and dnsmasq

BIND is the obvious comparison for anyone serving authoritative zones. BIND reads zone files and a named.conf, and its configuration language is its own. It supports NSEC3 and incremental zone transfer as first-class features. CoreDNS reaches the same territory through the file, transfer and dnssec plugins, and the README's own wording limits the DNSSEC support to NSEC and the secondary role to AXFR. If your zone depends on NSEC3 or on IXFR, the documented plugin set is not equivalent, and that difference is architectural rather than a missing flag.

dnsmasq is the comparison for small networks and local forwarding. It is a single small daemon with a flat configuration file and a fixed feature set. CoreDNS is the opposite: a small core with a compiled plugin chain, where the feature set is whatever you built into the binary. dnsmasq will not answer from Kubernetes objects or etcd; CoreDNS will not give you a DHCP server. The trade is between a fixed tool you configure and a framework you assemble.

A third comparison is the resolver you probably already run on the host. systemd-resolved and similar local resolvers handle recursion and caching for a workstation. CoreDNS is aimed at servers that answer for a zone or a set of zones, and the README's forwarding example points at 8.8.8.8 precisely because recursion is somebody else's job.

Licence, releases and the cost of tracking upstream

CoreDNS is licensed under Apache-2.0, the LICENSE file sits at the repository root, and the project is a CNCF graduated project. Apache-2.0 is a permissive licence with an explicit patent grant and a requirement to preserve notices; if you redistribute a modified binary, the notice obligations apply to what you ship. That is a general property of the licence, not advice about your situation, and anything involving redistribution or a derived product belongs with your own counsel.

The upgrade cost is dominated by two things. First, the plugin chain is generated from plugin.cfg, so a release that adds or reorders plugins changes the compiled directive order, and a Corefile that relied on the previous order can behave differently. The Makefile's check target exists to regenerate those files, which is a hint that they are expected to change. Second, go.mod pins a large dependency set including the miekg/dns library, the quic-go stack and cloud SDKs for Azure, AWS and Google. Building from source pulls all of it, so a build in a restricted network needs a module proxy or a vendored copy.

There is no separate upgrade tool. The repository ships Makefile.release and Makefile.docker alongside the main Makefile, which is how the project's own artifacts are produced, but the README does not document a rollback procedure for a bad Corefile or a bad binary. Plan for that yourself: keep the previous binary and the previous Corefile, and know which one you will revert.

Editorial conclusion

Adopt CoreDNS when DNS answers must be computed from a source of truth you already run, such as Kubernetes objects, etcd keys or a signed zone file, and when you are willing to own a Corefile. Do not adopt it if you want a recursive resolver with a graphical interface; nothing in the repository provides one, and the plugin set is aimed at serving and forwarding, not at being a consumer resolver with a UI. Before rollout, verify three things against your own environment: that port 53 is free or that you have set cap_net_bind_service, that the image runs as uid 65532 when runAsNonRoot is set, and that the plugin order in your Corefile matches the chain you expect. Start by running the binary with -conf against a two-line Corefile and querying it with dig before you touch a cluster.

Frequently asked questions

What is CoreDNS used for?

It is a DNS server and forwarder written in Go that chains plugins. Each plugin performs a DNS function, so the same binary can serve zone files, read from Kubernetes or etcd, forward to an upstream resolver, cache responses and expose Prometheus metrics, depending on what the Corefile enables.

How does CoreDNS work?

Configuration lives in a Corefile of server blocks, each with a zone, an optional port and a list of plugin directives. The plugin execution order comes from plugin.cfg, which is compiled into generated Go source by directives_generate.go, so a query is passed down a fixed chain until a plugin answers it.

How to install CoreDNS on Ubuntu?

The README documents building from source rather than a distribution package: clone the repository, ensure Go 1.25.0 or higher is installed, and run make to produce a coredns binary. It also gives a Docker-based build for machines without a Go environment.

How to use CoreDNS in Kubernetes?

The kubernetes plugin is listed among the backends that let CoreDNS answer from cluster objects. The README does not document the cluster deployment itself, so the manifests and defaults you get come from your distribution rather than from this repository.

How to set up CoreDNS?

Create a Corefile in the working directory with a server block such as .:53 containing a plugin directive, then start the binary with -conf Corefile. If port 53 is occupied, either change the port in the block or pass -dns.port with the port you want.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/coredns-coredns.svg)](https://hysenlabs.com/projects/coredns-coredns)