Skip to content

Deploy a daemon

Every machine you connect runs one daemon, dedmeshd. It makes a single outbound connection to the Console and needs no inbound firewall rules and no special privileges: root is used once, to install it as a service that runs as an unprivileged user.

The easiest path is the Console’s Connect a host wizard (on the Hosts page). It walks three steps:

  1. Name the host. The Console mints a single-use join token for it.
  2. Run the command it generates on the machine. The command embeds the join token and the Console’s CA fingerprint, so there is nothing to edit.
  3. Verify: the wizard watches until the host enrolls and shows its connection transport and discovered containers.

The generated command is shown below with placeholders so you know what you are running. Prefer copying the exact command from the wizard.

The wizard installs a boot-started systemd service:

Terminal window
curl -fsSL https://console.dednets.com/install.sh | sudo sh -s -- --system \
--console console.dednets.com:9443 \
--name myhost \
--token dn_... \
--ca-fingerprint sha256:...

This one must run as root. It writes a unit under /etc/systemd/system, creates the dedmesh service account, and (see below) may hand that unit the Docker socket’s group. The service itself still runs as the dedicated unprivileged dedmesh user, starts at boot, and keeps everything (config, credentials, and a self-updatable copy of the binary) under /var/lib/dedmesh-myhost. Root is for the install, not for the daemon.

Run it without root and nothing happens: the installer refuses before it downloads anything, so the machine is left untouched and the single-use join token is not spent. It prints the exact line to re-run, and if a terminal is attached it offers to re-run under sudo for you.

Three variants, depending on the access you have on that machine:

your session there what to run
a normal account with sudo the command above, as written
already root (many minimal cloud and container images have no sudo at all) the same line with | sudo sh changed to | sh
ssh myhost '<command>', non-interactive sudo cannot prompt for a password without a terminal and fails with “sudo: no tty present”. Use ssh -t, a NOPASSWD sudoers rule, or log in as root. For unattended runs prefer NOPASSWD or root over ssh -t: with a TTY attached, the installer’s “existing installation found” prompt also becomes interactive
no root at all hand the line to whoever has it; there is no workaround, and the installer will not half-install

If this server also runs Docker (since v0.6.1): the daemon looks for a local engine on every install, so your containers show up in the Console too. Reading the socket takes membership in the group that owns it, and setup wires that into the unit for you:

# systemctl cat dedmeshd-myhost
SupplementaryGroups=docker

It follows whatever group actually owns the socket on this host (docker, dockerroot, or a site-specific name), and if Docker is installed but not started yet it uses the docker group. Setup prints what it granted.

Worth knowing before you accept it: that group is effectively root on the machine. Anything that can talk to the Docker socket can start a privileged container. It is granted automatically because this command already required root to run, because it is scoped to this one unit rather than added to the dedmesh account for everything, and because it is easy to take back:

Terminal window
# delete the SupplementaryGroups line, then:
sudo systemctl daemon-reload && sudo systemctl restart dedmeshd-myhost

The restart is not optional. daemon-reload rewrites the unit, but the process that is already running keeps the group it started with until it is replaced. Simpler still: add --no-docker to the install command to decline it up front, or re-run the installer with --no-docker to drop it. A host with no Docker gets no line at all.

If you install Docker after enrolling, re-run the install command (or sudo dedmeshd setup --system with --token omitted) so the unit is rewritten; until then the daemon logs a warning that the socket is present but unreadable.

If you build or download the binary yourself, a minimal TOML config replaces the environment variables:

console = "console.dednets.com:9443"
name = "myhost"
join_token = "dn_..." # first run only
state_dir = "dedmeshd-state"
ca_file = "ca.pem" # the Console CA, pinned
geo = "auto" # auto (default) | off - see "Where your hosts
# appear on the map" below. Since v0.11.2
[[apps]] # optionally export a service right away
name = "nginx"
protocol = "http" # http | tcp | udp
local_host = "127.0.0.1"
local_port = 8080
public = true # gets a public hostname, see the expose guide

Run it with dedmeshd -config myhost.toml, or let dedmeshd setup install it under systemd (Linux) or launchd (macOS) as your own user.

Since v0.10.1 an app may front a local unix-domain socket with local_path instead of local_host / local_port:

[[apps]]
name = "haunted"
protocol = "tcp"
local_path = "/run/user/1000/haunted/daemon.sock"

local_path must be an absolute path, requires protocol = "tcp", and cannot be combined with local_port or local_host. These apps are not health-probed (there is no port to dial), so they report health unknown. The socket’s own file permissions are what protect it on the host; reach it from another machine with a proxy port rather than publishing it.

A config with no [docker] section discovers containers by default (since v0.6.1), tolerating a machine with no engine at all. Add the section to change that:

[docker]
enabled = false # no discovery at all
# enabled = true
# inventory = false # discover, but do not list containers to the Console

Where your hosts appear on the map (since v0.11.2)

Section titled “Where your hosts appear on the map (since v0.11.2)”

The Console’s overview opens on a map of your fleet. To put a host on it, the daemon asks https://ip.thenets.org/ where it is and reports the answer home: the public IP that endpoint saw, the country, region and city it resolves to, and a latitude/longitude. That is the whole list. Nothing about what runs on the machine, what is on its network, or who uses it ever goes to that endpoint - the request carries no information about your host beyond the address it is coming from, which is the address any site you visit already sees.

It is on by default, and there are two independent ways to turn it off:

  • Per host, in the config above: geo = "off" - or DEDMESH_GEO=off in the environment of a host that runs with no config file.
  • Per account, in the Console: Settings → Data collection. Turning it off there covers every host you own at once, including ones you cannot easily edit.

Either switch stops the lookup and deletes what the daemon cached (<state_dir>/geo.json), so nothing is left behind. Hosts with no location are not dropped from the overview - they are listed beside the map as “location unknown”, because a host you cannot see is worse than a host you cannot place.

The lookup is not a poll. The daemon calls that endpoint on its first run, when the Console notices its public address has changed, and at most once every 12 hours otherwise. A restart re-queries nothing.

The wizard’s third step confirms enrollment automatically. Outside the wizard, the Hosts page shows the machine online with its connection transport, and the CLI agrees:

Terminal window
dednetsctl daemons
  • Location is on, and opts out two ways. Hosts report their public location so the Console can map them; geo = "off" (or DEDMESH_GEO=off) turns it off for one host, and Settings → Data collection for the whole account. (Since v0.11.2.)
  • Tokens are single-use. If a command fails before enrolling, generate a fresh command from the wizard.
  • Updates are opt-in. Daemons do not update themselves unless you turn the rollout on; the Hosts page shows each host’s version and update state. A host whose install directory is read-only cannot update itself; re-run the install command on it to move it.
  • Revoking a host (trash icon on its row) deletes it and everything it published; its certificate stops working on the next connection.
  • An agent can do this for you. With the MCP server connected, an AI agent enrolls the host and hands you the exact install command: Let an AI agent run DedNets. (Since v0.4.1.)