Networking intermediate ⏱ 15 minutes K8s 1.28+

Kubernetes DNS Configuration: CoreDNS and dnsPolicy

Configure Kubernetes DNS: service/pod record formats, dnsPolicy and dnsConfig, CoreDNS forwarding, stub domains, static hosts, ndots, and NodeLocal DNSCache.

By Luca Berton • • 📖 7 min read

💡 Quick Answer: Services resolve via <service>.<namespace>.svc.cluster.local. Configure pod DNS with dnsPolicy (ClusterFirst, Default, None) and dnsConfig for custom nameservers/searches. Edit CoreDNS ConfigMap for cluster-wide changes like stub domains or upstream forwarders.

Key debug: kubectl run test --rm -it --image=busybox -- nslookup kubernetes.default.svc.cluster.local

Gotcha: Reduce ndots from default 5 to 2 for external-heavy workloads—fewer DNS queries for external domains.

Kubernetes uses CoreDNS for service discovery and name resolution. Understanding DNS configuration is essential for debugging connectivity issues and customizing name resolution.

How Kubernetes DNS Works

# Service DNS format
<service-name>.<namespace>.svc.cluster.local

# Pod DNS format
<pod-ip-with-dashes>.<namespace>.pod.cluster.local

# Examples:
# my-service.default.svc.cluster.local
# 10-244-1-5.default.pod.cluster.local
# Same namespace — short name works
curl http://api-service/endpoint

# Cross namespace — append the namespace
curl http://api-service.production/endpoint

# Fully qualified
curl http://api-service.production.svc.cluster.local/endpoint

# SRV record for a named port (_<port-name>._<proto>)
dig _http._tcp.api-service.default.svc.cluster.local SRV
graph TD
    A[Pod: curl api-service] --> B[/etc/resolv.conf]
    B --> C[CoreDNS: 10.96.0.10]
    C -->|cluster.local| D[Return ClusterIP]
    C -->|external| E[Forward to upstream DNS]

Check DNS Configuration

# View CoreDNS pods
kubectl get pods -n kube-system -l k8s-app=kube-dns

# View CoreDNS service
kubectl get svc -n kube-system kube-dns

# Check CoreDNS config
kubectl get configmap coredns -n kube-system -o yaml

Default CoreDNS Configuration

# coredns-configmap.yaml
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
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }

Custom DNS Configuration

Add Custom DNS Entries

Note: A separate coredns-custom ConfigMap is only read on distributions whose Corefile imports it (AKS, k3s/RKE2, some managed offerings). On kubeadm/vanilla clusters put server blocks directly in the coredns ConfigMap’s Corefile. On OpenShift use oc edit dns.operator/default (spec.servers).

# coredns-custom.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns-custom
  namespace: kube-system
data:
  custom.server: |
    example.com:53 {
        forward . 10.0.0.10 10.0.0.11
    }
    mycompany.local:53 {
        forward . 192.168.1.1
    }

Add Static Hosts

# coredns-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        ready
        hosts {
           10.0.0.100 legacy-server.example.com
           10.0.0.101 database.internal
           fallthrough
        }
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
        }
        forward . /etc/resolv.conf
        cache 30
        reload
    }

Pod DNS Policies

ClusterFirst (Default)

# cluster-first.yaml
apiVersion: v1
kind: Pod
metadata:
  name: cluster-first-pod
spec:
  dnsPolicy: ClusterFirst  # Default - use cluster DNS
  containers:
    - name: app
      image: nginx

ClusterFirstWithHostNet

# host-network-dns.yaml
apiVersion: v1
kind: Pod
metadata:
  name: host-network-pod
spec:
  hostNetwork: true
  dnsPolicy: ClusterFirstWithHostNet  # Required for hostNetwork pods
  containers:
    - name: app
      image: nginx

Default (Use Node’s DNS)

# default-dns.yaml
apiVersion: v1
kind: Pod
metadata:
  name: default-dns-pod
spec:
  dnsPolicy: Default  # Use node's /etc/resolv.conf
  containers:
    - name: app
      image: nginx

None (Custom DNS)

# custom-dns.yaml
apiVersion: v1
kind: Pod
metadata:
  name: custom-dns-pod
spec:
  dnsPolicy: None
  dnsConfig:
    nameservers:
      - 8.8.8.8
      - 8.8.4.4
    searches:
      - my-namespace.svc.cluster.local
      - svc.cluster.local
      - cluster.local
    options:
      - name: ndots
        value: "5"
      - name: timeout
        value: "3"
  containers:
    - name: app
      image: nginx

Custom DNS Config (dnsConfig)

# pod-with-dns-config.yaml
apiVersion: v1
kind: Pod
metadata:
  name: custom-resolved-pod
spec:
  dnsPolicy: ClusterFirst
  dnsConfig:
    nameservers:
      - 1.1.1.1  # Additional nameserver
    searches:
      - my-custom.domain
    options:
      - name: ndots
        value: "2"
      - name: edns0
  containers:
    - name: app
      image: nginx

External DNS Resolution

# forward-external.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
        }
        # Forward external queries to public DNS
        forward . 8.8.8.8 8.8.4.4 {
           max_concurrent 1000
        }
        cache 30
        reload
    }

Stub Domains for Private Zones

# stub-domains.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough
        }
        forward . /etc/resolv.conf
        cache 30
        reload
    }
    # Stub domain for corporate DNS
    corp.example.com:53 {
        forward . 10.150.0.1
        cache 30
    }
    # Stub domain for AWS private zones
    aws.internal:53 {
        forward . 169.254.169.253
        cache 30
    }

Headless Service DNS

# headless-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-headless
spec:
  clusterIP: None
  selector:
    app: my-app
  ports:
    - port: 80
# Headless service returns individual pod IPs
nslookup my-headless.default.svc.cluster.local
# Returns:
# 10.244.1.5
# 10.244.2.6
# 10.244.3.7

# StatefulSet pod DNS
# pod-0.my-headless.default.svc.cluster.local
# pod-1.my-headless.default.svc.cluster.local

Debug DNS Issues

# Test DNS from a pod
kubectl run dnsutils --image=registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3 --rm -it --restart=Never -- bash
# or: kubectl run dns-debug --rm -it --image=nicolaka/netshoot -- bash

# Inside pod:
nslookup kubernetes.default
nslookup my-service.my-namespace
dig my-service.my-namespace.svc.cluster.local
dig @10.96.0.10 my-service.my-namespace.svc.cluster.local   # query CoreDNS directly
cat /etc/resolv.conf

# Check CoreDNS logs
kubectl logs -n kube-system -l k8s-app=kube-dns

# Check CoreDNS metrics
kubectl port-forward -n kube-system svc/kube-dns 9153:9153
curl localhost:9153/metrics

Common DNS Issues

ndots Configuration

# Default ndots=5 causes many DNS queries for external names
# Reduce for performance with external services

spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"  # Reduce from default 5

DNS Caching

# Increase cache for frequently resolved names
data:
  Corefile: |
    .:53 {
        cache {
           success 9984 300  # Cache successful responses for 5 min
           denial 9984 30    # Cache NXDOMAIN for 30 sec
        }
        # ... rest of config
    }

High DNS Query Volume

# Add more CoreDNS replicas
kubectl scale deployment coredns -n kube-system --replicas=3

# Or use NodeLocal DNSCache
# Creates a DNS cache on each node

NodeLocal DNS Cache

# nodelocaldns.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-local-dns
  namespace: kube-system
spec:
  selector:
    matchLabels:
      k8s-app: node-local-dns
  template:
    metadata:
      labels:
        k8s-app: node-local-dns
    spec:
      hostNetwork: true
      dnsPolicy: Default
      containers:
        - name: node-cache
          image: registry.k8s.io/dns/k8s-dns-node-cache:1.22.20
          args:
            - -localip
            - "169.254.20.10"
            - -conf
            - /etc/Corefile
          ports:
            - containerPort: 53
              name: dns
              protocol: UDP
            - containerPort: 53
              name: dns-tcp
              protocol: TCP

ExternalName Service

# external-name.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-database
spec:
  type: ExternalName
  externalName: database.example.com
# Queries for my-database.default.svc.cluster.local
# return CNAME database.example.com

View Pod DNS Configuration

# Check resolv.conf in pod
kubectl exec my-pod -- cat /etc/resolv.conf

# Expected output:
# nameserver 10.96.0.10
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5

Frequently Asked Questions

What is the DNS name of a Kubernetes Service?

<service>.<namespace>.svc.cluster.local. Within the same namespace the short name <service> works; from another namespace use <service>.<namespace>. Headless StatefulSet pods get <pod>.<service>.<namespace>.svc.cluster.local.

What is the difference between dnsPolicy ClusterFirst and Default?

ClusterFirst (the default) sends queries to CoreDNS, which answers cluster names and forwards the rest. Default makes the pod inherit the node’s /etc/resolv.conf and cannot resolve Services. hostNetwork pods need ClusterFirstWithHostNet to use cluster DNS.

How do I forward a domain to a corporate DNS server?

Add a server block such as corp.example.com:53 { forward . 10.150.0.1 } to the CoreDNS Corefile (or spec.servers in the OpenShift DNS Operator). The reload plugin applies it within about 30 seconds.

Why is DNS slow in my cluster?

Usually ndots:5 search-domain expansion for external names, an under-resourced CoreDNS, or conntrack races on UDP. Set ndots: 2 for external-heavy pods, scale CoreDNS or deploy NodeLocal DNSCache. See CoreDNS troubleshooting.

Summary

Kubernetes DNS via CoreDNS enables service discovery within the cluster. Use DNS policies to control how pods resolve names, dnsConfig for custom settings, and CoreDNS ConfigMap for cluster-wide customization. For performance, consider reducing ndots, enabling NodeLocal DNS Cache, and scaling CoreDNS replicas. Use stub domains to integrate with existing corporate DNS infrastructure.


📘 Go Further with Kubernetes Recipes

Love this recipe? There’s so much more! This is just one of 100+ hands-on recipes in our comprehensive Kubernetes Recipes book.

Inside the book, you’ll master:

  • ✅ Production-ready deployment strategies
  • ✅ Advanced networking and security patterns
  • ✅ Observability, monitoring, and troubleshooting
  • ✅ Real-world best practices from industry experts

“The practical, recipe-based approach made complex Kubernetes concepts finally click for me.”

👉 Get Your Copy Now — Start building production-grade Kubernetes skills today!

#dns #coredns #networking #service-discovery #resolution
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens