On a Raspberry Pi 4 or later with a 64-bit system, or on any amd64 or arm64 server with Docker and its Compose plugin, in an empty directory:
curl -fsSLO https://github.com/warda-dns/Releases/releases/latest/download/docker-compose.yml
docker compose up -d
docker logs wardaFrom 0.8.0 each release carries its docker-compose.yml, on GitHub
(warda-dns/Releases; the link above always gives the one of the latest
release) and on Gitea (the release of warda-dns/warda, and the public
repository warda-dns/releases of https://gitea.tips-of-mine.com, whose
README gives the same commands). The CI writes it at release time from
packaging/docker-compose.yml (.gitea/scripts/compose-release.sh) with
the image pinned to the version (ghcr.io/warda-dns/warda:X.Y.Z; a
release candidate pins its rc), and lists it in SHA256SUMS, so it is
signed with the other files. The file of the Git repository keeps
latest, with a comment that recommends a version. The 12 READMEs of the
GitHub releases have a section Install with Docker (anchor #docker).
The CI also writes the file of each stable release at the root
of both public repositories of the releases (docker-compose.yml, beside
the README): the one of the latest version, for those who look for it
among the files of the repository.
The file runs Warda on the network of the host (no ports: mapping: the
ports are those of the machine) and sets the three listeners explicitly,
with comments:
| Setting | In the file | What to change |
|---|---|---|
WARDA_HTTP_LISTEN |
":80" |
Plain HTTP (sends the browser to HTTPS). Beside a reverse proxy (Traefik, nginx, Caddy) or the interface of a NAS that holds 80 and 443: ":8080", then open http://<address of the machine>:8080. The health check of the image follows it by itself. |
WARDA_HTTPS_LISTEN |
":443" |
The interface and its API: ":8443" beside a reverse proxy. |
WARDA_DNS_LISTEN |
":53" |
Never another port: the devices only ask port 53. Free it instead (below), or listen on one address of the machine, e.g. "192.168.1.10:53", to share the port with another DNS server bound elsewhere. |
WARDA_DOT_LISTEN and WARDA_DOQ_LISTEN (:853) are used only once the
encrypted DNS is turned on in the interface. Freeing port 53: on
Debian or Ubuntu the stub of systemd-resolved holds it —
DNSStubListener=no in /etc/systemd/resolved.conf, then systemctl restart systemd-resolved; stop Pi-hole, AdGuard Home or dnsmasq first.
The log of the container is bounded (driver json-file, 3 files of
10 MB: a memory card wears out). Moving 80 and 443 does not publish Warda
behind the reverse proxy: it only shares the machine. Warda is never
served to the Internet through a proxy or a tunnel (a request forwarded
for an address of the Internet is refused, see Misuse and how Warda
prevents it).
Once the interface listens, the log gives the addresses to open, at most
3, those of the bridges of Docker, Podman, libvirt or Kubernetes left out,
with the port when it is not 80 (WARDA_ADVERTISE_ADDR wins when set):
Warda is not installed yet: open http://10.0.4.133:8080 or http://[2a01:…]:8080 in a browser of the network (or http://warda:8080 once the devices use Warda as their DNS) and sign in as admin with the default password of this version (warda setup-info), within 60 minutesdocker exec warda warda setup-info prints the same addresses and the
default password. At the first start blocklists loaded domains=0 is
expected (no copy on disk yet), with the line blocklists not downloaded yet: they are downloaded as soon as Warda listens; a second blocklists loaded gives the real count. The line of the networks allowed reads
networks of this machine allowed besides the private ranges networks=[…] private=[10.0.0.0/8 …]: the private ranges are always allowed, the
networks listed are the extra ones (the global IPv6 networks and the CGNAT
networks of the machine).
Updating: write the new version in the line image: of the file, then
docker compose pull && docker compose up -d. Downloading the file of the
new release again works too, but replaces the ports changed in it.
Ports already in use#
A port that another program holds still stops Warda (exit 1), but from 0.8.0 the message names the listener, the port, the likely program and the setting, and every port in conflict is named at once (HTTP, HTTPS and DNS), not one per restart. For example:
web interface (HTTP): port 80 is already used by another program on this machine (a reverse proxy such as Traefik, nginx or Caddy, another web server, the interface of a NAS): stop it, or give Warda another port with WARDA_HTTP_LISTEN (option --http-listen; with Docker Compose, under environment: in docker-compose.yml), e.g. WARDA_HTTP_LISTEN=:8080: …For DNS the message names the stub of systemd-resolved, dnsmasq, Pi-hole
and AdGuard Home, how to free the port, and WARDA_DNS_LISTEN on one
address. The same form explains a port below 1024 without the right to
use it ("no right to listen on port …: a port below 1024 needs root or the
capability NET_BIND_SERVICE", with Docker user: "0:0" and cap_add
NET_BIND_SERVICE as in the file) and an address that is not one of the
machine ("… is not an address of this machine"). With restart: unless-stopped Docker starts Warda again with a growing delay (up to
about a minute): docker logs warda shows the message at each start.
Without Compose#
docker run -d --name warda --restart unless-stopped \
-p 192.168.1.2:53:53/udp -p 192.168.1.2:53:53/tcp \
-p 192.168.1.2:80:80 -p 192.168.1.2:443:443 \
-e TZ=Europe/Paris -e WARDA_ADVERTISE_ADDR=192.168.1.2 \
-v warda-data:/var/lib/warda \
ghcr.io/warda-dns/warda:latestFrom 0.7.9 the image of the customers is on GitHub:
ghcr.io/warda-dns/warda, tags X.Y.Z and latest
(packaging/docker-compose.yml names it), pulled without an account once
its package is public (see operations).
Replace 192.168.1.2 with the local network address of the host. Publish the
ports on that address only: published on every address, Docker relays IPv6
connections from its own gateway address, which Warda would take for a local
client, so Warda could answer the internet.
Test it:
docker exec warda warda lookup -server 127.0.0.1:53 example.com
curl http://192.168.1.2/healthz
docker exec warda warda setup-infoThe image has no shell and runs as a non-root user. It supports linux/amd64
and linux/arm64 (Raspberry Pi 4 and later with a 64-bit system).
In a bridged container Warda only sees the IP addresses of the devices, and
WARDA_ADVERTISE_ADDR gives the host address for the name warda. To
identify the devices by hardware address, which survives address changes,
use host networking: packaging/docker-compose.yml does it. It runs Warda
as the root user of the container (user: "0:0") with every capability
dropped but NET_BIND_SERVICE (ports below 1024), NET_RAW (ping,
traceroute, LLDP), DAC_OVERRIDE and FOWNER (the data directory of the
image, owned by 65532), with no-new-privileges and a read-only file
system (no tmpfs: Warda writes only in /var/lib/warda).
For the high availability, keepalived runs in a
container of its own on the network of the host, beside Warda: see the
commented service keepalived of packaging/docker-compose.yml.
The image is not updated by Warda: the System page tells when a new version
is published (see Updates); with Compose, its number written
in the line image:, docker compose pull && docker compose up -d
installs it (with latest, the same commands take the newest).