devmachine

How it works

DNS providers

Every fact here exists so you learn it from this page, not by damaging a zone. Read it before you touch a real one.

A provider is a package, not a built-in vendor

Talking to Hostinger or Cloudflare is an ordinary package: devmachine packages add hostinger, run on the machine, never on your computer. There is no vendor list inside the CLI binary.

That gives one safety property: a provider nobody installed cannot be written to. No code path reaches a registrar’s write API unless its package is on the lock file for that machine. Adding a provider is a decision with a diff.

What the provider gets from the shell

PATH, HOME, and the one token its own credential declares — nothing else. The CLI sources /etc/devmachine/<credential>/env right before running the entrypoint, so nothing else leaks in. A provider that reads a variable its credential does not declare works by accident on one machine and breaks everywhere else.

The token never appears in a command argument, where ps would show it to every account on the machine. Only the credential’s name does, and that name is not a secret.

Hostinger

RRsets, not records

Hostinger’s API has no idea of a single record. It has RRsets: one entry per (name, type), holding a list of values. www A is one RRset that can carry several addresses at once. list splits each RRset into one entry per value, but every write has to work in RRset terms.

overwrite: false appends, and the default is true

Hostinger’s PUT takes a whole RRset and an overwrite flag. With false, the request’s values are added to what is already there — pointing an existing name at a new address this way leaves both, a round-robin nobody asked for. Leave the field out and Hostinger’s own default is overwrite: true, the destructive mode, so the provider always sends it explicitly, decided by the code, never left to Hostinger’s default.

Replacing a value: delete, then append — not one call

upsert must make a name hold exactly the new value. Hostinger gives two ways: one PUT with overwrite: true, or a DELETE of the RRset then a PUT with overwrite: false. The provider uses the second, at the cost of two calls: Hostinger’s own docs say overwrite: true replaces every record in the payload, so a one-RRset PUT would empty the rest of the zone. The cost of the two-call path is real — the name holds nothing for the gap between them, so a resolver asking in that window gets NXDOMAIN — but that beats a write whose blast radius is the whole zone.

A DNS-only token needs its zones configured

Hostinger’s DNS API answers about one zone at a time and has no list-zones endpoint. Provider selection normally reads the account’s domains from the Domains portfolio endpoint, but a least-privilege DNS token gets a 403 there even though it can read its own zone. Set hostinger.zones on the machine for that case.

A delete filter takes the whole RRset

Hostinger’s DELETE removes an entire (name, type), not one value inside it. Deleting one value out of several means: read the RRset, delete it whole, then PUT back the values you meant to keep.

Writes are asynchronous

A successful PUT or DELETE means “request accepted,” not “done.” A list called right after can still show the old state — which is why the provider never checks its own writes immediately. Give it a moment before checking with dns status or dns list.

Cloudflare

PUT clears every field it was not given

Cloudflare’s DNS record PUT replaces the whole record: send only content and ttl, and proxied, comment and tags reset to defaults. The provider always uses PATCH instead, which touches only the fields in the request.

No upsert: every write past a create needs an id

Cloudflare has no “make this name hold this value” call. Creating a record returns an id; changing or deleting one needs that id, found by listing first. So upsert always lists before it writes: create on no match, PATCH by id on one match, refuse to guess on more than one (ambiguous).

An invisible zone is a 200, not a 404

Ask Cloudflare for a zone the token cannot see, and the answer is 200 with an empty result array, not an error. The provider treats an empty result as zone_not_found itself.

proxied is always false

Certificates on this machine renew through a challenge that needs the request to reach the machine directly. A proxied record answers from Cloudflare’s edge instead, and the challenge fails.

Neither provider retries a rate limit

A 429 is reported as rate_limited and the provider stops. Retrying inside the entrypoint would hide how throttled the registrar really is, and could turn one throttled request into a silent hang. Cloudflare blocks a token for five minutes after a single 429, so retrying would not even help.