Containers
Running sdns in Docker, and the two things a container gets wrong by default.
The image is built FROM scratch and contains the static binary and a CA
bundle. There is no shell in it.
docker run -d --name sdns \
-p 127.0.0.1:53:53 -p 127.0.0.1:53:53/udp \
-v sdns-data:/var/lib/sdns \
-v /etc/sdns.conf:/etc/sdns.conf:ro \
ghcr.io/semihalev/sdns:latest -c /etc/sdns.conf
Persist the state directory
directory in the configuration must point at a volume. It holds the RFC 5011
trust anchor database, cached blocklists and the local root copy. Without a
volume, every restart re-fetches all of it and — more importantly — throws away
the trust anchor state that tracks root KSK rollovers.
This is the mistake worth avoiding: a container that resolves fine will keep resolving fine for a long time without a volume, and the problem only surfaces at a rollover.
Publish both protocols
DNS needs UDP and TCP on the same port. -p 53:53 alone publishes TCP only, and
the result is a resolver that answers the occasional truncated retry and nothing
else. Both -p 53:53 and -p 53:53/udp are required.
Compose
services:
sdns:
image: ghcr.io/semihalev/sdns:latest
container_name: sdns
restart: unless-stopped
command: ["-c", "/etc/sdns.conf"]
ports:
- "127.0.0.1:53:53"
- "127.0.0.1:53:53/udp"
volumes:
- sdns-data:/var/lib/sdns
- ./sdns.conf:/etc/sdns.conf:ro
volumes:
sdns-data:
Binding to 127.0.0.1 keeps the resolver off the host’s public addresses. If
you publish it more widely, set accesslist first — see
Access control.
Ports the image declares
53/tcp 53/udp plain DNS
853/tcp DoT
8053/tcp DoH
8080/tcp HTTP API and metrics
EXPOSE in the image declares TCP unless a port says otherwise, so DoQ and
HTTP/3 have no declaration — they are UDP. docker run -P will not publish
them; name them explicitly (-p 853:853/udp, -p 8053:8053/udp) if you serve
either.
Publish only what you actually serve. The API listener in particular is plain HTTP with no TLS, so a bearer token sent to it crosses the network in the clear and can be replayed. Keep it unpublished, or publish it to loopback only; if it has to be reachable, the protection is a TLS-terminating authenticating proxy, a VPN or a source-restricted firewall, with the token as a second layer.
Validating the config
The image has no shell, but it does have the binary:
docker run --rm -v ./sdns.conf:/etc/sdns.conf:ro \
ghcr.io/semihalev/sdns:latest -t -c /etc/sdns.conf
Exit code 0 means the file is good.
Memory-constrained hosts
On a router or a small VPS, consider:
memorytrim = true
It returns a traffic burst’s memory to the operating system after several idle minutes, at the cost of one synchronous garbage collection over the whole process. That is the right trade on a 256 MB device and the wrong one on a busy server.