ExternalDNS
ExternalDNS watches Services (type LoadBalancer) and Ingresses and creates/updates DNS records automatically in your provider (Route53, Cloud DNS, Azure DNS, etc.). Annotate resources with external-dns.alpha.kubernetes.io/hostname to control which hostnames it manages.
How It Works
ExternalDNS reads hostnames from Service annotations, Ingress rules, and Gateway API HTTPRoutes, then creates matching DNS A/CNAME records in your cloud DNS provider — eliminating manual DNS entry management.
Install with Helm (Route53)
Use IRSA (for EKS) or a service account with Route53 permissions. The --domain-filter restricts ExternalDNS to only manage records under your owned zones.
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
helm repo update
helm upgrade --install external-dns external-dns/external-dns \
--namespace external-dns \
--create-namespace \
--set provider=aws \
--set aws.region=us-east-1 \
--set domainFilters[0]=example.com \
--set policy=upsert-only \ # safe: only create/update, never delete
--set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::123:role/external-dns-role
# Required IAM policy for Route53
# {
# "Version": "2012-10-17",
# "Statement": [
# {"Effect": "Allow", "Action": ["route53:ChangeResourceRecordSets"], "Resource": ["arn:aws:route53:::hostedzone/*"]},
# {"Effect": "Allow", "Action": ["route53:ListHostedZones","route53:ListResourceRecordSets","route53:ListTagsForResource"], "Resource": ["*"]}
# ]
# }Annotating Resources
Control ExternalDNS by annotating Services and Ingresses. Explicit hostname annotations take precedence over hostnames inferred from Ingress rules.
# Service: creates an A record pointing to the LB IP/hostname
apiVersion: v1
kind: Service
metadata:
name: myapp
annotations:
external-dns.alpha.kubernetes.io/hostname: myapp.example.com
external-dns.alpha.kubernetes.io/ttl: "300"
spec:
type: LoadBalancer
selector:
app: myapp
ports:
- port: 443
---
# Ingress: ExternalDNS reads hostnames from spec.rules automatically
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
external-dns.alpha.kubernetes.io/ttl: "120"
spec:
rules:
- host: myapp.example.com # ExternalDNS picks this up automatically
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80Troubleshooting
# Check ExternalDNS logs for sync activity
kubectl logs -n external-dns -l app.kubernetes.io/name=external-dns -f
# Check what sources ExternalDNS found
kubectl logs -n external-dns -l app.kubernetes.io/name=external-dns | grep "Adding\|Removing\|Updating"
# Verify the DNS record was created
dig myapp.example.com
nslookup myapp.example.com 8.8.8.8
# Check IAM permissions (EKS/IRSA)
kubectl exec -n external-dns deploy/external-dns -- \
aws sts get-caller-identity
# Dry-run mode: see what records would be changed without applying
# Set --dry-run flag in the Helm values to preview changes