# 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 ```sh mkdir -p ~/nxdns && cd ~/nxdns curl -fLO https://git.mial.net/mokhtar/nxdns/raw/tag/v/deploy/docker/compose.yaml ``` The compose file runs `git.mial.net/mokhtar/nxdns:`, 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](verify-a-release.md#6-verify-the-container-image). ## 2. Write the configuration ```sh 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: ```zon .{ .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: ```sh 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 ```sh 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 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](install-with-systemd.md#if-port-53-is-already-taken) shows how to free it. ## 4. Check it and point your network at it ```sh dig @127.0.0.1 example.com A +short ``` Open `http://: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: ```zon .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" }, }, ``` ```sh 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](../reference/configuration.md#lists) 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: ```sh 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: ```sh NXDNS_VERSION= docker compose up -d ``` To move to a new release, see [Upgrade](upgrade.md#docker).