Repository navigation
Mesh Containers
The published Coyote image runs the Coyote Mesh like any other Coyote: opt-in, per session, through the
same mesh: block. This page covers what the image gives you, what it deliberately does not, and how to mount the
two directories the mesh needs.
The root Dockerfile builds FROM docker/sandbox-templates:shell-docker, installs the prebuilt musl release tarball
and, from v0.10.4, a Reticulum daemon (rnsd, uv tool install "rns==1.5.2" under a uv-managed CPython 3.12), and
ends with USER 1000, WORKDIR /home/agent/workspace and
ENTRYPOINT ["/usr/bin/tini", "-s", "-g", "--", "/usr/local/bin/coyote-entrypoint"]. It sets
NO_PROXY=localhost,127.0.0.1,::1,172.17.0.0/16. There is no EXPOSE, no VOLUME, and no compose file in the
repository.
That is on purpose:
- The mesh is off by default, and the image is a Docker Sandboxes template whose users already mount the config dir.
- The only listener is
rnsdon127.0.0.1:4242inside the container, which Coyote dials over the container's own loopback. Nothing outside the container can reach it, so there is no port to publish; never publish4242. Other hosts are reached through a relay (COYOTE_MESH_RELAY, below), not by exposing this one. - No
VOLUMEis added because the two directories below are chosen by environment variable, not fixed paths.
Before v0.10.4 the image shipped only the Coyote binary and no daemon, so the shipped private 127.0.0.1:4242
default could not connect inside it.
The image runs rnsd for you. On every start the entrypoint starts it beside the main command (coyote, or the
sh/bash/path you pass, the Docker Sandboxes keep-alive included), and the shipped default
private 127.0.0.1:4242 dials it with no mesh configuration at all. Three environment variables shape the daemon:
| Variable | Effect |
|---|---|
COYOTE_MESH_RELAY=host:port |
Adds a [[Team Relay]] TCPClientInterface to your team relay or propagation node; this is how a container reaches anything outside itself. |
COYOTE_MESH_LAN=1 |
Adds the AutoInterface for link-local discovery. Only useful with --network host on Linux: the default bridge does not carry link-local multicast. |
COYOTE_MESH_RNSD=0 |
Starts no daemon. Point mesh.interfaces at a relay yourself, as before v0.10.4. |
The daemon's config is rendered once, from /opt/coyote/reticulum.config.tmpl into ~/.reticulum/config
(enable_transport = True, [logging] loglevel = 4, the loopback [[Coyote Sessions]] listener with
ingress_control = No, plus the optional stanzas above), and never overwritten: with ~/.reticulum on a volume, a
later change to the variables does not reach the file; edit it there or remove it. A mounted ~/.reticulum has to be
writable by uid 1000 like the two directories below, or the entrypoint prints a WARNING: and starts no daemon. The
daemon logs raw to the container's stderr (docker logs), and rnstatus inside the container attaches to it. A
daemon that fails to start never stops the main command; .mesh on failing with its interface error is where you see
it. The image works with --read-only given --tmpfs /home/agent/.reticulum --tmpfs /tmp.
A node behind a relay needs no published port. Mount a config dir and a cache dir, point the two variables at them, and name the relay:
docker run -it \
-e COYOTE_CONFIG_DIR=/home/agent/.config/coyote \
-e COYOTE_CACHE_DIR=/home/agent/.cache/coyote \
-e COYOTE_MESH_RELAY=relay.example:4242 \
-v <host-config>:/home/agent/.config/coyote \
-v <host-cache>:/home/agent/.cache/coyote \
darkalex17/coyoteThe paths are yours to pick; the environment variables are what matter. chown 1000:1000 both host directories
first: the image runs as uid 1000, and the first .mesh on creates mesh/identity.key owner-only (0600) inside a
0700 mesh/ directory, which fails on a root-owned bind mount.
Without COYOTE_MESH_RELAY the container is a mesh of one: its daemon hears nothing but the sessions inside it. A
daemon on the Docker host written by scripts/mesh-relay.sh listens on the host's loopback only
([[Coyote Sessions]] binds 127.0.0.1), so it is not reachable from a container either; name the team relay or the
propagation node, or add a listener on a host address to that daemon's config yourself.
Signals: docker stop sends TERM, which tini delivers to the main command directly; the entrypoint waits for it,
stops the daemon, and exits with the main command's code (143 for a command that dies of the TERM, coyote's own
otherwise). A Ctrl-C under docker run -it, or docker kill -s INT, reaches the main command and never the daemon,
which runs in its own session.
The config dir holds the identity and trust.yaml; the cache dir holds the peer table, knocks, correlations and
instance locks. Two containers sharing one config dir do start, since the lock a running node holds on the identity
key is shared; with different sessions they run as two destinations of one identity. What collides is one cache dir
together with one session: the instance lock is per session and per cache dir, so a second container using the same
session on the same cache dir is refused. The same session on two cache dirs is not refused: both containers
announce one destination, since the instance id lives in the session file under the shared config dir. Give each
container its own session, or .mesh on --fresh in one of them. Two separate identities need two config dirs,
exactly like two identities on one host (see Two sessions on one host).
"Private" is about reachability, not access control: the relay authenticates nobody, so anyone who can reach its port joins it and sees every announce. Keep the relay behind the firewall or VPN; Coyote's trust list is what gates status cards and messages.
The AutoInterface is IPv6 link-local multicast on :42671 (discovery 29716), which does not cross the Docker
bridge. It works only with --network host, and then only one process on the host can hold it, the container
included. In the image that process is the daemon: COYOTE_MESH_LAN=1 with --network host gives it the
AutoInterface, and sessions keep the loopback default; a type: lan entry in the same container would compete with
it for the port. Prefer a relay.
The image's NO_PROXY lists ::1 unbracketed but not [::1]. An in-container HTTP client that formats loopback as
[::1] still goes through the proxy. Reticulum uses plain TCP sockets, not HTTP, so this does not touch the mesh; it
touches HTTP tooling running in the same container. Add [::1] to NO_PROXY if you hit it.
Inside an sbx sandbox the same rules apply, with the sandbox's egress policy on top; see
Sandboxes.
The relay-and-store-and-forward daemon has its own image under
deployment/propagation-node.
It publishes 4242 and keeps its identity on a volume; a host's rnsd dials it with --relay, a container's with
COYOTE_MESH_RELAY=<node host>:4242, and a container running with COYOTE_MESH_RNSD=0 can point type: private at
it directly. What Coyote does and does not do with propagation nodes in this build
is on Mesh Deployment.
Like the relay, it authenticates nobody for transport or posting; its allowed list gates fetching only.