<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><script src="https://www.rss.style/js/atom-style.js" xmlns="http://www.w3.org/1999/xhtml"/><title>Tower of Kubes</title><link rel="self" type="application/atom+xml" hreflang="en" href="https://www.towerofkubes.com/tags/dns/feed.xml"/><link rel="alternate" type="application/atom+xml" hreflang="he" href="https://www.towerofkubes.com/he/tags/dns/feed.xml"/><link rel="alternate" type="application/atom+xml" hreflang="x-default" href="https://www.towerofkubes.com/he/tags/dns/feed.xml"/><link rel="alternate" type="text/html" hreflang="en" href="https://www.towerofkubes.com/tags/dns/"/><link rel="alternate" type="application/rss+xml" hreflang="en" href="https://www.towerofkubes.com/tags/dns/index.xml"/><id>/</id><updated>2026-09-17T00:00:00Z</updated><author><name>Ro'i Bandel</name></author><generator>Hugo 0.165.0</generator><entry><title>The Curious Incident of the Missing DNS Records</title><link rel="alternate" type="text/html" hreflang="en" href="https://www.towerofkubes.com/articles/external-dns-incident/"/><id>https://www.towerofkubes.com/articles/external-dns-incident/</id><updated>2026-09-17T00:00:00Z</updated><summary type="html">How a breaking change in external-dns wiped out all my Cloudflare DNS records overnight, how I diagnosed and fixed it in my GitOps homelab, and why Semantic Versioning matters.</summary><content type="html"><![CDATA[<img src="https://www.towerofkubes.com/articles/external-dns-incident/featured_hu_e62c1cc6c84e4416.jpg" /><p>I’ve been running my Kubernetes-based <a href="https://homelab.towerofkubes.com/"  target="_blank" rel="noreferrer"><strong>Homelab as Code</strong></a> for six months. It has been a challenging but rewarding experience. Running an over-engineered homelab can often be more trouble than it’s worth. At the same time, I find myself learning valuable lessons.</p>

<h2 class="relative group">The Incident
    <div id="the-incident" class="anchor"></div>
    
</h2>
<p>Earlier this week, I read about a movie I wanted to watch and decided to add it to my watchlist on <a href="https://dannyvfilms.github.io/floppy/"  target="_blank" rel="noreferrer">Floppy</a>, one of the services that I had <a href="https://github.com/roib20/homelab-as-code/pull/1249"  target="_blank" rel="noreferrer">recently deployed</a> to track media.</p>
<p>When I went to access my domain from my phone, I received the dreaded <code>ERR_CONNECTION_REFUSED</code>. What was going on? It could have been a bunch of different things. Running a Kubernetes homelab quickly teaches you all the fascinating failure modes of deploying enterprise-grade workloads on underpowered mini PCs. Overloaded nodes, connectivity issues, crash loops, and plenty more. At this point, I’ve seen more than I care to admit. However, the issue turned out to be none of the above.</p>
<p>Going over to my laptop, I was surprised to see all services working fine: the cluster was 100% healthy, with all pods running. At this point, if your first guess as to the culprit was DNS, <a href="https://isitdns.com/"  target="_blank" rel="noreferrer">you were correct</a>. Everything was working on my laptop, but not on my phone, thanks to split-horizon DNS, which meant my laptop was connecting directly to the homelab network. My phone, on the other hand, was connecting to my public domain over public DNS.</p>
<p>I manage all of my domains and DNS records through Cloudflare. When I went over to the Cloudflare dashboard, I was alarmed to see that all the DNS records were missing. This explained the issue, but not the cause. What caused the DNS records to be deleted? I checked <a href="https://github.com/roib20/homelab-as-code/tree/main/kubernetes/cluster/active/addons/external-dns"  target="_blank" rel="noreferrer">my external-dns GitOps deployment</a>. I had not changed my declarative DNS records lately, so that couldn’t be the cause, could it? It was then that I remembered <a href="https://github.com/roib20/homelab-as-code/pull/1321"  target="_blank" rel="noreferrer">merging an external-dns chart update the night before</a> (using Renovate).</p>
<figure><img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="auto"
    alt="Missing DNS records for the domain on the Cloudflare dashboard"
    width="1354"
    height="706"
    src="/articles/external-dns-incident/missing-dns-records_hu_89b3b96bf98e9a69.png"
    srcset="/articles/external-dns-incident/missing-dns-records_hu_89b3b96bf98e9a69.png 800w, /articles/external-dns-incident/missing-dns-records_hu_4ef5898d3d6a3819.png 1280w"
    sizes="(min-width: 768px) 50vw, 65vw"
    data-zoom-src="/articles/external-dns-incident/missing-dns-records.png"></figure>

<h2 class="relative group">The Culprit
    <div id="the-culprit" class="anchor"></div>
    
</h2>
<p>Last month, the external-dns project released <a href="https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.22.0"  target="_blank" rel="noreferrer">v0.22.0</a> with this breaking change:</p>

    <div class="admonition quote">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M448 296c0 66.3-53.7 120-120 120l-8 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l8 0c30.9 0 56-25.1 56-56l0-8-64 0c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l64 0c35.3 0 64 28.7 64 64l0 32 0 32 0 72zm-256 0c0 66.3-53.7 120-120 120l-8 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l8 0c30.9 0 56-25.1 56-56l0-8-64 0c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l64 0c35.3 0 64 28.7 64 64l0 32 0 32 0 72z"/></svg>
        <span>Quote</span>
      </div>
      <div class="admonition-content">
        <ul>
<li>
<p>⚠️ The default annotation prefix is now <code>external-dns.kubernetes.io/</code> with no fallback (<a href="https://github.com/kubernetes-sigs/external-dns/pull/6424"  target="_blank" rel="noreferrer">#6424</a>)</p>
<ul>
<li>🚨 This change can <em>delete</em> <strong>all</strong> your DNS records.</li>
</ul>
</li>
</ul>
      </div>
    </div><p>The source of this change was <a href="https://github.com/kubernetes-sigs/external-dns/pull/6424"  target="_blank" rel="noreferrer">a pull request from May</a>, which changed the default prefix for external-dns annotations. The issue is that when external-dns is configured with the <code>sync</code> policy, it can allow the deletion of unmanaged DNS records. When the default annotation prefix changed, any cluster resources that kept the legacy annotations were treated as unmanaged, causing external-dns to delete all DNS records for these resources.</p>

<h2 class="relative group">The Solution
    <div id="the-solution" class="anchor"></div>
    
</h2>
<p><a href="https://github.com/kubernetes-sigs/external-dns/pull/6424"  target="_blank" rel="noreferrer">The external-dns pull request</a> suggested this solution:</p>
<blockquote><p>This change can be harmless for users that needs or want to stay with previous prefix. ExternalDNS can be configured to use it:</p>
<p><code>--annotation-prefix=external-dns.alpha.kubernetes.io/</code></p>
</blockquote><p>While the change was far from harmless, and I believe this could have been communicated better, this solution does work.</p>
<p>An alternative is to refactor all annotations to use the new prefix by replacing <code>external-dns.alpha.kubernetes.io/</code> with <code>external-dns.kubernetes.io/</code>. On a GitOps-managed cluster like my homelab-as-code, this solution is simple once you know what to look for. Indeed, this is the solution I went with: <a href="https://github.com/roib20/homelab-as-code/pull/1341"  target="_blank" rel="noreferrer">fix(dns): migrate ExternalDNS annotation prefix by roib20 · Pull Request #1341 · roib20/homelab-as-code</a>.</p>
<p>Once the pull request was merged, the issue was fully resolved for me: the DNS records were quickly re-provisioned by external-dns connecting to Cloudflare DNS, and access to my domain was restored (after DNS propagation).</p>

<h2 class="relative group">Community Reaction
    <div id="community-reaction" class="anchor"></div>
    
</h2>
<p>Over on <a href="https://github.com/kubernetes-sigs/external-dns/pull/6424"  target="_blank" rel="noreferrer">the external-dns pull request</a>, the community reaction was harsh (but fair). Here are some of the comments:</p>

    <div class="admonition quote">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M448 296c0 66.3-53.7 120-120 120l-8 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l8 0c30.9 0 56-25.1 56-56l0-8-64 0c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l64 0c35.3 0 64 28.7 64 64l0 32 0 32 0 72zm-256 0c0 66.3-53.7 120-120 120l-8 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l8 0c30.9 0 56-25.1 56-56l0-8-64 0c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l64 0c35.3 0 64 28.7 64 64l0 32 0 32 0 72z"/></svg>
        <span>Quote</span>
      </div>
      <div class="admonition-content">
        <p>In hindsight, I think the release that included this change should have been v1.0.0 by this logic and more importantly to draw more attention to the massive breaking change included in this that can wipe/misconfigure DNS, or leak sensitive info and IP addresses via misconfigured DNS entries. If the project wasn’t ready for v1.0.0 then the alpha annotation is (was) still very much necessary and this should not have been merged. Not much we can do now though, except incorporate the lessons learned for the future since the act of retracting the release would probably also cause harm as well.</p>
<hr>
<blockquote><p>how is this a minor? Not cool. ⚠️ The default annotation prefix is now external-dns.kubernetes.io/ with no fallback (<a href="https://github.com/kubernetes-sigs/external-dns/pull/6424"  target="_blank" rel="noreferrer">#6424</a>)</p>
<p>🚨 This change can delete all your DNS records.</p>
</blockquote><p>What a disaster.</p>
<hr>
<p>It seems extremely irresponsible to release this without a safe and easy-to-understand migration path. I’m glad I always read the release notes!</p>
      </div>
    </div><p>The official response was:</p>

    <div class="admonition quote">
      <div class="admonition-header"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M448 296c0 66.3-53.7 120-120 120l-8 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l8 0c30.9 0 56-25.1 56-56l0-8-64 0c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l64 0c35.3 0 64 28.7 64 64l0 32 0 32 0 72zm-256 0c0 66.3-53.7 120-120 120l-8 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l8 0c30.9 0 56-25.1 56-56l0-8-64 0c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l64 0c35.3 0 64 28.7 64 64l0 32 0 32 0 72z"/></svg>
        <span>Quote</span>
      </div>
      <div class="admonition-content">
        <p>We hear you.
Please do not upgrade if you do not have done it already we discuss the best solution to move out of this.
Sorry that it happened, we are all human and can do errors (oh AI can also do errors).
Thanks for your understanding.</p>
      </div>
    </div>
<h2 class="relative group">The Helm Chart Update
    <div id="the-helm-chart-update" class="anchor"></div>
    
</h2>
<p>Three weeks later, <a href="https://github.com/kubernetes-sigs/external-dns/releases#release-external-dns-helm-chart-1.22.0"  target="_blank" rel="noreferrer">external-dns-helm-chart-1.22.0</a> was released. This was the update that deleted my DNS records. The chart update bundled the <a href="https://github.com/kubernetes-sigs/external-dns/releases#release-v0.22.0"  target="_blank" rel="noreferrer">v0.22.0</a> app update. The chart also had its own breaking change, which required setting a policy value that wasn’t mandatory in previous chart versions.</p>

<h2 class="relative group">My Opinion
    <div id="my-opinion" class="anchor"></div>
    
</h2>
<p>In my opinion, the external-dns maintainers should have bumped the major version of both the app and the Helm chart. While it’s always important to read release notes, developers and administrators rely on projects to follow <a href="https://semver.org/"  target="_blank" rel="noreferrer">Semantic Versioning</a> and to bump the major version when there are breaking changes. A caveat is that to this day, nine years after its initial release, the external-dns project is still on major version 0, which is not considered stable and doesn’t offer any guarantees. On the other hand, the Helm chart is on major version 1, which should have been bumped due to the breaking changes.</p>
<p>The external-dns project is part of the <a href="https://github.com/kubernetes-sigs"  target="_blank" rel="noreferrer">Kubernetes SIGs organization</a>. Kubernetes itself has received criticism for the way it follows Semantic Versioning.</p>
<figure><img
    class="my-0 rounded-md"
    loading="lazy"
    decoding="async"
    fetchpriority="auto"
    alt="Kubernetes ‘Semantic’ Versioning Explained"
    width="1200"
    height="630"
    src="/articles/external-dns-incident/k8s-semver_hu_fc18996420b232c7.webp"
    srcset="/articles/external-dns-incident/k8s-semver_hu_fc18996420b232c7.webp 800w, /articles/external-dns-incident/k8s-semver.webp 1280w"
    sizes="(min-width: 768px) 50vw, 65vw"
    data-zoom-src="/articles/external-dns-incident/k8s-semver.webp"></figure>
<p>At the end of the day, I view this as an important learning opportunity. I am glad this happened to me in my homelab and not in production. Rather than deterring me from using external-dns in production, this experience has encouraged me to be extra vigilant about reading release notes (for all projects). I’ve been using external-dns for years. For example, <a href="https://github.com/roib20/terraform-provision-gke-cloudflare/tree/main"  target="_blank" rel="noreferrer">here’s a demo I made four years ago featuring declarative external-dns</a>.</p>
<p>At the end of the day, the most frustrating part was that by the time I finished handling this incident, I had forgotten which movie I wanted to add to Floppy. I blame DNS.</p>
<hr>
<p><em>Featured image by <a href="https://unsplash.com/@choys_?utm_source=hugo&utm_medium=referral"  target="_blank" rel="noreferrer">Conny Schneider</a> on <a href="https://unsplash.com/photos/a-blue-background-with-lines-and-dots-xuTJZ7uD7PI?utm_source=hugo&utm_medium=referral"  target="_blank" rel="noreferrer">Unsplash</a>.</em></p>
]]></content><author><name>Ro'i Bandel</name></author><category term="kubernetes" label="Kubernetes" scheme="https://www.towerofkubes.com/tags/kubernetes/"/><category term="k8s" label="K8s" scheme="https://www.towerofkubes.com/tags/k8s/"/><category term="dns" label="Dns" scheme="https://www.towerofkubes.com/tags/dns/"/><category term="homelab" label="Homelab" scheme="https://www.towerofkubes.com/tags/homelab/"/><category term="cloudflare" label="Cloudflare" scheme="https://www.towerofkubes.com/tags/cloudflare/"/><category term="gitops" label="Gitops" scheme="https://www.towerofkubes.com/tags/gitops/"/><published>2026-09-17T00:00:00Z</published></entry></feed>