Reference

Running in Docker

Two images — which one to run, and what is in each.

MeshHold ships two container images. They run the same daemon, from the same binary; what differs is which of other people's programs are installed beside it.

meshhold:<version>-<arch> meshhold:<version>-tools-<arch>
Base distroless static Debian slim
Size ~14 MB compressed many times that
Shell none yes
ffmpeg, LibreOffice no yes

Which one

Take the plain image unless you know you want the other. It is a storage, sync, chat and VPN node, which is most of what MeshHold is, and it is a distroless image with no shell in it — a smaller thing to keep patched and a smaller thing to break into.

Take the -tools image when you want that node to do work on the files it holds: convert documents, transcode video, make previews, or let office files reach the search index. Every program on the external tools page is already in it.

The two are interchangeable at any time. Nothing in a node's data depends on which image wrote it, so swapping the image and restarting is a supported move in both directions — and it is the quickest way to find out whether a job type you are missing is missing for want of a program.

Loading and running

The release publishes each image as a gzipped OCI tarball rather than to a registry:

gunzip -c meshhold-<version>-amd64.tar.gz | docker load
docker run -d --name meshhold \
  -p 7777:7777/udp -p 7778:7778 -p 8080:8080 \
  -v meshhold-data:/var/lib/meshhold \
  -v /srv/meshhold/config.yaml:/etc/meshhold/config.yaml:ro \
  -e MESHHOLD_PASSWORD='choose-something' \
  meshhold:<version>-amd64

7777/udp (QUIC) and 7778/tcp (the TCP leg, for peers whose UDP does not get through) are the ports peers reach this node on, and the ones worth forwarding. A masquerade you enable in node.obfs needs its own port published as well. 8080/tcp is the Web UI and REST API, and is not something to expose to the internet without thinking about it first.

/var/lib/meshhold is the volume: the metadata store and the encrypted blocks. The config file is deliberately not in it — it is a bind mount, or absent entirely, in which case the daemon comes up in limited mode and the Web UI walks you through setup.

MESHHOLD_PASSWORD is read once. If the store has no password hash yet, the daemon writes an Argon2id hash of it on first boot and ignores the variable from then on, so leaving it in a compose file does not keep resetting anything.

The -tools image runs identically — same ports, same volume, same entrypoint — under uid 65532, with HOME inside the volume so that anything a tool writes for itself survives a restart.

Checking what the node can do

docker exec meshhold meshhold tools --api http://127.0.0.1:8080

On the plain image this lists everything as missing, which is correct and expected. On the -tools image everything should read yes; anything that does not is worth reporting, because that image exists precisely so nobody has to install these by hand.

The same list is in the Web UI under Settings → Tools.

Building the images yourself

build_docker.bat

It expects the Linux binaries to already exist under distr/linux/<arch>/ from build_linux.bat, and deliberately does not build them itself: one Go binary, several packagers, no chance of the .deb, the .msi and the image drifting apart in toolchain version or build flags.

The tools image is slow to build — it installs LibreOffice — so SKIP_TOOLS=1 skips it, and it is built for amd64 only unless you ask for both:

set TOOLS_ARCHES=amd64 arm64