A CoreDNS plugin that provides DNS resolution for Docker containers. Drop-in replacement for Joyride (dnsmasq-based Docker DNS).
Using the pre-built image:
docker run -d --name coredns-docker \
-p 54:54/udp -p 54:54/tcp -p 5454:5454 \
-e HOSTIP=192.168.16.61 \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/traefikturkey/joyride:corednsOr build locally with Docker Compose:
export HOSTIP=192.168.16.61
docker compose up -d- Watches Docker for containers with DNS labels
- Serves A records for labeled containers
- Unknown hostnames get no response (timeout) - allows EdgeRouter to query upstream
Add labels to your containers to register DNS names:
services:
myapp:
image: nginx
labels:
- "coredns.host.name=myapp.example.com"Multiple hostnames:
labels:
- "coredns.host.name=app.example.com,api.example.com"Joyride compatibility:
labels:
- "joyride.host.name=myapp.example.com"For hosts that aren't Docker containers (NAS, printers, etc.), add entries to the hosts file:
vi ./etc/joyride/hosts.d/hostsUse standard hosts file format:
192.168.1.10 nas.example.com nas
192.168.1.20 printer.example.com
Static entries take priority over Docker container labels. Changes are picked up automatically (no restart required).
| Variable | Description | Default |
|---|---|---|
HOSTIP |
IP address to return for all container DNS records | Auto-detected (host network) |
DOCKER_SOCKET |
Docker socket path | unix:///var/run/docker.sock |
DNS_UNKNOWN_ACTION |
What to do for unknown hostnames: drop or nxdomain |
drop |
For backward compatibility with existing joyride configurations:
| Legacy Variable | Maps To | Notes |
|---|---|---|
JOYRIDE_ENABLE_SERF |
CLUSTER_ENABLED |
true enables clustering |
JOYRIDE_NXDOMAIN_ENABLED |
DNS_UNKNOWN_ACTION |
true = nxdomain, false = drop |
New variables take precedence if both are set. Deprecation warnings are logged.
docker-cluster {
docker_socket unix:///var/run/docker.sock
host_ip 192.168.16.61
label coredns.host.name
label joyride.host.name
ttl 60
unknown_action drop
}
Multiple CoreDNS nodes can share DNS records using SWIM gossip protocol. Each node watches its local Docker daemon and replicates records to peers.
Using Docker Compose with host networking (recommended for clustering):
# HOSTIP is auto-detected when using host networking
NODE_NAME=node1 docker compose -f docker-compose.host.yml up -dOr manually:
# Node 1 - HOSTIP auto-detected from default route
docker run -d --network host \
-e CLUSTER_ENABLED=true \
-e NODE_NAME=node1 \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/traefikturkey/joyride:coredns
# Node 2 (automatically finds Node 1 via broadcast)
docker run -d --network host \
-e CLUSTER_ENABLED=true \
-e NODE_NAME=node2 \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/traefikturkey/joyride:corednsNote: Host networking enables:
- Automatic HOSTIP detection from the default network interface
- UDP broadcast discovery (nodes find each other automatically)
- No need for
CLUSTER_SEEDSconfiguration
For Docker bridge networks where broadcast doesn't work:
environment:
- CLUSTER_ENABLED=true
- CLUSTER_SEEDS=192.168.16.61:7946,192.168.16.62:7946
- NODE_NAME=node3| Variable | Description | Default |
|---|---|---|
CLUSTER_ENABLED |
Enable clustering | false |
NODE_NAME |
Unique node identifier | Required when clustering |
CLUSTER_PORT |
Memberlist gossip port | 7946 |
CLUSTER_SEEDS |
Comma-separated seed nodes (host:port) | Empty (use broadcast) |
CLUSTER_BIND_ADDR |
Address to bind memberlist | 0.0.0.0 |
DISCOVERY_PORT |
UDP port for broadcast discovery | 8889 |
CLUSTER_SECRET |
Encryption key for cluster traffic | None |
- Each node watches its local Docker daemon for labeled containers
- When a container starts/stops, the node broadcasts the change via SWIM gossip
- All nodes maintain a merged view of all cluster records
- Any node can answer DNS queries for any container in the cluster
# Run 3-node cluster test
make test-clusterConfigure EdgeRouter to forward specific domains to CoreDNS:
configure
set service dns forwarding options "server=/example.com/192.168.16.61#54"
set service dns forwarding options "all-servers"
commit
saveImportant: The all-servers option is required. It queries all DNS servers in parallel. When CoreDNS doesn't know a hostname (e.g., www.example.com pointing to GitHub Pages), it stays silent, and EdgeRouter uses the upstream response.
Without all-servers, EdgeRouter would wait for CoreDNS to respond before trying upstream DNS, causing timeouts for external hostnames.
When running a cluster, add each node:
set service dns forwarding options "server=/example.com/192.168.16.61#54"
set service dns forwarding options "server=/example.com/192.168.16.62#54"
set service dns forwarding options "all-servers"Joyride runs on port 54 to avoid conflicts with local systemd-resolved. It does not forward DNS requests to another server - instead, configure your main DNS server to forward specific domain queries to it.
echo -e "server=/example.com/192.168.1.2#54\nall-servers\nno-negcache" | sudo tee -a /etc/dnsmasq.d/03-custom-dns-names.confReplace:
example.com- your domain192.168.1.2- IP address of the server running Joyride
Then restart dnsmasq:
sudo systemctl restart dnsmasq
# Or for PiHole:
pihole restartdns-
all-servers - Query all available DNS servers simultaneously and use the first response. Without this, dnsmasq waits for Joyride to respond (or timeout) before trying upstream DNS, causing delays for external hostnames.
-
no-negcache - Disable caching of negative results (NXDOMAIN). This prevents dnsmasq from caching "not found" responses, which is important when Joyride containers start/stop dynamically.
See the dnsmasq documentation for all available options.
# Query a container hostname
dig @192.168.16.61 -p 54 myapp.example.com
# Run integration tests
docker compose -f docker-compose.test.yml up --abort-on-container-exitcurl http://192.168.16.61:5454/healthHealth check is on port 5454.
View registered hostnames and configuration:
docker logs coredns-docker-clusterExample output:
docker-cluster: host_ip=192.168.16.61 labels=[coredns.host.name joyride.host.name] ttl=60 unknown_action=drop
docker-cluster: docker_socket=unix:///var/run/docker.sock
docker-cluster: connected to Docker daemon
docker-cluster: synced 3 containers with DNS records
docker-cluster: registered hostnames: [app.example.com api.example.com web.example.com]
If you want CoreDNS to forward unknown queries to upstream DNS (instead of dropping them), use the native forward plugin:
.:54 {
docker-cluster {
host_ip {$HOSTIP}
fallthrough
}
forward . 8.8.8.8 1.1.1.1
errors
health :8080
}
See the commented example in Corefile for details.