Skip to content

Install with Docker

Run the published nxdns image with Docker Compose. At the end a container answers DNS on port 53 and keeps its data in a named volume. The image is built for linux/amd64 and linux/arm64, so the same steps work on a PC and on a Raspberry Pi.

1. Get the compose file

Terminal window
mkdir -p ~/nxdns && cd ~/nxdns
curl -fLO https://git.mial.net/mokhtar/nxdns/raw/tag/v0.0.25/deploy/docker/compose.yaml

The compose file runs git.mial.net/mokhtar/nxdns:0.0.25, the release it was tagged with. It publishes ports 53 (UDP and TCP) and 8080, keeps the data directory in the volume nxdns-data, and mounts ./etc-nxdns read-only as /etc/nxdns.

To check the image against the signed release before you run it, follow Verify a release.

2. Write the configuration

Terminal window
mkdir -p etc-nxdns
$EDITOR etc-nxdns/config.zon

The smallest configuration that starts is a group named default and one upstream resolver, with a password for the web interface:

.{
.groups = .{ .{ .name = "default" } },
.upstreams = .{ .{ .url = "https://cloudflare-dns.com/dns-query" } },
.web = .{ .password = "choose-a-real-password" },
}

The container runs as uid 65532 and cannot change the mounted files, so make the file readable by that uid and nobody else:

Terminal window
sudo chown 65532:65532 etc-nxdns/config.zon
sudo chmod 0600 etc-nxdns/config.zon

The compose file runs nxdns in file mode: this file is the configuration. Every start applies it, and the web interface refuses configuration changes and names the file instead. To change anything, edit the file and restart the container. A new or recreated nxdns-data volume rebuilds itself from the file.

3. Start it

Terminal window
docker compose up -d
docker compose logs -f

A healthy first start applies the file and names every socket it opened. Among its first lines:

reconciled '/etc/nxdns/config.zon': upstreams +1 ~0 -0; settings +46 ~0 -0;
web authentication is now enabled
info(nxdns): authority: file (/etc/nxdns/config.zon)
info(nxdns): nxdns 0.0.25 serving on udp [::]:53 tcp [::]:53 tcp 0.0.0.0:53; 1 upstream(s); blocklist generation 1
info(web_server): web interface listening on 0.0.0.0:8080

Later starts with an unchanged file log reconciled '/etc/nxdns/config.zon': no changes.

If the file is missing, unreadable or invalid, nxdns prints what is wrong and exits 2, for example:

FAIL groups: no group named 'default'; every unknown client is assigned to it
nxdns run failed: MissingDefaultGroup

With restart: unless-stopped, Docker then restarts the container until you fix the file, so read the log. If a host port is already in use, docker compose up fails with address already in use; free the port or change the ports: list. On a systemd host, port 53 is usually held by systemd-resolved; Install with systemd shows how to free it.

4. Check it and point your network at it

Terminal window
dig @127.0.0.1 example.com A +short

Open http://<host-ip>:8080 and log in. The interface shows everything nxdns does; in file mode its configuration pages are read-only and name the file to edit.

Blocklists go in the file too. Add a source and attach it to the default group, then restart:

.blocklist_sources = .{
.{ .url = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts", .name = "StevenBlack hosts" },
},
.group_sources = .{
.{ .group = "default", .source_url = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts" },
},
Terminal window
docker compose restart

The restart logs the change and then the download:

reconciled '/etc/nxdns/config.zon': sources +1 ~0 -0; group_sources +1 ~0 -0;
info(blocklist_manager): blocklist snapshot generation 3: 1 of 1 sources loaded, 1997188 bytes

The configuration reference lists everything else the file can hold: more groups, devices, rules and local records.

To make every device use nxdns, set the DNS server your router hands out (its DHCP settings) to the Docker host’s address. Leave the Docker host itself on another resolver: the container looks up its upstreams’ names through the host, and that should not depend on the container already running.

Database mode instead

To change settings in the web interface rather than in the file, delete the command: line from compose.yaml. The database in the volume is then the configuration and the file is not read. Load the file into the empty database once, then start:

Terminal window
docker compose run --rm nxdns import /etc/nxdns/config.zon
docker compose up -d

Without that import the empty database has no upstream, and the container exits 2 with NoUsableUpstreams.

Run another release

NXDNS_VERSION overrides the release the compose file names:

Terminal window
NXDNS_VERSION=0.0.25 docker compose up -d

To move to a new release, see Upgrade.