Skip to content

Mesh Containers

Alex Clarke edited this page Oct 10, 2026 · 8 revisions

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 image as published

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 rnsd on 127.0.0.1:4242 inside 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 publish 4242. Other hosts are reached through a relay (COYOTE_MESH_RELAY, below), not by exposing this one.
  • No VOLUME is 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.

Running with type: private

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/coyote

The 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.

type: lan inside a container

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 [::1] gap

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.

Sandboxes

Inside an sbx sandbox the same rules apply, with the sandbox's egress policy on top; see Sandboxes.

A propagation node container

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.

Start Here

Using Coyote

Model Providers

Tools & Function Calling

Agents

Knowledge & Automation

Configuration

Clone this wiki locally