blog GitOps for My DNS


I’ve used Namecheap for many years for hosting my DNS. It’s nothing particularly special but it works well enough for my needs. When managing DNS entries it can sometimes be a little annoying doing everything on their web page. I’d rather be able to script something. After thinking about it for a while, I finally created something.

Namecheap’s API is implemented in XML which makes parts of it feel a bit old, but our use case is fairly simple. We can use get-hosts to download a backup of our DNS entries for history, and then set-hosts to publish our new values. I wrote a class to handle the basic Namecheap API calls (which is mostly formatting them and wrapping the requests library).

# Some parts redacted but most of the interesting bits are here
class Namecheap:
    def __init__(self):
        self.api_base = ""

    def params(self, params):
        # Add our required parameters in a single place
        params.setdefault("ApiUser", self.username)
        params.setdefault("ApiKey", self.apikey)
        params.setdefault("UserName", self.username)
        return params

    def post(self, params, **kwargs) -> requests.Response:
        # Build a post requeest
        kwargs.setdefault("url", self.api_base)
        result =, **kwargs)
        return result

    def setHosts(self, domain, tld, params) -> requests.Response:
        params.setdefault("Command", "")
        params.setdefault("SLD", domain)
        params.setdefault("TLD", tld)

and then I have a Request class to build the actual setHost request

class Request:
    def __init__(self, domain, tld):
        self.client = Namecheap()
        self.domain = domain
        self.tld = tld
        self.count = 0
        self.params = {}

    def setHosts(self) -> requests.Response:
        return self.client.setHosts(self.domain, self.tld, self.params)

    def __record(self, **params):
        # The Namecheap API prefixes a bunch of their keys with a number
        # so we keep track of that here
        self.count += 1
        params.setdefault("TTL", 1800)
        for key in params:
            self.params[f"{key}{self.count}"] = params[key]
        return params

    def a(self, host, address):
        return self.__record(

I can now write a quick script for each of the domains I want to manage

ls scripts

which looks roughly like this

from pi.client.namecheap import Request
from pi.constants import Address

request = Request("paultraylor", "net")
request.a("@", Address.CHIHARU)
request.a("www", Address.CHIHARU)

# My old japanese notes blog.
request.cname("nihongo", "")
request.txt('@', 'some other information)

I also wrote a quick wrapper so I can quickly take a backup.

.PHONY: backup-dns
backup-dns: $(APP_BIN)
    $(APP_BIN) backup-dns
    $(APP_BIN) backup-dns

Having these scripts made things easier while doing my various domain migrations over the past weekend.