CoreDNS is a flexible, extensible DNS server that’s become the default in Kubernetes. Running it locally with Docker Compose is a fantastic way to test your DNS configurations or plugins before deploying them to a cluster.
Here’s how to get CoreDNS running in a Docker container, all managed by Docker Compose.
First, let’s create a docker-compose.yml file. This file will define our CoreDNS service.
version: '3.8'
services:
coredns:
image: coredns/coredns:1.11.1
ports:
- "53:53/udp"
- "53:53/tcp"
volumes:
- ./Corefile:/etc/coredns/Corefile
command: "-conf /etc/coredns/Corefile"
The image specifies the CoreDNS version we want to use. I’ve picked 1.11.1 as it’s a recent stable version. The ports section maps UDP and TCP port 53 on your host machine to the container, making CoreDNS accessible from your local network. The volumes section is crucial: it mounts your local Corefile into the container at /etc/coredns/Corefile, which is where CoreDNS looks for its configuration by default. Finally, command tells the container to start with the specified configuration file.
Now, let’s create the Corefile itself. This file dictates how CoreDNS behaves. For a basic setup that just serves local zones, you might have something like this:
.:53 {
hosts {
127.0.0.1 localhost
10.0.0.1 myapp.local
10.0.0.2 db.myapp.local
fallthrough
}
cache 30
log
errors
prometheus :9153
}
This Corefile does a few things:
.:53: This is the main configuration block, applying to all zones (.) on port 53.hosts: This plugin allows you to define static host-to-IP mappings directly in theCorefile. It’s great for local development.fallthrough: If thehostsplugin doesn’t find a matching entry, it passes the query to the next plugin in the chain.cache 30: This enables caching for up to 30 seconds, reducing load and improving response times.log: Enables basic logging of DNS queries.errors: Enables logging of errors.prometheus :9153: Exposes Prometheus metrics on port 9153, useful for monitoring.
To start your local CoreDNS server, simply navigate to the directory containing docker-compose.yml and Corefile in your terminal and run:
docker compose up -d
The -d flag runs the container in detached mode, so it runs in the background.
Once it’s running, you can test it. A common way is using dig. First, make sure your system is configured to use your local CoreDNS as its primary DNS server. On Linux, you’d typically edit /etc/resolv.conf and set nameserver 127.0.0.1. On macOS, you can change your network interface’s DNS settings in System Preferences.
Then, try querying a name defined in your Corefile:
dig @127.0.0.1 myapp.local
You should see output indicating that myapp.local resolves to 10.0.0.1.
; <<>> DiG 9.18.18-0ubuntu0.22.04.1-Ubuntu <<>> @127.0.0.1 myapp.local
; (1 server found)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12345
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;myapp.local. IN A
;; ANSWER SECTION:
myapp.local. 0 IN A 10.0.0.1
;; Query time: 1 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Wed Jan 17 10:00:00 UTC 2024
;; MSG SIZE rcvd: 59
You can also test external domains. If you don’t specify a forward plugin in your Corefile, CoreDNS will not forward requests to upstream DNS servers. To allow resolution of external domains like google.com, you need to add a forward directive.
Here’s an updated Corefile to forward unknown queries to Google’s public DNS servers:
.:53 {
hosts {
127.0.0.1 localhost
10.0.0.1 myapp.local
10.0.0.2 db.myapp.local
fallthrough
}
forward . 8.8.8.8 8.8.4.4
cache 30
log
errors
prometheus :9153
}
With this change, dig @127.0.0.1 google.com should now successfully resolve. The forward . 8.8.8.8 8.8.4.4 line tells CoreDNS to forward any queries it can’t answer itself to the specified IP addresses.
To apply the changes to your running container, you need to recreate it. Stop the existing container and start a new one:
docker compose down
docker compose up -d
This process ensures your local CoreDNS setup is robust and ready for more complex configurations, like integrating custom DNSSEC validation or specific load balancing plugins.
The most surprising thing about CoreDNS is that its configuration language, the Corefile, is actually a powerful Domain Specific Language (DSL) that can be extended and manipulated programmatically. It’s not just a static config file; it’s a set of directives that orchestrate a chain of plugins, each performing a specific DNS function.
This setup is incredibly useful for developing and testing Kubernetes DNS configurations, as CoreDNS is the de facto standard in Kubernetes. You can replicate scenarios and debug issues locally before committing them to your cluster.
One often overlooked aspect of the Corefile is the order of plugins. The directives are processed sequentially. If a plugin handles a request and returns a result, subsequent plugins in the chain might not even see it. This is why fallthrough is so important when using plugins like hosts or rewrite – it explicitly tells CoreDNS to continue processing the request if the current plugin couldn’t resolve it.
When you eventually want to stop your local CoreDNS server, use:
docker compose down
This will stop and remove the containers defined in your docker-compose.yml file.