Native Ubuntu install
- When to pick native
- What you’ll end up with
- Prerequisites
- Run the install
- Verify
- Operating it
- Sandboxing
- Uninstall
- Troubleshooting
Install dnsmesh-node directly on Ubuntu / Debian as a systemd unit,
fronted by Caddy for auto-TLS. No Docker daemon required.
This is an alternative to the Docker recipe at Deployment → Docker, aimed at operators who don’t want a container runtime on the host. The Docker path stays canonical for cluster deploys; native is a single-node choice.
Both paths are first-class. Pick the one whose ergonomics match how you operate the rest of your infrastructure. Trade-offs are summarized at the end of this page.
When to pick native
- Tiny VMs. Docker daemon idles around ~150 MB; the native path
runs in ~50 MB. On a
s-1vcpu-512mbors-1vcpu-1gbDroplet that difference matters. - Distro-native operations. systemd lifecycle, journald logs, apt-pinned Caddy.
- No container runtime. Some hardened hosts disallow the kernel features Docker needs.
When to pick Docker instead:
- Cluster deploys.
docker-compose.cluster.ymlis the path of least resistance for a 3-node federated setup. - Reproducibility. Pinning
:X.Y.Ztags is more rigorous than pinning apip install dnsmesh==X.Y.Zagainst a moving distro Python.
What you’ll end up with
- A
dnsmeshsystem user with no login shell. - The package installed in a venv at
/opt/dnsmesh/venv. - A systemd unit
dnsmesh-node.servicerunning as thednsmeshuser withCAP_NET_BIND_SERVICE(the only capability it needs, to bind UDP 53). - Caddy fronting the HTTP API on TCP 443 with auto Let’s Encrypt.
- Persistent state at
/var/lib/dmp/dmp.db. - ufw rules for
UDP 53,TCP 80,TCP 443,UDP 443(when ufw is active).
Prerequisites
- Ubuntu 22.04 LTS or newer / Debian 12 or newer.
- A DNS A (and ideally AAAA) record pointing at the machine’s public IP. Caddy uses the HTTP-01 ACME challenge, so the hostname has to resolve here before the script runs.
- Open inbound:
UDP 53,TCP 80,TCP 443,UDP 443. The script handlesufwautomatically; cloud-firewall layers (DigitalOcean Cloud Firewall, AWS Security Group, etc.) you set yourself.
Run the install
SSH into the machine as root (or a sudoer):
curl -fsSL https://raw.githubusercontent.com/oscarvalenzuelab/DNSMeshProtocol/main/deploy/native-ubuntu/install.sh \
| sudo DMP_NODE_HOSTNAME=dmp.example.com bash
What it does, in order:
- Sanity checks (root, apt, no prior install at the standard paths).
- Installs
python3,python3-venv,caddy(from the upstream apt repo for fresh versions on LTS),ufw,openssl. - Creates the
dnsmeshsystem user and the directory layout. pip install dnsmeshinto/opt/dnsmesh/venv.- Generates a 256-bit operator bearer token (or reuses
DMP_OPERATOR_TOKENif you set it). - Writes
/etc/dnsmesh/node.env(mode 0640, root:dnsmesh). - Drops the systemd unit, runs
daemon-reload. - Configures
/etc/caddy/Caddyfilewith the hostname. - Opens the four required ports on
ufwif active. systemctl enable --now dnsmesh-nodeand reloads Caddy.- Waits up to 30 seconds for
/healthto return ok.
The token is the only secret gating publish writes. It’s written to
/etc/dnsmesh/node.env (mode 0640) and printed once; save it.
Verify
From any machine:
curl -sI https://dmp.example.com/health
# HTTP/2 200
Caddy obtains the TLS cert on first request to port 443, so the very first call may take a few seconds. Subsequent calls are immediate.
To prove DNS works:
dig @dmp.example.com test-resolution.invalid TXT +short
# (empty response, but no SERVFAIL)
Operating it
Service control
sudo systemctl status dnsmesh-node
sudo systemctl restart dnsmesh-node
journalctl -u dnsmesh-node -f
The startup log includes the discovery surface (whether the heartbeat layer is on, and where peers can see this node):
INFO dmp.server.node: DMP node up: dns=0.0.0.0:53/udp http=127.0.0.1:8053 db=/var/lib/dmp/dmp.db peers=0
INFO dmp.server.node: discovery: heartbeat disabled (this node is private). Set DMP_HEARTBEAT_ENABLED=1 + ...
Upgrade
sudo /opt/dnsmesh/venv/bin/pip install -U dnsmesh
sudo systemctl restart dnsmesh-node
Persistent state survives the upgrade; only the venv contents change.
Pin a specific version with dnsmesh==X.Y.Z.
Token rotation
sudo nano /etc/dnsmesh/node.env # replace DMP_OPERATOR_TOKEN
sudo systemctl restart dnsmesh-node
Existing per-user tokens stored under ~/.dmp/tokens/<host>.json on
client machines need to be re-issued (run dnsmesh register --node <host>
from the client side).
Enabling multi-tenant auth
Add to /etc/dnsmesh/node.env:
DMP_AUTH_MODE=multi-tenant
DMP_REGISTRATION_ENABLED=1
Restart. See Multi-tenant deployment for the full operator surface.
Enabling discovery
DMP_HEARTBEAT_ENABLED=1
DMP_HEARTBEAT_SELF_ENDPOINT=https://dmp.example.com
DMP_HEARTBEAT_OPERATOR_KEY_PATH=/etc/dnsmesh/operator-ed25519.hex
Generate the operator key (32 random bytes hex, mode 0440 root:dnsmesh):
openssl rand -hex 32 | sudo tee /etc/dnsmesh/operator-ed25519.hex >/dev/null
sudo chown root:dnsmesh /etc/dnsmesh/operator-ed25519.hex
sudo chmod 0440 /etc/dnsmesh/operator-ed25519.hex
sudo systemctl restart dnsmesh-node
Once on, your node appears at
https://dmp.example.com/nodes (HTML view) and
https://dmp.example.com/v1/nodes/seen (JSON feed). Add it to the
canonical aggregator at
/directory/ by PR-adding the URL
to directory/seeds.txt.
Sandboxing
The systemd unit applies the standard hardening directives a normal
unprivileged process doesn’t need to bypass: NoNewPrivileges,
ProtectSystem=strict, ProtectHome, ProtectKernelTunables,
ProtectKernelModules, ProtectKernelLogs, ProtectControlGroups,
RestrictAddressFamilies, LockPersonality, RestrictRealtime,
RestrictSUIDSGID, MemoryDenyWriteExecute.
ReadWritePaths=/var/lib/dmp is the only writable path on disk.
The single capability the service holds is CAP_NET_BIND_SERVICE,
needed to bind UDP 53. Everything else runs as the unprivileged
dnsmesh user.
Uninstall
sudo systemctl disable --now dnsmesh-node
sudo rm /etc/systemd/system/dnsmesh-node.service
sudo systemctl daemon-reload
sudo rm -rf /opt/dnsmesh /etc/dnsmesh
# Optionally also: sudo rm -rf /var/lib/dmp
sudo userdel dnsmesh
Troubleshooting
TLS cert never issues. Either DNS isn’t pointing at this host,
or TCP 80 is blocked at the cloud-firewall layer (Caddy’s HTTP-01
challenge happens on port 80, even when the cert serves 443). Run
dig +short dmp.example.com from outside and check it resolves to
this machine’s public IP. Then journalctl -u caddy for the
acme-issuer logs.
systemctl status dnsmesh-node shows the unit failed to bind UDP
53. The AmbientCapabilities=CAP_NET_BIND_SERVICE line in the
unit handles this on systemd ≥ 229 (anything LTS-supported). If it
still fails, check that the kernel hasn’t disabled ambient
capabilities, or fall back to running on a non-privileged port and
using iptables to redirect 53.
/health returns 502 from Caddy. dnsmesh-node isn’t listening
on 127.0.0.1:8053. sudo systemctl status dnsmesh-node and
journalctl -u dnsmesh-node will say why.
pip install dnsmesh fails on a fresh Droplet. The cryptography
- argon2-cffi wheels usually ship for amd64 / arm64 + the major
glibc lines. If your distro ships a too-new openssl that breaks the
wheel, install
build-essential libssl-dev libffi-devand retry.