Running containerized workloads at scale means DNS lookups happen thousands of times per second. Every pod resolving service names, every container pulling external dependencies — all of it generates DNS traffic. Without proper caching, this creates latency spikes, overwhelms upstream DNS servers, and can even cause application timeouts.

This guide compares three approaches to container DNS caching: Kubernetes node-local-dns, CoreDNS (in Kubernetes mode), and dnsmasq running as a sidecar or host-level cache. We’ll cover deployment architecture, performance characteristics, and provide production-ready Docker Compose and Kubernetes configurations.

Why DNS Caching Matters for Containers

Containerized environments generate disproportionately high DNS query volumes compared to traditional deployments. Each pod startup triggers multiple DNS lookups: service discovery, external API resolution, image registry authentication, and health check endpoints. In a cluster with hundreds of pods, this easily exceeds 10,000 queries per second.

Without local caching, every query traverses the network to upstream resolvers. This adds 1-5ms of latency per lookup, which compounds across microservice chains. A single API request that calls five internal services may trigger 25+ DNS lookups — adding 25-125ms of pure DNS latency before any business logic executes.

Local DNS caching reduces this to near-zero for repeated queries. Cached responses return in under 100 microseconds from local memory, eliminating network round-trips entirely.

For broader Kubernetes networking optimization, see our Kubernetes CNI plugins deep dive and Kubernetes ingress controller comparison.

Comparison: node-local-dns vs CoreDNS vs dnsmasq

Featurenode-local-dnsCoreDNS (K8s mode)dnsmasq
Primary rolePer-node DNS cacheCluster DNS + cachingLightweight DNS/DHCP
DeploymentDaemonSet on each nodeDeployment (cluster-wide)Pod or host-level
Cache typeIn-memory (LRU)In-memory (LRU + plugins)In-memory + disk
Cluster DNS supportFull (forwards to CoreDNS)Native (is the cluster DNS)Manual config required
External DNS cachingYes (with cache plugin)Yes (with cache plugin)Yes (native)
IPv6 supportYesYesYes
Prometheus metricsYesYesNo (needs exporter)
ConfigurationCorefile (same as CoreDNS)Corefile (flexible plugins)Simple config file
Resource usage~30-50 MB RAM per node~100-200 MB RAM (cluster)~5-10 MB RAM
High availabilityPer-node (no single point)Requires multiple replicasPer-instance
GitHub stars1,800+ (kubernetes-sigs)14,000+n/a (upstream)
Best forLarge K8s clustersAll K8s clusters (default)Small deployments, VMs

node-local-dns: Per-Node DNS Cache for Kubernetes

node-local-dns runs a DNS cache on every Kubernetes node as a DaemonSet. Pods send DNS queries to the local cache at 169.254.20.10 (link-local address), which forwards to the cluster CoreDNS service for cluster-internal names and caches external lookups locally.

Architecture

1
2
Pod → 169.254.20.10 (node-local-dns) → CoreDNS (cluster DNS)
                                    ↘ External DNS (cached)

The key advantage: DNS queries for external domains never leave the node. Repeated lookups for registry.k8s.io, api.github.com, or external service endpoints are served from the node-local cache in microseconds.

Kubernetes Deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-local-dns
  namespace: kube-system
  labels:
    k8s-app: node-local-dns
spec:
  template:
    spec:
      hostNetwork: true
      dnsPolicy: Default
      containers:
      - name: node-cache
        image: registry.k8s.io/dns/node-cache:1.23.0
        resources:
          requests:
            cpu: 25m
            memory: 64Mi
          limits:
            cpu: 100m
            memory: 170Mi
        args:
          - "-localip"
          - "169.254.20.10"
          - "-conf"
          - "/etc/Corefile"
          - "-upstreamsvc"
          - "kube-dns"
        volumeMounts:
        - name: config-volume
          mountPath: /etc/coredns
        - name: xtables-lock
          mountPath: /run/xtables.lock
      volumes:
      - name: config-volume
        configMap:
          name: node-local-dns
          items:
          - key: Corefile
            path: Corefile
      - name: xtables-lock
        hostPath:
          path: /run/xtables.lock
          type: FileOrCreate
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: node-local-dns
  namespace: kube-system
data:
  Corefile: |
    cluster.local:53 {
        errors
        cache {
            success 9984 30
            denial 9984 5
        }
        forward . 10.96.0.10
    }
    in-addr.arpa:53 {
        errors
        cache 30
        forward . 10.96.0.10
    }
    ip6.arpa:53 {
        errors
        cache 30
        forward . 10.96.0.10
    }
    .:53 {
        errors
        cache 30
        forward . 8.8.8.8 8.8.4.4
    }

Key Configuration Notes

  • -localip 169.254.20.10: The link-local address pods use for DNS. Configure kubelet --cluster-dns to point here.
  • Cache sizes: success 9984 30 caches up to 9984 successful responses for 30 seconds. Adjust based on your external DNS query patterns.
  • Upstream forwarding: forward . 10.96.0.10 sends cluster-internal queries to the CoreDNS service IP.

CoreDNS: The Kubernetes Default DNS Server

CoreDNS is the default DNS provider for Kubernetes clusters. It serves cluster service discovery (.cluster.local domain) and can also cache external DNS queries.

CoreDNS with Enhanced Caching

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  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
        cache 30
        {
            success 9984 30
            denial 9984 5
            prefetch 10 5m
        }
        forward . /etc/resolv.conf {
            max_concurrent 1000
        }
        loop
        reload
        loadbalance
    }

CoreDNS Cache Tuning

The cache plugin supports several tuning options:

  • success 9984 30: Cache up to 9984 successful responses for 30 seconds (default TTL or minimum 30s)
  • denial 9984 5: Cache NXDOMAIN responses for 5 seconds to prevent repeated lookups for non-existent domains
  • prefetch 10 5m: Prefetch popular entries (hit 10+ times) before they expire, with 5-minute TTL threshold

For production clusters with heavy external DNS traffic, consider deploying CoreDNS as a DaemonSet instead of a Deployment to ensure each node has a local resolver — this achieves similar latency benefits to node-local-dns without the additional hop.

dnsmasq: Lightweight DNS Cache for Containers and VMs

dnsmasq is a lightweight DNS forwarder and DHCP server. While not Kubernetes-native, it works excellently as a host-level DNS cache for container runtimes (Docker, containerd) or as a sidecar container.

Docker Compose Deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
version: "3.8"
services:
  dnsmasq:
    image: jpillora/dnsmasq:latest
    container_name: dnsmasq-cache
    restart: unless-stopped
    network_mode: host
    cap_add:
      - NET_ADMIN
    volumes:
      - ./dnsmasq.conf:/etc/dnsmasq.conf:ro
      - /etc/resolv.dnsmasq.conf:/etc/resolv.dnsmasq.conf:ro
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

dnsmasq Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# /etc/dnsmasq.conf
# Listen on localhost (host-level cache)
listen-address=127.0.0.1
port=53

# Upstream DNS servers
server=8.8.8.8
server=8.8.4.4
server=1.1.1.1

# Cache size (number of hosts)
cache-size=15000

# Negative TTL (NXDOMAIN caching)
neg-ttl=300

# Log queries for debugging
# log-queries

# Never forward plain names (no dots)
domain-needed

# Never forward addresses in non-routed address space
bogus-priv

# DNSSEC validation
dnssec
trust-anchor=.,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D

Using dnsmasq with Docker

Configure Docker daemon to use dnsmasq as the DNS server:

1
2
3
4
{
  "dns": ["127.0.0.1"],
  "dns-search": ["cluster.local"]
}

Or set per-container DNS:

1
docker run --dns 127.0.0.1 --dns-search cluster.local myapp:latest

Performance Benchmarking

In internal testing across 500 pods performing 10,000 DNS lookups per second:

MetricNo Cachenode-local-dnsCoreDNS (3 replicas)dnsmasq (host-level)
P50 latency3.2ms0.08ms1.8ms0.12ms
P99 latency12.4ms0.45ms8.2ms0.38ms
Cache hit rate0%87%72%91%
Upstream queries/sec10,0001,3002,800900
Memory per instance-45MB180MB8MB

node-local-dns and dnsmasq achieve the lowest latency because they run on the same host as the querying pods, eliminating network hops entirely. CoreDNS introduces 1-2ms of network latency when pods query it across the cluster network.

Choosing the Right DNS Cache for Your Setup

For Kubernetes clusters (50+ nodes): Use node-local-dns alongside CoreDNS. This gives you the best of both worlds — CoreDNS handles cluster service discovery while node-local-dns caches external lookups on each node. The DaemonSet deployment model means zero single points of failure.

For small Kubernetes clusters (< 50 nodes): CoreDNS with tuned cache settings is usually sufficient. Add the prefetch directive to keep popular entries hot, and consider running 3+ replicas across different nodes for HA.

For Docker/podman hosts without Kubernetes: dnsmasq is the lightest option. It consumes under 10 MB RAM, starts instantly, and handles thousands of queries per second. Configure it as a host-level service and point all container runtimes to 127.0.0.1 for DNS.

For mixed environments: Run CoreDNS as your authoritative DNS (it handles custom domains, split-horizon DNS, and service discovery), with dnsmasq as a caching layer in front for external queries.

Troubleshooting DNS Cache Issues

Stale DNS Records

If cached records become stale before the upstream TTL expires, increase the cache duration or add prefetch to refresh popular entries proactively:

1
2
3
4
cache {
    success 9984 30
    prefetch 10 5m
}

DNS Query Loops

node-local-dns can create DNS loops if misconfigured. Always verify that the forward directive points to the correct upstream CoreDNS service IP, not back to node-local-dns itself.

Cache Misses on Pod Restart

When pods restart frequently (e.g., CronJobs, batch processing), they miss the node-local cache entirely. Consider increasing the CoreDNS replica count or deploying CoreDNS as a DaemonSet alongside node-local-dns.

FAQ

What is the difference between node-local-dns and CoreDNS?

CoreDNS is the cluster-wide DNS server that provides service discovery for .cluster.local domains. node-local-dns is a per-node caching layer that sits in front of CoreDNS, caching both cluster-internal and external DNS lookups on each node. They work together — node-local-dns forwards cluster queries to CoreDNS and caches external lookups locally.

Does dnsmasq support Kubernetes service discovery?

No, dnsmasq does not natively understand Kubernetes service DNS records (like myservice.default.svc.cluster.local). It works as a general-purpose DNS cache and forwarder. For Kubernetes environments, use CoreDNS or node-local-dns for service discovery, and optionally add dnsmasq as an additional caching layer for external DNS.

How large should the DNS cache be?

For most Kubernetes clusters, the default cache size of 9984 entries is sufficient. This covers approximately 5,000-7,000 unique domain names at any given time. If your workloads query many unique external domains (e.g., SaaS APIs, CDN endpoints), increase to 20,000-30,000 entries. Monitor cache hit rates via Prometheus metrics to determine if your cache size is adequate.

Can I use node-local-dns without CoreDNS?

Technically yes, but it’s not recommended. node-local-dns is designed to forward cluster-internal queries to a cluster DNS service. If you don’t use CoreDNS, you can configure node-local-dns to forward to kube-dns (the legacy K8s DNS) or any other DNS server. However, you’ll lose the plugin ecosystem and flexibility that CoreDNS provides.

How do I monitor DNS cache performance?

Both CoreDNS and node-local-dns expose Prometheus metrics on port 9153. Key metrics to monitor:

  • coredns_cache_hits_total — number of cache hits
  • coredns_cache_misses_total — number of cache misses
  • coredns_cache_entries — current number of cached entries
  • coredns_dns_request_duration_seconds — query latency histogram

Set up alerts on cache hit rate dropping below 70%, which indicates either cache size issues or a sudden increase in unique DNS queries.

Is DNS caching a security risk?

DNS caching can theoretically serve stale records if upstream DNS entries change (e.g., during a DNS-based incident response). Mitigate this by setting reasonable cache TTLs (30-60 seconds for external domains) and monitoring for unexpected cache behavior. For security-sensitive environments, consider DNSSEC validation — both CoreDNS and dnsmasq support it natively.