Use DNS to publish a version pointer for a private repo

Keep the routine update check public, then spend credentials only when a newer release needs to be fetched.

Checking a private repository for updates normally means authenticating every poll. A public DNS TXT record can remove that login step: publish only the current version in DNS, then access the private repository only when the version changes.

The TXT value is a hint, not an authorization boundary. It may cause the updater to check for a release, but the private host still authenticates the client and the updater must verify the downloaded release before installing it.

Publish one small value

Choose a dedicated name:

version.myproject.example.com

Add a TXT record whose value contains a normal MAJOR.MINOR.PATCH version:

v=2.4.1

Semantic Versioning gives each component a specific meaning once the project has declared a public API and reached 1.0.0:

  • MAJOR identifies incompatible public API changes.
  • MINOR identifies backward-compatible public API additions or deprecations.
  • PATCH identifies backward-compatible bug fixes.

SemVer gives 0.y.z no stability guarantee. This example also excludes prerelease and build metadata. Keeping the accepted grammar narrow makes the client comparison unambiguous.

Set the record's TTL to 300 seconds or less if clients should notice releases quickly. A recursive resolver may cache the old answer for the TTL, and some resolvers can serve stale data when the authoritative DNS service cannot be reached. Treat the TTL as a freshness target, not a delivery deadline.

A shorter TTL also creates more queries at the recursive resolver and authoritative DNS service. Five minutes is a useful starting point, not a universal requirement.

Update the record as part of the release pipeline, after the private release is available. Only the release system should have permission to change it. A client that sees the TXT value before the artifact exists will otherwise perform an authenticated check that cannot succeed.

Read and compare the version

Install dnspython:

python3 -m pip install dnspython

The reader joins all character strings in a TXT record, accepts exactly one v= value, and validates the restricted three-number format before returning it:

#!/usr/bin/env python3
import re
import sys

import dns.resolver

DNS_NAME = "version.myproject.example.com"
VERSION_PATTERN = re.compile(r"(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)")


def get_remote_version(dns_name: str) -> tuple[int, int, int]:
    answer = dns.resolver.resolve(dns_name, "TXT", lifetime=5.0)
    values = []

    for record in answer:
        raw_value = b"".join(record.strings)
        if raw_value.startswith(b"v="):
            values.append(raw_value.removeprefix(b"v=").decode("ascii"))

    if len(values) != 1:
        raise ValueError(f"Expected one version value for {dns_name}, found {len(values)}")

    version = values[0]
    if VERSION_PATTERN.fullmatch(version) is None:
        raise ValueError(f"Invalid version value for {dns_name}: {version!r}")

    return tuple(int(part) for part in version.split("."))


if __name__ == "__main__":
    try:
        print(".".join(map(str, get_remote_version(DNS_NAME))))
    except Exception as exc:
        print(f"Error: {exc}", file=sys.stderr)
        sys.exit(1)

Run it directly:

python3 read_version.py

Python compares equal-length tuples from left to right, which gives the required major, minor, then patch ordering:

local_version = (2, 3, 7)
remote_version = get_remote_version(DNS_NAME)

if remote_version > local_version:
    if remote_version[0] > local_version[0]:
        print("A potentially incompatible update is available")
    else:
        print("An update is available")

The actual updater can now use its repository token to fetch the named release. Do not install whatever happens to be newest if it differs from the DNS pointer. Confirm that the repository response has the expected version, then verify a release signature or compare the artifact with a digest obtained from authenticated, trusted metadata before replacing local code.

If the lookup times out, returns no pointer, or fails validation, keep the installed version and try again later. A broken update hint should not stop the current application from running.

Keep DNS outside the trust boundary

DNS is public, and an ordinary DNS lookup does not by itself prove who published the answer. DNSSEC can authenticate a signed record when a validating resolver builds a chain to a configured trust anchor, but it does not make the TXT value secret. The example above performs ordinary stub resolution and does not validate DNSSEC itself.

The safe division of responsibility is narrow:

  • DNS announces that a version may be available.
  • The repository authenticates the client and authorizes access to the release.
  • Authenticated transport protects the fetch.
  • A release signature, or a digest from authenticated metadata, verifies the downloaded artifact.
  • The updater decides whether policy allows the version, especially across a major-version change.

Never put a token, private repository URL containing credentials, or other secret in the record. DNS carries one small public pointer here; it is not release metadata, an audit log, or a database.

References

fullscreen