ExternalDNS with DNScale on Kubernetes
Quick answer
Install the DNScale webhook alongside ExternalDNS using the official chart. Restrict the controller to selected Kubernetes resources and an explicit DNScale zone, use a stable TXT owner ID, and begin with DNSCALE_DRY_RUN=true in the sidecar. Enable upsert-only writes after review, then sync if removed resources should delete their DNS.
On this page
What you'll learn
- Install the DNScale ExternalDNS webhook with the official Helm chart
- Restrict API access and DNS ownership to a dedicated zone
- Publish Service and Ingress addresses automatically
- Validate dry-run, updates, and deletion policies
The DNScale ExternalDNS webhook lets Kubernetes resources define their public DNS. ExternalDNS watches selected Services and Ingresses, computes the desired records, and asks the DNScale sidecar to apply changes through the API.
The tested baseline is ExternalDNS v0.23.0, the official Helm chart 1.23.0, and Kubernetes 1.35.0. The public provider image supports Linux AMD64 and ARM64.
Choose an ownership boundary
Start with a dedicated delegated zone such as k8s.example.org. The zone must
already exist in DNScale and resolve through its assigned nameservers.
ExternalDNS does not create zones or parent NS delegation.
Create a token with zones:read, records:read, and records:write, scoped to
that zone. DNScale DNS-name restrictions match exact owners, not every name
under a suffix. For applications with changing names, a dedicated zone provides
a practical authorization boundary. Exact-name restrictions must include the
ownership TXT names as well as the application names.
Only one active controller should manage a given name/type record set. Keep Terraform, DNSControl, and manual automation outside that ownership boundary. TXT ownership covers the entire record set; it does not protect individual IP addresses manually added to a controller-owned set.
Install in dry-run mode
Create the namespaces and a Secret from a local credential file:
kubectl create namespace applications
kubectl create namespace external-dns
kubectl -n external-dns create secret generic dnscale-api-token \
--from-file=token=/secure/path/dnscale-api-token
curl -fsSLo values.yaml \
https://raw.githubusercontent.com/dnscaleou/external-dns-webhook-dnscale/v1.0.0/deploy/values.yamlEdit the downloaded values:
- Replace the domain in both
domainFiltersandDNSCALE_DOMAIN_FILTER. - Set
DNSCALE_ZONE_ID_FILTERto the existing zone's UUID. - Choose a stable owner ID in both
txtOwnerIdandDNSCALE_TXT_OWNER_ID. - Set
sourceNamespaceandlabelFilterfor the resources to watch. - Keep
txtPrefix: "edns-%{record_type}.", one replica, and theRecreatedeployment strategy.
The values select LoadBalancer and ExternalName Services plus Ingresses. This avoids cluster-wide Node access while using namespace-scoped Kubernetes RBAC. The API token is mounted only into the webhook container.
helm upgrade --install external-dns external-dns \
--repo https://kubernetes-sigs.github.io/external-dns \
--version 1.23.0 --namespace external-dns \
--values values.yaml --wait
kubectl -n external-dns logs deployment/external-dns -c webhookThe sidecar's DNSCALE_DRY_RUN setting controls webhook dry-run. It defaults
to true. The tested ExternalDNS controller does not send its own --dry-run
flag through the webhook protocol, so that controller flag alone cannot prevent
writes. The webhook validates proposed batches and logs their record-set count
without changing DNS.
Publish a Service or Ingress
For a LoadBalancer Service, add the selected label and hostname annotation:
apiVersion: v1
kind: Service
metadata:
name: web
namespace: applications
labels:
external-dns: enabled
annotations:
external-dns.kubernetes.io/hostname: web.k8s.example.org
external-dns.kubernetes.io/ttl: "300"
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 8080A load-balancer controller must populate the Service's status. ExternalDNS maps IPv4 targets to A records, IPv6 targets to AAAA, and hostname targets to CNAME. An Ingress uses its configured hostnames and the ingress controller's published load-balancer status. See the repository's Ingress example.
The default TTL is 300 seconds. Explicit TTL values must be between 300 and 86400. Multiple IP targets form one record set with a shared TTL.
Enable writes and choose deletion behavior
After reviewing resource selection and DNS scope, change the sidecar's
DNSCALE_DRY_RUN environment value to "false" and set
extraArgs.dry-run: false. Repeat the Helm installation command.
The supplied upsert-only policy creates records and applies target or TTL
changes, while retaining DNS when a Kubernetes resource disappears. The
create-only policy leaves existing records unchanged. With sync, removed
resources cause the controller's data and ownership records to be deleted.
Verify the application record and its ownership TXT:
dig +short web.k8s.example.org A
dig +short edns-a.web.k8s.example.org TXTTest a target change and resource removal in a disposable scope before enabling
sync. Existing unowned or foreign-owned records are not automatically adopted.
For migration, stop the previous writer, export its state, and review ownership
for the complete record set before starting ExternalDNS.
Operate and troubleshoot
The provider re-reads complete API inventory before writes, creates ownership metadata before data, and deletes ownership only after data cleanup. Retried batches use current record IDs. Partial operations return errors and can be reconciled again; they are not a multi-record transaction.
For a stalled reconciliation, check both containers' logs. Authorization errors usually mean an incorrect zone UUID, token scope, or exact-name restriction. Conflicts can indicate foreign ownership, disabled records, or another writer changing the same record set. Exclude delegated subzones outside this controller's scope.
The sidecar exposes health, readiness, and metrics on port 8080, while its mutation API listens only on the Pod's loopback interface. Start with the two-minute reconciliation interval and monitor API usage. Update the Secret and restart the deployment to rotate credentials.
To stop reconciliation, scale the deployment to zero. Preserve DNS and ownership TXT while investigating. A Helm rollback does not undo earlier DNS changes.
Use the cert-manager guide for TLS
certificate validation. Keep its _acme-challenge names separate from
ExternalDNS-owned records. Wildcard DNS names, arbitrary TXT data, advanced
routing, and apex CNAME flattening are outside this provider's first release.
Frequently asked questions
- Does DNScale support ExternalDNS?
- Yes. The DNScale ExternalDNS webhook runs as a sidecar to the upstream controller and manages A, AAAA, CNAME, and ownership TXT records through the DNScale API.
- Which permissions does the API token need?
- Use zones:read, records:read, and records:write, restricted to the existing zone being managed. DNS-name scopes match exact names; if used, include application names and their generated ownership TXT names.
- Does the ExternalDNS dry-run flag prevent webhook writes?
- The tested controller does not forward its dry-run flag through the webhook protocol. Set DNSCALE_DRY_RUN=true in the DNScale sidecar to prevent writes. The supplied values enable this by default.
- Can ExternalDNS and cert-manager share a zone?
- Yes, with distinct record ownership. ExternalDNS manages application records and its edns-prefixed TXT markers; cert-manager manages separate _acme-challenge TXT values. Do not have both controllers write the same record set.
- Are wildcard hostnames supported?
- The first ExternalDNS webhook release does not support wildcard DNS names, routing policies, or apex CNAME flattening. cert-manager wildcard certificate validation is a separate integration.
Put the guide into practice
Manage authoritative DNS records, review changes, and monitor your zones with DNScale.
Start free