Self-hosting a node
A Tabbify node is a supervisord orchestrator that joins the private mesh, advertises which runtimes it can offer, and spawns a runner per app. The coordinator address is baked into the binary, so bringing a node online is one command — the machine registers itself and the node API starts routing apps to it. Here are the three ways to do it.
Prerequisites
The mesh transport is userspace WireGuard over a TUN device. Every node needs:
NET_ADMINcapability and/dev/net/tun— to open the TUN device and join the mesh./dev/kvm— only if you want the supervisor to offer the Firecracker runtime.- A Docker socket — only if you want the Docker runtime.
A supervisor never escalates past what you hand it: it always offers wasm, adds firecracker when /dev/kvm is present, and docker when a daemon is reachable. Those become the mesh tags the node matches an app's runtime against.
NixOS: one rebuild
The turnkey path is nixos/tabbify-node.nix. Copy it to /etc/nixos/, edit the one nodeName line, add the import, and:
sudo nixos-rebuild switch
That's it. The module loads the tun and kvm-intel modules (switch to kvm-amd on AMD), installs the host tools the runtimes shell out to (firecracker, iproute2, e2fsprogs, busybox, oras), and wires four systemd units: tabbify-fetch bootstraps the supervisor binaries + a Firecracker guest kernel over anonymous HTTPS on first boot, tabbify-supervisor runs the node (Type=notify, Restart=on-failure, runs as root for TUN + KVM), tabbify-appsrv serves staged artifacts on :9000, and tabbify-update does a health-gated self-update on demand (systemctl start tabbify-update). No AWS account, no config — the node joins and starts hosting.
Docker: one run
Same node, packaged. The lean image offers wasm; you opt into more capabilities by what you expose at launch:
docker run -d --name tbf-sup --restart=on-failure \
--device /dev/net/tun --cap-add NET_ADMIN \
-v tbf-state:/var/lib/tabbify \
tabbify-supervisor
Add --device /dev/kvm (with the :fc image) for Firecracker, or mount /var/run/docker.sock plus --network host for Docker apps. Point at a non-default coordinator with -e TABBIFY_MESH_COORDINATOR=http://host:8888; name it with -e SUPERVISOR_NAME=edge-fra-1. The tbf-state volume holds the mesh identity (mesh-identity.json), so a restart re-claims the same ULA. Drop the volume and the node rejoins as a fresh peer.
Prefer a declarative manifest? tcli node up -f node.toml renders the docker run from a node.toml ([backend] restart, [capabilities] firecracker/docker, [mesh] coordinator, [resources] cpus/memory_mb); add --dry-run to print the command without launching.
Verify and operate
The control API binds the peer ULA ([my_ula]:8730), not host loopback. Check the roster or run loopback dev mode:
curl -s http://<coordinator>:8888/v1/mesh/peers | jq '.peers[] | {ula,display_name,tags}'
docker run --rm -p 8730:8730 tabbify-supervisor --no-mesh --bind 0.0.0.0:8730
curl -s localhost:8730/health # {"status":"ok","firecracker":…,"docker":…,"ula":…}
What's RnD-grade
Be clear-eyed about the current state. Today's coordinators run --insecure-no-mtls and supervisors join plaintext — production join via mTLS cert / join token (TABBIFY_JOIN_TOKEN, see auth) is pending. In the Docker path supervisord is PID 1, so its death stops the container and every in-container runner; --restart gives cold recovery, not the host-process crash-survival you get from the systemd/NixOS path. Docker-over-host-socket apps (DooD) need --network host, and untrusted code belongs in Firecracker or WASM, not raw Docker. The tcli node up backend ships Docker only — Firecracker is wired through the NixOS and :fc Docker images. See the deploy pipeline for the end-to-end app flow.