Configure Kubernetes External DNS for automatic DNS record management with cloud providers

Intermediate 25 min Apr 16, 2026 260 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up External DNS controller to automatically create and manage DNS records for your Kubernetes services and ingresses. This tutorial covers installation, cloud provider integration, and security configuration for production-ready DNS automation.

Prerequisites

  • Running Kubernetes cluster with admin access
  • Cloud provider account (AWS, GCP, or Azure)
  • kubectl and Helm 3 installed
  • Domain with cloud-managed DNS zones

What this solves

External DNS automatically creates and manages DNS records for your Kubernetes services and ingresses without manual intervention. When you deploy applications with ingress controllers or LoadBalancer services, External DNS watches these resources and automatically creates corresponding DNS entries in your cloud provider's DNS service, eliminating the need for manual DNS management and reducing deployment overhead.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you have the latest package information.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install kubectl and Helm

External DNS is best deployed using Helm charts. Install kubectl for cluster access and Helm for package management.

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

Create External DNS namespace

Create a dedicated namespace for External DNS to organize resources and apply security policies.

kubectl create namespace external-dns

Create service account and RBAC

External DNS needs cluster-wide permissions to watch ingresses, services, and create DNS records. Create the necessary RBAC configuration.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  namespace: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: external-dns
rules:
  • apiGroups: [""]
resources: ["services","endpoints","pods"] verbs: ["get","watch","list"]
  • apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"] verbs: ["get","watch","list"]
  • apiGroups: [""]
resources: ["nodes"] verbs: ["list","watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: external-dns-viewer roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: external-dns subjects:
  • kind: ServiceAccount
name: external-dns namespace: external-dns

Apply RBAC configuration

Apply the RBAC configuration to grant External DNS the necessary permissions.

kubectl apply -f external-dns-rbac.yaml

Configure cloud provider credentials (AWS example)

Create AWS credentials for External DNS to manage Route53 records. Replace with your actual access keys.

apiVersion: v1
kind: Secret
metadata:
  name: aws-credentials
  namespace: external-dns
type: Opaque
data:
  access-key-id: QUtJQVlPVVJBQ0NFU1NLRVK=
  secret-access-key: eW91ci1zZWNyZXQtYWNjZXNzLWtle=

Apply credentials secret

Apply the credentials secret to enable External DNS authentication with your cloud provider.

kubectl apply -f aws-credentials-secret.yaml
Note: For production environments, use IAM roles with IRSA (IAM Roles for Service Accounts) instead of static credentials for better security.

Deploy External DNS with Helm

Add the External DNS Helm repository and install the controller with AWS Route53 configuration.

helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
helm repo update

Create Helm values file

Configure External DNS for your cloud provider and domain. This example uses AWS Route53.

provider: aws
aws:
  zoneType: public
  assumeRoleArn: ""
  batchChangeSize: 1000
  evaluateTargetHealth: true

domainFilters:
  - example.com

zoneIdFilters: []

policy: upsert-only

registry: txt
txtOwnerId: external-dns
txtPrefix: external-dns-

interval: 1m
triggerLoopOnEvent: false

logLevel: info
logFormat: text

metrics:
  enabled: true
  port: 7979

serviceAccount:
  create: false
  name: external-dns

securityContext:
  fsGroup: 65534
  runAsUser: 65534
  runAsNonRoot: true

resources:
  limits:
    cpu: 250m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi

nodeSelector: {}
tolerations: []
affinity: {}

env:
  - name: AWS_ACCESS_KEY_ID
    valueFrom:
      secretKeyRef:
        name: aws-credentials
        key: access-key-id
  - name: AWS_SECRET_ACCESS_KEY
    valueFrom:
      secretKeyRef:
        name: aws-credentials
        key: secret-access-key

Install External DNS

Deploy External DNS using the Helm chart with your custom values.

helm install external-dns external-dns/external-dns \
  --namespace external-dns \
  --values external-dns-values.yaml

Configure for Google Cloud DNS (alternative)

If using Google Cloud DNS instead of AWS, create a service account key and configure different values.

provider: google

google:
  project: your-gcp-project-id
  serviceAccountSecretKey: credentials.json

domainFilters:
  - example.com

policy: upsert-only

registry: txt
txtOwnerId: external-dns

interval: 1m

serviceAccount:
  create: false
  name: external-dns

env:
  - name: GOOGLE_APPLICATION_CREDENTIALS
    value: /etc/secrets/service-account/credentials.json

extraVolumes:
  - name: google-service-account
    secret:
      secretName: external-dns-gcp-sa

extraVolumeMounts:
  - name: google-service-account
    mountPath: /etc/secrets/service-account
    readOnly: true

Configure network policies (optional)

Implement network policies to restrict External DNS network access for enhanced security.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: external-dns-network-policy
  namespace: external-dns
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: external-dns
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: monitoring
    ports:
    - protocol: TCP
      port: 7979
  egress:
  - to: []
    ports:
    - protocol: TCP
      port: 53
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 443

Apply network policies

Apply the network policies to secure External DNS network communications.

kubectl apply -f external-dns-network-policy.yaml

Test with sample ingress

Create a test ingress to verify External DNS automatically creates DNS records.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-app
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    external-dns.alpha.kubernetes.io/hostname: test.example.com
spec:
  tls:
  - hosts:
    - test.example.com
    secretName: test-app-tls
  rules:
  - host: test.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: test-service
            port:
              number: 80

Apply test ingress

Deploy the test ingress and watch External DNS create the corresponding DNS record.

kubectl apply -f test-ingress.yaml

Verify your setup

Check that External DNS is running correctly and processing DNS records.

kubectl get pods -n external-dns
kubectl logs -n external-dns -l app.kubernetes.io/name=external-dns
kubectl get ingress -A
nslookup test.example.com

Monitor External DNS metrics and verify DNS record creation.

kubectl port-forward -n external-dns svc/external-dns 7979:7979
curl http://localhost:7979/metrics
dig test.example.com

Configure for production environments

Enable monitoring integration

Configure External DNS to work with your existing monitoring stack. This integrates with Kubernetes monitoring with Prometheus and Grafana.

metrics:
  enabled: true
  port: 7979
  serviceMonitor:
    enabled: true
    namespace: monitoring
    interval: 30s
    scrapeTimeout: 10s
    labels:
      prometheus: kube-prometheus

podAnnotations:
  prometheus.io/scrape: "true"
  prometheus.io/port: "7979"
  prometheus.io/path: "/metrics"

Configure resource limits and requests

Set appropriate resource limits for production workloads to ensure stability.

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 250m
    memory: 256Mi

podDisruptionBudget:
  enabled: true
  minAvailable: 1

replicaCount: 2

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 100
      podAffinityTerm:
        labelSelector:
          matchExpressions:
          - key: app.kubernetes.io/name
            operator: In
            values:
            - external-dns
        topologyKey: kubernetes.io/hostname

Configure DNS zone filters

Limit External DNS to specific zones for security and prevent accidental DNS modifications.

domainFilters:
  - example.com
  - staging.example.com

zoneIdFilters:
  - Z1D633PJN98FT9  # production zone
  - Z2E744QKM87GH2  # staging zone

policy: sync

annotationFilter: external-dns.alpha.kubernetes.io/exclude notin (true)

txtPrefix: k8s-

Common issues

Symptom Cause Fix
DNS records not created Missing RBAC permissions Verify service account has cluster-wide ingress/service read permissions
Access denied errors Invalid cloud credentials Check AWS credentials or IAM role permissions for Route53
External DNS pod crash looping Invalid domain filters or zone configuration Check logs with kubectl logs -n external-dns -l app.kubernetes.io/name=external-dns
DNS records created but not resolving TTL propagation delay Wait 5-10 minutes for DNS propagation, check with dig +trace hostname
Multiple DNS records for same hostname Conflicting ingress annotations Use external-dns.alpha.kubernetes.io/hostname annotation consistently
External DNS not watching ingresses Incorrect annotation filter Remove or adjust annotation filters in External DNS configuration

Security best practices

Warning: Never use overly permissive IAM policies. Grant External DNS only the minimum permissions needed for DNS record management in specified zones.

For production deployments, consider implementing Pod Security Standards and admission controllers to enforce security policies across your cluster.

Create minimal IAM policy for AWS

Create a restrictive IAM policy that only allows DNS operations on specific hosted zones.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/Z1D633PJN98FT9"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "route53:ListHostedZones",
                "route53:ListResourceRecordSets"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.