kudurru in one container
The single-instance shape (docs/architecture.md §4.4, §6): the sqlite store
in /var/lib/kudurru/state.db, configuration in /etc/kudurru/kudurru.yaml
plus objects.d/, the API and UI on :8080.
docker run
mkdir -p kudurru/secrets && cd kudurru
openssl rand -base64 32 > secrets/encryption_key
openssl rand -base64 18 > secrets/admin_password
docker run -d --name kudurru --restart unless-stopped \
-p 53:53/udp -p 53:53/tcp -p 8080:8080 \
-v kudurru-data:/var/lib/kudurru \
-v "$PWD/secrets:/run/secrets:ro" \
-e KUDURRU_ENCRYPTION_KEY_FILE=/run/secrets/encryption_key \
-e KUDURRU_ADMIN_PASSWORD_FILE=/run/secrets/admin_password \
ghcr.io/robertherbaugh/kudurru:0.1.0
dig @127.0.0.1 example.com
Without a mounted kudurru.yaml the built-in defaults apply: plain DNS on
:53 forwarding to 1.1.1.1 and 9.9.9.9, the starter blocklist, the query log.
The image runs as uid 65532 and binds :53 because Docker starts the
container's unprivileged port range at 0. With --network host the host's
net.ipv4.ip_unprivileged_port_start applies instead: run the
-hostnetwork image (ghcr.io/robertherbaugh/kudurru:0.1.0-hostnetwork,
the same binary with the file capability cap_net_bind_service=+ep), set the
sysctl to 53 on the host, or use the packages below. The -hostnetwork
image needs NET_BIND_SERVICE among the container's capabilities (Docker's
default set has it; with --cap-drop ALL add --cap-add NET_BIND_SERVICE),
otherwise it does not start ("exec /ko-app/kudurru: operation not
permitted").
docker compose
deploy/compose/single is the reference: a read-only root filesystem, all
capabilities dropped, no-new-privileges, Docker secrets for the key and
the admin password, a named volume for /var/lib/kudurru and the health
check kudurru health --ready.
cp -r deploy/compose/single ~/kudurru && cd ~/kudurru
mkdir -p secrets
openssl rand -base64 32 > secrets/encryption_key
openssl rand -base64 18 > secrets/admin_password
docker compose up -d
docker compose logs -f kudurru
First login
Open https://<host>:8080/ and sign in as admin with the content of
secrets/admin_password. The UI asks for a new password. Without
KUDURRU_ADMIN_PASSWORD_FILE a password is generated and logged once at the
first start (docker compose logs kudurru | grep -i password). The file is
read only while no user exists; to recover a lost password use
docker compose exec kudurru kudurru admin reset-password admin.
Without a configured certificate the API and UI serve a self-signed one
(written to keys/api.crt in the data volume), so the browser warns on the
first visit; set listeners.api.tls.certFile/keyFile for a trusted one.
Configuration
kudurru.yaml: node keys (listeners, data directory, store, log) and the initial cluster settings (upstreams, cache, blocking, query log, rate limiting).kudurru config schemaprints every key with its scope and flags; changes to keys flaggedRneeddocker compose restart kudurru.KUDURRU_SETTINGS_FILE(/var/lib/kudurru/settings.yamlin the compose files): cluster settings changed in the UI, withkudurru settings setorPATCH /api/v1/settingsare written here and layered overkudurru.yaml; the file is watched and hot keys apply without a restart. Without the variable such changes live in memory only (persisted: false).objects.d/*.yaml: zones, record sets, block lists, client groups and policies in the CRD format (kubectl applyYAML; examples inconfig/samples/). They are file-managed (managedBy: file:<path>), re-applied on change, and read-only in the UI.- Environment:
KUDURRU_<SECTION>__<KEY>overrides scalar keys, for exampleKUDURRU_LOG__LEVEL: debug.
Encrypted transports
Either let kudurru terminate them (add listeners.dot, doh, doq, doh3
with tls.certFile/keyFile to kudurru.yaml, mount the certificate and
publish 853/tcp, 853/udp, 443/tcp, 443/udp), or put dnsdist in front with
deploy/compose/single-edge: dnsdist owns :53/:853/:443 and the packet
cache, kudurru listens on 127.0.0.1:5353 in dnsdist's network namespace and
accepts PROXYv2 from loopback, so the query log keeps the real client
address. The kudurru-dnsdist image is a milestone M4 deliverable.
Persistence and backups
Everything lives in the kudurru-data volume: state.db (objects, users,
tokens, audit), querylog.db, local.db (cache dump), lists/ (blocklist
cache). Back up with the container stopped:
docker compose stop kudurru
docker run --rm -v kudurru_kudurru-data:/data -v "$PWD:/backup" busybox \
tar -czf /backup/kudurru-data.tgz -C /data .
docker compose start kudurru
kudurru backup --out objects.yaml exports the objects (zones, record sets,
lists, client groups, policies) online and kudurru restore -f objects.yaml
re-applies them; it does not include users, tokens, the audit trail or the
query log, so keep the volume backup above for those.
Metrics
Publish 127.0.0.1:9153:9153 (commented in the compose file) and scrape
/metrics. Import deploy/grafana/kudurru-overview.json into Grafana and
load the spec.groups of deploy/prometheus/rules.yaml as a rule file.
Linux packages and systemd
The deb, rpm and apk packages install /usr/bin/kudurru, the unit
/usr/lib/systemd/system/kudurru.service, /etc/kudurru/kudurru.yaml and
/etc/kudurru/kudurru.env (both kept on upgrade) and create the kudurru
system user. The unit runs as that user with only CAP_NET_BIND_SERVICE,
ProtectSystem=strict, StateDirectory=kudurru (/var/lib/kudurru) and
RuntimeDirectory=kudurru (/run/kudurru). The package never starts the
service by itself:
sudo sh -c 'umask 027; openssl rand -base64 32 > /etc/kudurru/encryption_key'
sudo chgrp kudurru /etc/kudurru/encryption_key
sudo sed -i 's|^#KUDURRU_ENCRYPTION_KEY_FILE=|KUDURRU_ENCRYPTION_KEY_FILE=|' /etc/kudurru/kudurru.env
sudo systemctl enable --now kudurru
kudurru health --ready
If systemd-resolved holds :53 on the host, disable its stub listener
(DNSStubListener=no in /etc/systemd/resolved.conf) first.