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.2rather 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.
| Term | What it means |
|---|---|
| Mesh | A 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). |
| Member | A Node inside a Mesh. Each member gets a private overlay IP from the Mesh subnet, a role, and a status. |
| Role | What 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:
| Stat | What it counts |
|---|---|
| Meshes | How many overlay networks you have. |
| Bonded nodes | Total members across all Meshes. |
| Mode | live 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:
- Creates a new Mesh named
A ⇄ Bon the default10.10.0.0/24subnet. - Adds both Nodes as members, assigning each the next free overlay IP (
10.10.0.1,10.10.0.2, …). - Records the Node roles (an app Node stays
app, a data Node staysdata, 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.xoverlay IPs, roles, and statuses (control-plane/web/lib/mesh/). - The
meshesandmesh_membersdatabase schema (migration0004-mesh), plus WireGuard key columns andmesh-join/mesh-leavejob kinds (migration0034-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 byfleet.view/fleet.manage. - Agent WireGuard provisioning — marrying or adding a Node enqueues
mesh-joinjobs; the agent writes/etc/wireguard/wg0.confand runswg-quick up. Member status flips tojoinedwhen the job succeeds (meshLive() === true). Requireswireguard-toolson 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.
Related
- 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).