Email for your domain, with DNS setup handled. PostScale

    Sign Up

    ExternalDNS with DNScale on Kubernetes

    4 min readIntermediate

    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.

    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.yaml

    Edit the downloaded values:

    • Replace the domain in both domainFilters and DNSCALE_DOMAIN_FILTER.
    • Set DNSCALE_ZONE_ID_FILTER to the existing zone's UUID.
    • Choose a stable owner ID in both txtOwnerId and DNSCALE_TXT_OWNER_ID.
    • Set sourceNamespace and labelFilter for the resources to watch.
    • Keep txtPrefix: "edns-%{record_type}.", one replica, and the Recreate deployment 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 webhook

    The 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: 8080

    A 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 TXT

    Test 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