Documentation / Guides
DNS in CI
A zone checked into git is only useful if something notices when the live
one stops matching it. dns diff --check exits 8
when they differ, which is a distinct code from a failed command, so a
pipeline can tell "the zone has drifted" apart from "the tool broke".
Capture the zone
Export once and commit the result. JSON is the fuller format — it carries the domain's forwarded mailboxes as well as its records:
namecheap dns export example.com > dns/example.com.json
git add dns/example.com.json
The zone-file format is the more readable one and is what
dns edit shows you, but it has no way to express forwarding,
so --format zone notes that forwarding exists rather than
pretending it round-trips.
namecheap dns export example.com --format zone > dns/example.com.txt
Assert it has not drifted
namecheap dns diff example.com dns/example.com.json --check
Exit 0 means the live zone matches the file. Exit
8 means it does not, and the diff is printed. Any other code
is a real failure — bad credentials, a network problem, a domain that is
not on the account.
A GitHub Actions job
Authenticate from the environment so there is no stored state to manage.
NAMECHEAP_API_KEY is checked before the keychain and before
the config file, so nothing else needs to exist on the runner.
name: DNS drift
on:
schedule: [{ cron: '0 7 * * *' }]
workflow_dispatch:
jobs:
check:
runs-on: ubuntu-latest
env:
NAMECHEAP_API_USER: ${{ secrets.NAMECHEAP_API_USER }}
NAMECHEAP_API_KEY: ${{ secrets.NAMECHEAP_API_KEY }}
NAMECHEAP_CLIENT_IP: ${{ secrets.NAMECHEAP_CLIENT_IP }}
steps:
- uses: actions/checkout@v4
- run: cargo install namecheap-cli --locked
- run: namecheap dns diff example.com dns/example.com.json --check
Namecheap's API is IP-whitelisted, and a hosted runner's address is not stable. Either run this on a self-hosted runner with a fixed address, or accept that a scheduled check from GitHub's pool will need the whitelist kept current. This is a Namecheap constraint, not a tool one.
Applying rather than asserting
The same file drives the write. sync brings the zone into line
with it; --delete also removes records the file does not
mention, making the file authoritative rather than additive.
# add and update what the file describes, leave everything else alone
namecheap dns sync example.com dns/example.com.json -y
# make the zone match the file exactly
namecheap dns sync example.com dns/example.com.json --delete -y
Mailboxes behave the same way: those in the file are added or updated, and
those it does not mention are left alone unless --delete is
given. A file with no email_forwarding key says nothing about
forwarding, so forwarding is left untouched rather than read as "there
should be none".
Use -y only where nobody can answer a prompt, and pair it with
--dry-run in a pull-request job so the diff is reviewed before
the branch that applies it merges.
Machine-readable output
Every command takes --json. Validation problems come back as
an issues array rather than formatted text, which is what you
want when something downstream has to decide what to do about them.
namecheap dns list example.com --json | jq '.records[] | select(.type == "MX")'
Exit codes
0 | Success |
1 | General error |
2 | Authentication error |
3 | Domain not found |
4 | Record not found |
5 | Validation error |
6 | Network error |
7 | Verification failed |
8 | dns diff --check found differences |