Jakub Jirůtka | InstallFest 2026
Image source: OpenAI gpt-image-1
https://slides.com/d/Unn3cUU/live
Image source: OpenAI gpt-image-1
System engineer and developer
Works at FEE CTU in Prague
FOSS developer & contributor
Alpine Linux developer
Source: https://lezebre.lu/en/sticker-simons-cat-claws/
@jirutka
@jakub@jirutka.cz
jakub@jirutka.cz
Source: OpenAI gpt-image-1
SSH
edit zone file
bump serial
reload
check logs
Primary DNS
BIND
BIND
Secondary DNS
NOTIFY/AXFR
Image source: OpenAI gpt-image-1
Primary DNS
BIND
BIND
Secondary DNS
NOTIFY/AXFR
It may take several hours for errors to manifest!
; DAKIZ, FJFI Martincik
dakiz-fw IN A 147.32.191.121
;
;; tov - Slackware u Andrejovice v kancelari (snad 148B3) - Karel Julis
; tov IN A 147.32.193.94
;; semik - docasne SSL telnetd server 147.32.193.35 zruseno 22.6.07
;; ivr - Bezpalec, B3-619, 2093
; ivr IN A 147.32.193.35
;;
;; intercom CIIRC
ic-ciirc-5np-sever IN CNAME voip-135-201-32-147.feld.cvut.cz.
ic-ciirc-5np-jih IN CNAME voip-198-201-32-147.feld.cvut.cz.
;;
;; sitovy hw
;bs115-1 IN A 147.32.193.96
;bs115-2 IN A 147.32.193.97
;bs301-1 IN A 147.32.193.98
;bs450-test IN A 147.32.193.99
;
;bs301-3 IN A 147.32.193.100
;
; Armada1
;xxx IN A 147.32.193.77
;xxx IN CNAME aladdin.ten.cz.
;yyy IN CNAME jerry.ten.cz.
;zzz IN A 147.32.193.76
;qqq IN A 147.32.193.75
;ttt IN A 147.32.193.74
Many semantic errors
BIND mostly checks just syntax, not semantic/logical errors.
No versioning and no metadata
Who knows…?
Is this record still needed?
Image source: OpenAI gpt-image-1
Mostly the same problems.
Even less comments (0).
Source: https://deaftogether.org.uk/wp-content/uploads/2023/09/deaftogether_hero_img.jpg
Source: OpenAI gpt-image-1
28
5896
6285
zones
domains names
res. records
Numbers without reverse zones.
All records are signed with DNSSEC.
# yaml-language-server: $schema=../schemas/zone.json
$extends: ./_soa.yml
$ORIGIN: example.cz.
$default:
tenant: 13373
"@":
ALIAS: wproxy
MX:
- 10 smtp.prg
- 20 smtp.example.org.
smtp.prg:
//: The primary inbound SMTP relay.
A: 203.0.113.25
AAAA: 2001:db8:113::25
wproxy:
//: The central web reverse proxy.
owners: flynnkev
tags: [ HTTP ]
A: 203.0.113.80
AAAA: 2001:db8:113::80
subdomains:
dev:
CNAME: wproxy
; Build-Date: 2026-03-19T17:13:59.857Z
; Git-Revision: 5e00c8c6d40af5751e290782f5eea1d39be401c1
$ORIGIN example.cz.
$TTL 3600
@ SOA ns1.example.cz. hostmaster.example.cz. (0 900 300 1209600 600)
; zones/example.cz.yml:7-12
@ NS ns1.example.cz.
@ NS ns2.example.org.
@ NS nsa.ces.net.
@ MX 10 smtp.prg
@ MX 20 smtp.example.org.
@ A 203.0.113.80
@ AAAA 2001:db8:113::80
; zones/example.cz.yml:13-17
smtp.prg A 203.0.113.25
smtp.prg AAAA 2001:db8:113::25
; zones/example.cz.yml:18-27
wproxy A 203.0.113.80
wproxy AAAA 2001:db8:113::80
dev.wproxy CNAME wproxy
zones/
├── _soa.yml
├── brian.cvut.cz.yml
├── example.cz.yml
├── fel.cvut.cz.d/
│ ├── 13101.yml
│ ├── 13102.yml
│ ├── ...
│ ├── 13373-SVTI.d/
│ │ ├── 13373-printers.yml
│ │ └── 13373-classrooms.yml
│ └── 13373-SVTI.yml
├── fel.cvut.cz.yml
zones:
(string) (description)(string) (aka include)(string)(number)(object) (for metadata)(object)string – descriptionnumber(string | string[])(string[])(string | string[])(boolean)(string | string[])({ttl, rdata, ptr}[])(object)pc-[9-10,12]:
owners: flynnkev
A: 147.32.209.$
vm-38[4-5]-[100-101]:
A: 10.38.$1.$2
pc-[01-03]:
A: 10.3.45.(($1 + 99))
pc-9:
owners: flynnkev
A: 147.32.209.9
pc-10:
owners: flynnkev
A: 147.32.209.10
pc-12:
owners: flynnkev
A: 147.32.209.12
vm-384-100:
A: 10.38.4.100
vm-384-101:
A: 10.38.4.101
vm-385-100:
A: 10.38.5.100
vm-385-101:
A: 10.38.5.101
pc-01:
A: 10.3.45.100
pc-02:
A: 10.3.45.101
pc-03:
A: 10.3.45.102
Edit zone
(YAML)
Open MR
OK?
Automated checks
Review
Edit zone
(YAML)
Generate
zone files
request
changes
git push
git push
merge
Deploy
Image source: OpenAI gpt-image-1
❯ make check-style
zones/fel.cvut.cz.d/13373-SVTI.yml
322:121 warning line too long (137 > 120 characters) (line-length)
326:1 warning too many blank lines (3 > 2) (empty-lines)
323:6 error syntax error: mapping values are not allowed here (syntax)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^(@|\\*|(\\*\\.)?(\\.(?=[^.])|[a-z0-9_-]|\\[([0-9]+(-[0-9]+)?,?)+\\])+)$": {
"type": "object",
"properties": {
"//": { "description": "A comment.", "type": "string" },
"owners": {
"description": "CTU username of one or more people responsible for this domain.",
"anyOf": [
{ "type": "string", "pattern": "^[a-z0-9_@.+~-]+$" },
{ "type": "array", "items": { "type": "string", "pattern": "^[a-z0-9_@.+~-]+$" } }
]
},
"A": {
"anyOf": [
{ "$ref": "#/$defs/IPv4Address" },
{
"type": "object",
"required": ["rdata"],
"properties": {
"rdata": { "$ref": "#/$defs/IPv4Address" },
"ttl": { "description": "Time to Live (in seconds) for this record.", "type": "number" },
"ptr": { "description": "Set to false if this record should not be used to generate the PTR (reverse record).", "type": "boolean" }
}
},
{
"type": "array",
"items": {
"anyOf": [
{ "$ref": "#/$defs/IPv4Address" },
# yaml-language-server: $schema=<path>
ajv-cli is CLI for Ajv JSON schema validator
See more at https://github.com/jirutka/ajv-cli
❯ make check-schema
--> zones/fel.cvut.cz.d/13373-SVTI.yml:323:11
#/installfest/owners
|
| installfest:
| //: Website for the InstallFest conference.
323 | owners: Kevin Flynn
| ^^^^^^^^^^^ must match pattern '^[a-z0-9_@.+~-]+$'
| A: 203.0.113.80
| AAAA: 2001:db8:113::80
|
--> zones/fel.cvut.cz.d/13373-SVTI.yml:328:3
#/pretalx.installfest
| AAAA: 2001:db8:113::80
|
| pretalx.installfest:
328 | owners: flynnkev
329 | A: 203.0.113.20
330 | AAA: 2001:db8:113::20
| ^ must not include property 'AAA'
|
| intranet:
{
"origin": "example.cz.",
"ttl": 3600,
"soa": { … },
"domains": [
{
"name": "wproxy.example.cz.",
"owners": [ "flynnkev" ],
"tenant": 13373,
"tags": [ "HTTP" ],
"records": [
{ "type": "A", "rdata": "203.0.113.80" },
{ "type": "AAAA", "rdata": "2001:db8:113::80" }
],
"@source": "zones/example.cz.yml:18-26",
}
]
}
* checked also by kzonecheck
❯ make check-schema
1) ERROR: A record "203.0.113.666" is invalid.
zones/installfest.cz.yml:8
5 | "@":
6 | //: Website for the InstallFest conference.
7 | owners: flynnkev
8 > A: 203.0.113.666
9 | AAAA: 2001:db8:113::80
2) ERROR: CNAME target "pretalx-01.apps.fel.cvut.cz." was not found.
zones/installfest.cz.yml:15
12 | pretalx:
13 | //: A conference planning tool for InstallFest.
14 | owners: flynnkev
15 > CNAME: pretalx-01.apps.fel.cvut.cz.
3) WARNING: CNAME pretalx-dev.installfest.cz. points to CNAME pretalx.installfest.cz..
zones/installfest.cz.yml:16
16 | pretalx-dev:
17 | //: A conference planning tool for InstallFest (dev instance).
18 | owners: flynnkev
19 | CNAME: pretalx
Convert zones in JSON to the standard zone format for the DNS server.
; Build-Date: 2026-03-19T17:13:59.857Z
; Git-Revision: 5e00c8c6d40af5751e290782f5eea1d39be401c1
$ORIGIN example.cz.
$TTL 3600
@ SOA ns1.example.cz. hostmaster.example.cz. (0 900 300 1209600 600)
; zones/example.cz.yml:7-12
@ NS ns1.example.cz.
@ NS ns2.example.org.
@ NS nsa.ces.net.
@ MX 10 smtp.prg
@ MX 20 smtp.example.org.
@ A 203.0.113.80
@ AAAA 2001:db8:113::80
; zones/example.cz.yml:13-17
smtp.prg A 203.0.113.25
smtp.prg AAAA 2001:db8:113::25
; zones/example.cz.yml:18-27
wproxy A 203.0.113.80
wproxy AAAA 2001:db8:113::80
dev.wproxy CNAME wproxy
Knot utility that checks zone file syntax and runs semantic checks on the zone content.
See https://www.knot-dns.cz/docs/3.5/singlehtml/#semantic-checks
deploy-dns:
stage: deploy
only:
- master
environment:
name: ns-master
# Ensures that only one instance of this job can run at a time.
resource_group: ns-master
script:
- rsync --dirs --delete -i output/zones/ user@host:/etc/knot/zones/
- ssh user@host doas zone-reload
#!/bin/sh
set -eu
knotc zone-check
knotc zone-reload
timeout 5 tail -f /var/log/messages \
| sed -En 's/.*daemon\.\w+ knot\[\d+\]: (.*)$/\1/p' || true
zone-reload
.gitlab-ci.yml
hidden master
ssh+rsync
checks
DNSSEC signing
GitLab CI/CD
YAML→JSON
JSON→.zone
checks
rev. zones
NOTIFY/AXFR
primary
secondary
tertialy (external)
Sites FEL
ala GitLab Pages
zone files
zones
git repo
YAMLs
Domain
Viewer
JSONs
webhook HTTP
template:
- id: public
storage: "/var/lib/knot"
file: "/etc/knot/zones/%s.zone"
notify: ns-public
semantic-checks: true
journal-content: all
zonefile-sync: -1
zonefile-load: difference-no-serial
serial-policy: unixtime
module: mod-queryacl/default
(7) Keep full zone content (and history) in journal.
(8) Treat zone files as input only.
(9) Compare file vs in-memory, load only differences, ignore serial.
(10) Set serial to the current unix time.
Automatically managed by
knot.conf
[
{
"description": "[yamllint] syntax error: mapping values are not allowed here",
"check_name": "yamllint:syntax",
"fingerprint": "5213eb1df4235eb980b8fdc604d34f5aaa06138d",
"severity": "major",
"location": {
"path": "zones/feld.cvut.cz.d/13373-SVTI.yml",
"lines": {
"begin": 323,
"end": 6
}
}
}
]
test:
script:
- ...
after_script:
- make gl-cq-report.json
artifacts:
reports:
codequality: gl-cq-report.json
.gitlab-ci.yml
gl-cq-report.json
https://docs.gitlab.com/ci/testing/code_quality/
VSCodium
VS Code
and
https://open-vsx.org/extension/redhat/vscode-yaml
https://github.com/redhat-developer/yaml-language-server
JSON schema powered:
modeline to specify JSON schema
yaml-language-server can be used even in Emacs, vim and other editors with LSP.
{
"tasks": [
{
"label": "Check semantics (opened files)",
"group": { "kind": "test", "isDefault": false },
"type": "process",
"command": "make",
"args": [ "check-data", "CHECK_ZONES_FORMAT=basic" ],
"problemMatcher": {
"owner": "check-zones",
"fileLocation": ["relative", "${workspaceFolder}"],
"applyTo": "openDocuments",
"pattern": [
{
"regexp": "^\\d+\\) (ERROR|WARNING): (.*)$",
"severity": 1,
"message": 2,
},
{
"regexp": "^\\s+.*? in (\\S+):(\\d+)$",
"file": 1,
"line": 2,
"loop": true,
},
],
},
},
],
}
2) WARNING: Found 2 domains without ptr:false pointing to IP 2001:db8:113::80: courses.example.cz, wproxy.example.cz.
courses.example.cz. in zones/example.cz.yml:15
wproxy.example.cz. in zones/example.cz.yml:27
Ctrl+Shift+P > Tasks: Run Task > [Task]
.vscode/tasks.json
https://open-vsx.org/extension/webfreak/advanced-local-formatters
{
"advancedLocalFormatters.formatters": [
{
"languages": ["yaml"],
"command": ["./scripts/zoneyml-fmt"],
},
],
"[yaml]": {
// XXX: This is applied on all yaml files, not just in zones/, but there's
// nothing we can do about it (see microsoft/vscode#32693). :(
"editor.defaultFormatter": "webfreak.advanced-local-formatters",
"editor.formatOnSave": true,
},
}
.vscode/settings.json
Readable and familiar
easy to write, easy to understand
Structured
clear hierarchy, no ambiguity
machine-readable and machine-writeable
AI-friendly
Strong tooling
schema validation, autocomplete, linter, formatters
Git-friendly
clean diffs, easy reviews
Do you know a better format?
A web UI looks simple at first — until you need versioning, diffs, reviews, bulk changes, validation, automation.…
We get all of that for free by treating DNS as code and leveraging decades of SW development ecosystem. It gives us powerful capabilities that are hard and expensive to build in a UI.
This solution is simple, very flexible, easy to maintain and extend.
| Component | Lines of code | npm dependencies |
|---|---|---|
| core tools | ~1500 | 7 (19) |
| aux tools | ~1500 | 11 (23 total) |
| Domain Viewer | ~2000 | 15 (125 total) |
Image source: OpenAI gpt-image-1
Source: OpenAI gpt-image-1 trained on art stolen from the Internet