Mesh

The private overlay network that bonds Nodes so they talk over a private address.

Mesh

A Mesh is a private overlay network that bonds two or more Nodes so they reach each other over a private address instead of the public internet. The headline case: marry an app Node to a data Node so your app talks to Postgres over a 10.10.0.x address that never leaves your fleet — exactly like the live .207 ↔ .211 database bridge that runs today, but provisioned in one click instead of by hand.

This page explains the model and is honest about what is live today versus what is modeled and waiting on execution wiring. For the vocabulary of Nodes, Racks, and Slots that Mesh builds on, read Slots first.

Why you want a private overlay

By default, a Node reaches another Node the same way anyone on the internet would — over its public IP. That is fine for serving a website, but it is the wrong path for private traffic like a database connection: it is exposed, it depends on public firewall rules being exactly right, and it crosses the open network.

A Mesh gives the bonded Nodes a second, private network on top of the public one — a WireGuard overlay. Each Node in the Mesh is assigned a private address out of the Mesh's subnet (default 10.10.0.0/24), and the Nodes route to each other over that address. What this enables:

  • Private node-to-node traffic. An app Node reaches a data Node at 10.10.0.2 rather than a public IP.
  • Database over the mesh. Point your app at the data Node's overlay address so Postgres (or any private service) is only reachable inside the Mesh, not from the internet.
  • A stable private address. The overlay IP is assigned by the control plane and stays put, independent of the Node's public IP.

The vocabulary

You only need three words.

TermWhat it means
MeshA private overlay network (a WireGuard network) that bonds Nodes. It has a name and a subnet (an IPv4 /24, default 10.10.0.0/24).
MemberA Node inside a Mesh. Each member gets a private overlay IP from the Mesh subnet, a role, and a status.
RoleWhat a member is for. One of app, data, cache, worker, edge, or other. Roles are labels for how you read the board — they don't change the networking.

A member's status is one of pending, joined, or offline. A freshly added Node starts pending — it is modeled and addressed, but the tunnel is not up yet (see What's live today).

The Mesh page

The Mesh page lives in the app at /mesh. It is gated on the fleet.view privilege — without it, the page shows a locked state. Managing Meshes (creating, bonding, tearing down) additionally requires the fleet.manage privilege; if you only have fleet.view, the board is read-only.

At the top, three stats summarize your overlays:

StatWhat it counts
MeshesHow many overlay networks you have.
Bonded nodesTotal members across all Meshes.
Modelive or modeled — see below. Today this reads modeled.

Below the stats, the Mesh board lists each Mesh as a card showing its name, subnet, and members. Each member row shows its role, Node name, overlay IP, and status. If you have fleet.manage, each row has a control to remove that Node, and each card has a Tear down button to delete the whole Mesh.

Marrying two Nodes

The one-click flow is Marry two Nodes, at the bottom of the page (visible only with fleet.manage). You pick Node A and Node B from your fleet, and the platform:

  1. Creates a new Mesh named A ⇄ B on the default 10.10.0.0/24 subnet.
  2. Adds both Nodes as members, assigning each the next free overlay IP (10.10.0.1, 10.10.0.2, …).
  3. Records the Node roles (an app Node stays app, a data Node stays data, and so on).

If either add fails, the whole Mesh is torn down so you are never left with a half-formed overlay. You can't marry a Node to itself, and a Node can't be added to the same Mesh twice.

You can also build a Mesh up piece by piece: create an empty Mesh, then add Nodes to it one at a time. Adding a Node always claims the next free address in the subnet, and a /24 holds up to 254 members before it is full. Only real fleet Nodes are offered — demo/seed Nodes are filtered out of the picker.

What's live today

Live now:

  • The Mesh model and address assignment — creating a Mesh, bonding Nodes, assigning non-colliding 10.10.0.x overlay IPs, roles, and statuses (control-plane/web/lib/mesh/).
  • The meshes and mesh_members database schema (migration 0004-mesh), plus WireGuard key columns and mesh-join / mesh-leave job kinds (migration 0034-mesh-live).
  • The read/manage board at /mesh: view Meshes and members, marry two Nodes, add or remove a member, and tear down a Mesh — all gated by fleet.view / fleet.manage.
  • Agent WireGuard provisioning — marrying or adding a Node enqueues mesh-join jobs; the agent writes /etc/wireguard/wg0.conf and runs wg-quick up. Member status flips to joined when the job succeeds (meshLive() === true). Requires wireguard-tools on the Node.
  • A dual backend: the live Postgres tables when the control plane is connected to its database, and a seeded in-memory set otherwise.

Overlay health (observed monitoring)

Whatever the model says, the Mesh page also shows what each agent actually sees on the wire. Every heartbeat carries the node's live WireGuard peer state — peer public keys, endpoints, overlay routes (allowed-ips), last-handshake time, and transfer counters (never private or preshared keys). The Overlay health section renders one row per peer:

  • healthy — handshake within the last 3 minutes (an active link re-handshakes about every 2).
  • idle — 3–15 minutes; could be a quiet link, not alarming on its own.
  • down / never — a long-stale or never-completed handshake. This is the failure that hides behind two green nodes: both boxes heartbeat fine while the tunnel between them is dead. It also raises a fleet alert (<node>: mesh link to <route> looks down), so it reaches the alerts surface, not just this page.

Requires agent v0.6.0+ on the node; a node with no wg interface simply doesn't appear.

  • Slots — the Node / Rack / Slot vocabulary the Mesh bonds together.
  • Load Balancing — the mesh overlay is also one of the two backends a load-balancer pool can use (a Traefik front distributing over the private overlay).