Skip to main content

Automate DNS records with external-dns

Inspect 1.36

external-dns synchronizes DNS records with your Kubernetes cluster. Whenever a Service or Ingress is provisioned with a LoadBalancer address, external-dns creates or updates the corresponding DNS records at your DNS provider eliminating manual record maintenance when IPs change. It is not part of the core platform; you install and manage it as a standard cluster workload.

Read this guide if you are:

Install it with your provider

To install external-dns, add the official Helm chart repository and create a Kubernetes Secret containing your DNS provider credentials. Ensure your provider token or IAM policy is strictly scoped to only the specific DNS zones managed by this cluster.

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

Store a scoped Cloudflare API token, then install pointed at the cloudflare provider:

		$ kubectl create namespace external-dns
$ kubectl create secret generic cloudflare-api-token -n external-dns \
  --from-literal=token=<CF_API_TOKEN>
$ helm install external-dns external-dns/external-dns \
  --namespace external-dns \
  --set provider=cloudflare \
  --set 'env[0].name=CF_API_TOKEN' \
  --set 'env[0].valueFrom.secretKeyRef.name=cloudflare-api-token' \
  --set 'env[0].valueFrom.secretKeyRef.key=token'
	

Create the token with edit rights on only the DNS zones you delegate to it.

Annotate what to publish

external-dns reads a hostname annotation from your Services and Ingresses and publishes a record for it:

yaml
		metadata:
  annotations:
    external-dns.alpha.kubernetes.io/hostname: app.example.com
	

For an Ingress, external-dns can also read the host rules directly, so the annotation is optional there.

Publish A and AAAA together

Every Hetzner load balancer receives a public IPv4 and IPv6 address, so publish both an A and an AAAA record to serve IPv6 clients. external-dns creates both when it sees both addresses in status.loadBalancer.ingress. See for why the IPv6 side needs no change inside the cluster.

Ownership and multi-tenant safety

To track records and prevent accidental overwrites, external-dns creates a companion TXT registry record alongside every DNS entry it provisions. The record includes a configurable owner identifier (--txt-owner-id), ensuring controllers in different clusters or environments only modify their own entries. Always assign a unique owner ID per cluster.

In multi-tenant or managing multiple customer domains, restrict each external-dns deployment with --domain-filter to permit modifications only on explicit zones. Deploying isolated controller instances per tenant prevents cross-tenant record tampering and ensures clean administrative boundaries.

Run several clusters against one zone

When a production and a staging cluster both publish into example.com zone, each external-dns has to tell its own records apart from its neighbor's. The owner id decides which records a cluster is willing to touch, and the TXT prefix keeps their registry records from colliding on the same name. Owner id alone carries you only as long as no cluster ever contends for a hostname another one already publishes, so set both before the first install. The chart exposes them as values:

Cluster txtOwnerId txtPrefix domainFilters
Production prod-eu prod- example.com
Staging staging-eu staging- staging.example.com
values-prod.yamlyaml
		txtOwnerId: prod-eu
txtPrefix: prod-
domainFilters:
  - example.com
policy: sync
	

Where the topology allows it, give each cluster its own subdomain and filter on that, as the staging row does. The clusters then cannot contend for a record name at all, and the owner id becomes a second line of defense rather than the only one. If the zone also holds records external-dns did not create, set policy: upsert-only so it never deletes anything.

Note

external-dns reconciles on an interval and every provider rate-limits its API. If records lag behind a change, that reflects the interval, not a failure. Do not set the interval so low that you hit the provider's rate limit.

Where to go next