Ilija Matoski

    ← Articles

    A name should say what something is, not where it runs

    I counted seven naming shapes on my own network for the same kind of thing, and one of the suffixes meant two different backends depending on which name you picked.

    So I took a look at the shapes of the names on my own network, and there were seven of them, for the same kind of thing.

    Shape Example Count
    bare immich.ig3.c.example.com 39
    .svc komga.svc.ig3.c.example.com 5
    .container navidrome.container.ig3.c.example.com 3
    dashed karakeep-ig3-c.example.com 4
    apex n8n.example.com 6
    nested console.minio.ig3.c.example.com 3
    wildcard *.svc, *.s3, *.web-s3 4

    Every one of those was the right decision on the day it was made, and together they mean you can’t look at a name and know anything about it.

    .svc doesn’t mean what it says

    All five explicit .svc records are CNAMEs to boxy, a single machine, while the *.svc wildcard sends everything else to the Kubernetes ingress.

    So the suffix predicts nothing, and the only way to find out which one a name is is to resolve it.

    navidrome exists three times in three shapes, komga twice, and ha is Home Assistant in one zone and Proxmox in another, which is the same word, two machines, one label of context apart.

    The wildcard hides the dead

    awx, moodist and nc all resolve to the ingress and all return 404, because awx’s real ingress serves a different host and the other two have no ingress anywhere.

    Nothing ever failed loudly, because *.svc and a handful of bare CNAMEs answer for names no backend serves.

    A decommissioned app should return NXDOMAIN, not a 404 from whatever the wildcard happens to point at.

    Where the dashes came from

    The dashed names look arbitrary and they weren’t.

    Cloudflare’s Universal SSL issues one certificate carrying example.com and *.example.com, and a wildcard covers exactly one label, so karakeep.ig3.c.example.com would have been served an invalid certificate.

    karakeep-ig3-c.example.com collapses the same identity into a single label that the wildcard does cover, which was the correct workaround for the constraint that existed.

    The rules

    A name says what something is, never where it runs. No .container meaning Proxmox, no -k8s, no .svc meaning Kubernetes, because services move between machines, so we change the record and not the name.

    Two tiers, both four labels. Three issuers hold a certificate here, and a level missing from one of them is a browser warning on exactly the path that uses it, so depth is capped.

    <name>.svc.example.com a service, anything that serves requests
    <name>.host.example.com a machine, anything that runs things

    host covers bare metal, VMs, LXC, nspawn and a Pi, because vm would encode an implementation that changes, and the tiers retire .container on their own, since the real collisions are where the service and the machine under it both need a name:

    komga.svc.example.com the service you browse
    komga.host.example.com the LXC it runs on

    One canonical FQDN per service, and anything else is a CNAME to it with the date it can be deleted.

    The same name inside and out, because split horizon already gives the LAN answer internally and the public one externally.

    No wildcards. A new app needs a DNS record and not just an Ingress manifest, and what that buys is that a typo and a dead app both return NXDOMAIN.

    The zone goes away and nothing replaces it

    ig3 is a street address, I don’t remember what the .c was for, and both of them sit inside every name on the network.

    I tried twice to replace it, first with a site role like hq, then with a neutral namespace like lab, and both are wrong for the reason the first rule gives.

    A site label encodes where something runs, and it stops being true the moment a service moves to a VPS.

    So we need to remove the zone, and nothing takes its place:

    mealie.svc.ig3.c.example.com -> mealie.svc.example.com
    boxy.ig3.c.example.com -> boxy.host.example.com

    Do it in one pass

    In my case this touches every proxy host, every Ingress, every certificate SAN and every app’s own root_url.

    Removing the zone and normalising the tiers is one pass, so we do both at the same time, because doing them separately means the second one renames names the first has just finished renaming.

    I started with the machine names and gave four Kubernetes nodes their new FQDNs without thinking about what reads a node’s hostname, which turned into its own problem.