# knot-dns Authoritative DNS node — [Knot DNS](https://www.knot-dns.cz/) on Alpine, native (no container), for a global anycast estate with automatic DNSSEC. **Exception to the repo norm**, alongside `squid` and `openbao`: no Docker, no Caddy, no Let's Encrypt. Knot binds :53 directly, needs real client addresses for RRL and DNS cookies, and its DNSSEC key store must live on the host filesystem. Containerising it buys nothing and costs the host firewall. ## What this deploys, and what it does not This stands up **a node**. It does not manage zones — that is the [`dns`](../../../dns) repo, and its pipeline delivers zone data here. The split point is `/etc/knot/knot.conf`. This deployment writes it once as a skeleton of `include:` lines covering only what belongs to a *box*: identity, NSID, storage paths, listen addresses, logging, control socket. Everything that belongs to *DNS policy* — templates, DNSSEC policy, remotes/ACLs, modules, the domain inventory, the zone files — arrives from the `dns` repo. | Deployed here, once | Delivered by the `dns` repo, continuously | |---|---| | `knot.conf` skeleton | `templates.conf`, `policy.conf`, `remotes.conf`, `modules.conf` | | `secrets.conf` (TSIG, rendered locally, never in git) | `dnssec.conf`, `public.conf`, `arpa.conf` | | `knsctl`, aliases, MOTD, `zone.tmpl` | `zones/**/*.zone` | | packages, users, directories, firewall | | ## Files | File | Purpose | |---|---| | `knot.conf` | Bootstrap skeleton. `@NODE_ID@` / `@LISTEN@` substituted from `.env`. | | `secrets.conf.example` | TSIG `key:` block. Rendered to `/etc/knot/secrets.conf`, `0640 root:knot`. Never committed. | | `knsctl` | Admin CLI. Replaces `adddns.pl` / `adddnssec.pl` and fixes four defects in them. | | `knotdns-aliases.sh` | `/etc/profile.d/` — the existing muscle memory, with `-b` added everywhere. | | `knot-dns.motd` | The cheat-sheet MOTD. | | `zone.tmpl` | New-zone skeleton (mirrors `dns/skeleton/zone.tmpl`). | ## Roles `ROLE` in `.env` selects what the node is: - **`primary`** — holds zone files, signs with automatic DNSSEC, owns the KASP database, notifies the secondaries. Exactly one node. Not necessarily public-facing: a hidden (stealth) primary is the recommended shape. - **`secondary`** — receives zones by AXFR/IXFR with TSIG, serves queries, signs nothing. `dnssec-signing` must be **off**; a secondary serves data that is already signed. Adding a public node is a `secondary` deploy plus one address in the primary's `remotes.conf`. With catalog zones configured, the new node self-populates. ## What survives from the previous setup Everything an operator types. `knrl`, `knsc`, `knzc`, `knpbz`, `knsec`, `knarpa`, `ozf` all still work, from the same paths, on the same box. Three corrections were folded in: 1. **`-b` on every triggering `knotc` command.** Without it `knotc` returns OK when the command was *sent*, not when it succeeded, so a rejected zone file reports green. `knrl` was a bare `knotc reload`. 2. **`knzr` (`zone-reload`) added** next to `knrl` (`reload`). Reloading one zone's data is the right verb for a record change — smaller blast radius, and a parse error in one zone cannot disturb the others. `knrl` reloads configuration and is only needed when a zone is added or removed. 3. **`knsctl` replaces the Perl scripts**, whose duplicate check searched for the domain in BIND `named.conf` double-quote syntax against unquoted YAML and therefore never matched; which never consulted the other class's manifest, so a domain in `public.conf` could be appended to `dnssec.conf` and fail the reload after both files were already written; and which reloaded without validating. ## Key material `/var/lib/knot/keys` (the KASP LMDB) is node-local to the primary and replicates nowhere. Losing it is the one unrecoverable failure in this system: Knot will happily generate fresh KSKs, every published DS will point at keys that no longer exist, and every signed domain goes bogus until each registrar is updated by hand. ```sh knotc -b zone-backup +backupdir /var/backups/knot/$(date -u +%FT%H) +journal ``` **`+journal` is not the default** — the documented default filter set excludes it, and in this configuration the journal holds the only durable copy of the signed zone and the last real SOA serial. A default `zone-backup` captures your keys and loses your zone data. Restore order is config → KASP + journal → *then* start `knotd`, never the reverse. ## Usage ```sh # interactive bash deploy.sh # non-interactive (this is what cloud-init.yml does) ROLE=secondary NODE_ID=ANYCAST-DNS-3 PRIMARY_ADDR=10.1.24.64 \ SKIP_PROMPTS=1 bash deploy.sh ```