Every feature that ships today.
Grouped by what each one is for. The roadmap and design intent live in the handbook’s design notes. Everything below is real — you can install it right now.
Nodes
Two kinds of node. Which one you want depends on how the software is shipped, not on preference.
- Docker containers
- Anything shipped as an image. Starts in a second, costs tens of MB, runs FRR, SR Linux, XRd, cEOS, plain Linux hosts.
- QEMU virtual machines
- Anything shipped as a disk. Vendor appliances, Windows, BSD. Real savevm/loadvm snapshots. KVM where the host has it.
- Companion BIOS + CD-ROM per template
- Some appliances (NX-OSv 9000, cat9kv, vJunos-EVO, uccx) need a specific BIOS or a CD-ROM to boot. Attach both per template.
- Cold-plug NICs
- Every interface a node knows about boots with the guest as a real -netdev tap. Vendor appliances that enumerate PCI once at boot (NX-OS, PAN-OS) see all their interfaces.
- Editable templates
- RAM, CPUs, NIC model, disk bus, iface scheme, description — everything set at import time is a pencil-icon away in the palette.
- Bring your own images
- QEMU images are yours. The catalogue records where each one came from so you know where to look. No vendor images ship with Labtris.
- GNS3 registry importer
- 228 GNS3 appliance definitions import as templates. Correct RAM, NIC model, disk bus, console type, download URL — no re-authoring per image.
- First-boot bootstrap as data
- A template can carry a list of wait-for-this-prompt, type-that steps. On a node's first boot they are driven against the serial console, so the guest is logged in and configured before anyone opens a terminal. The vendor problem vrnetlab solves with a Python file per appliance, done as a regex and a string — for custom templates, with a short list of recipes so far.
- Pull an image before the class starts
- Download, extract and convert run in the background with a progress bar, from the palette, the CLI or an MCP tool. Every catalogue row says whether it is already on this host.
Wires and segments
The kernel's own forwarding path — the same bridges, veths and qdiscs you would configure on a real box.
- Point-to-point links
- Drag from one port to another. Creates a veth pair, puts one end in each node's namespace, joins them through a bridge.
- Internal bridge segments
- Plain L2, lab-only. No host address, no uplink, no way out.
- Open vSwitch segments
- An OVS bridge instead of a Linux one, with an OpenFlow table you can program and OVSDB behind it. ovs-ofctl add-flow to drop or redirect, or point it at a Faucet or Ryu controller. Comes up in standalone fail-mode so it learns like a switch until you say otherwise. Needs the openvswitch kernel module on the host; impairment, capture and VLANs act on Linux-bridge devices and are not wired to it yet.
- NAT segments with DHCP
- Bridge with gateway + masquerade + DHCP server + reservations. Guests reach the outside; nothing outside reaches in.
- Cloud (host NIC)
- Attach the lab to a real wire. Picks a NIC or a host-owned bridge. Refuses the management NIC unless explicitly overridden.
- VXLAN across hosts
- One segment spanning several machines, so a topology bigger than one host is still one topology.
- VLAN filtering
- 802.1Q or QinQ. Access ports and trunk ports as first-class objects, re-applied every boot because a port is a fresh veth every time.
- netem impairment per direction
- Delay, jitter, loss, reordering, rate — applied to each direction's own tap. 200 ms one-way is a different experiment from 200 ms symmetric.
- Admin-down that unplugs the cable
- QMP set_link alongside the host tap flip, so the guest driver raises the expected link-down interrupt.
Data-centre and AI fabrics
The shapes a GPU cluster is wired in, generated rather than dragged, with a programmable data plane underneath. RDMA works on a new enough kernel; the Ultra Ethernet transport is newer still and says where it stops.
- Topology generator
- labtris lab generate --pattern spine-leaf | rail-optimised | fat-tree. Fat-tree is k-ary Al-Fares: (k/2)² core, k²/2 aggregation, k²/2 edge, k³/4 hosts. Sixty to eighty nodes in about a second, and they land as ordinary rows — delete a node or redraw a wire straight afterwards. API, CLI and MCP only; there is no canvas button for it yet.
- bmv2 P4 switches
- A P4 node is a p4lang simple_switch container whose program is per-node and survives restarts, wipes and snapshots. Four curated programs ship: L2 forwarding, ECMP over a five-tuple hash, ECN marking above a queue threshold, and UEC-style packet trimming. Swap one in with labtris node p4 set <id> --builtin ecmp. No P4Runtime or simple_switch_CLI yet, so tables are populated in the program's apply block.
- Generated BGP EVPN underlay
- --with-bgp-evpn writes FRR config into every generated node: loopback router-ids, unnumbered eBGP on each spine-leaf pair, l2vpn evpn with advertise-all-vni. Spine-leaf, rail-optimised (per-rail iBGP islands) and fat-tree each get their own AS layout. Pair it with --spine-kind frr --leaf-kind frr — the generator defaults spines to bmv2, which carries no FRR to configure.
- Three demo fabrics, pre-wired
- spine-leaf-2x2-evpn, rail-optimised-2x2 and fat-tree-k2 ship as loadable pods with their BGP configs already populated. A couple of KB each, because a cold Docker pod carries image references rather than image bytes. labtris lab load fat-tree-k2.pod.tar.gz --name my-first-fabric.
- ECN marking on any link
- A RED qdisc under netem and tbf marks CE while the queue average sits in a band and drops only above it — the signal DCTCP and DCQCN actually read. The datacenter preset is 25 Gbps with a 50–150 KB marking band, which is a leaf-spine hop rather than the lan preset's 2 ms.
- Per-priority DCB queueing
- An eight-band prio qdisc with its own RED-plus-ECN on the priorities you name, stacked under any rate and delay you already set. This is the queueing behaviour PFC produces — class isolation and per-class early marking — not wire-level PAUSE frames, which veth cannot emit.
- Link groups for multipath
- Model a 4×100G bundle or a dual-homed host as one object: POST a group with a member count and a hash policy. Members are ordinary point-to-point links, so a routing daemon in the guest does the ECMP. REST only today — Labtris itself does no load-balancing, and the canvas draws the members as separate wires.
- RDMA over soft-RoCE
- An rdma-host profile brings rdma-core, ibverbs-utils and perftest, and Labtris binds /dev/infiniband into the node so each rxe device appears as you create it. ib_send_bw between two lab nodes measured 713.85 MB/s average (814.91 peak) on a Labtris bridge, with 64 KB messages on a mainline 7.2 kernel. Needs Linux 7.1 or newer on the host — that is where rxe gained per-namespace UDP 4791 sockets, and before it the transfer connects and moves nothing. `ss -lun | grep 4791` is the check; host.capabilities reports it too. Ubuntu 24.04 and 26.04 both ship kernels that are too old.
- Ultra Ethernet hosts
- The uet-ref node builds the Ultra Ethernet Consortium's own reference provider (MIT) from a shipped Dockerfile: SES with tagged and untagged messages and RMA, PDS, TSS with AES-GCM, all four delivery modes (RUD, ROD, RUDI, UUD) and a partial implementation of UET congestion control. It needs no hardware — a raw Ethernet socket on an ordinary veth is enough. UET is its own transport on IP protocol 253, and Labtris ships the reference Wireshark dissector so a capture decodes 103 fields instead of reading Unknown (253). Upstream's own gaps: multi-path delivery is not fully supported and there is no key exchange. Two older kinds remain — ue-sim (an ns-3 simulator) and ue-stack, our own proof of concept, which has no congestion control and no multipath; prefer uet-ref for anything you intend to measure.
Getting inside a node
Serial from the first byte of boot, container shells over a real PTY, VNC and RDP for graphical guests (no RDP on a native 26.04 install).
- QEMU serial console
- Full boot log, kernel messages, single-user mode. Raw bytes — no CRLF injection, no control-frame contamination.
- Container PTY console
- Real terminal, docker exec, resizable, correct colour handling.
- VNC + RDP through Guacamole
- HTML5, no plugin. Force-shared display so a same-tab reconnect works. Tunnel flushed before reopening. One gap: on a native Ubuntu 26.04 install there is no RDP. Ubuntu stopped packaging Guacamole after 24.04, so Labtris builds guacd from source there, and 1.6.0 does not compile against FreeRDP 3 — the only FreeRDP 26.04 ships. VNC, SSH and telnet are unaffected, and QEMU consoles are VNC. The compose install pulls a prebuilt guacd that has RDP.
- Dock, split, pop-out
- Terminals dock at the bottom, split side by side, or pop out to their own window. Layout persists across reloads.
Seeing what happened
Capture, address discovery, and the honest "we don't know" that keeps you from debugging a healthy node.
- tcpdump on any link with a BPF filter
- Filter at capture time, not client-side. Downloads as pcap.
- Wireshark GUI streamed to your browser
- The real Wireshark binary runs on the lab host under Xvfb + x11vnc, tunneled through the same guacd. Real dissectors, no plugin.
- Addressing overlay
- Every running guest is asked what addresses it has. Three states — stopped, no agent to ask, no address — because conflating them sends people to debug a healthy node.
- Live traffic on the wires
- A Traffic toggle labels every link with rx ↔ tx, polled every two seconds and read from the kernel's own IFLA_STATS64. A hundred links cost one round-trip per poll, anything under 1 kbps goes unlabelled so a dense fabric stays readable, and a 32-bit counter wrap reports no rate rather than a bogus one.
Ready, captured, carried elsewhere
A lab is not just a drawing. It can tell you when it is genuinely up, and it can leave the machine it was built on.
- Ready hooks
- YAML on the lab saying what ready means: wait for a regex on a serial console, run a command and check its exit code, ping something, or expect an HTTP status. They fire once every node is running and each one's output is kept and streamed to the browser. Sequential, one-shot per apply, and the command, ping and http kinds need a container — a QEMU guest has serial_wait.
- A whole lab as one file
- labtris lab snapshot writes topology, hooks, templates and every node's disk into a single tar.gz that any other Labtris host can load as a new lab. Loading never overwrites — it always creates a new lab with fresh ids.
- Hot snapshots that resume
- Snapshot a lab while it runs and the archive carries live QEMU memory and committed container filesystems, so loading it elsewhere resumes at the moment you captured rather than rebooting. Cold archives are single-digit gigabytes, hot ones tens. Handing someone a broken state as a file beats describing it.
One host, many nodes
The ceilings are not CPU or RAM. They're the knobs Ubuntu ships stock. Labtris tunes them.
- KSM (Kernel Samepage Merging)
- Tuned in, aggressive, measured 8.5:1 dedup ratio across a mixed VM workload — 62 GiB deduplicated on the reference host.
- Raised file descriptors
- 1024 → 1048576. Each tap + monitor + console eats FDs; stock defaults die at a few hundred nodes.
- Raised ARP table
- 128 / 512 / 1024 → 4× each. Stock defaults produce silent, intermittent L2 blackholing on big topologies.
- Tuned swappiness + hugepages
- Swapping guest RAM destroys density. THP in madvise mode; static hugetlb reserved only where profiles ask for it.
- Multi-host with VXLAN overlay
- Register a second host, wire nodes across them, VXLAN builds the tunnel.
Accounts and access
Two roles, add and remove from the UI, admin-only. Every API and WebSocket path authenticates.
- First-run admin creation
- First visit asks you to create an administrator. Until you do, anyone who can reach the port can claim it.
- Two roles: admin, user
- Admins do everything and see every lab. Users see their own. Ownership is what protects.
- Settings → Users
- Add, remove, list, promote. From the UI, admin-only.
REST, WebSocket, MCP
Everything the interface does, it does over the same HTTP API you can use. There is no private back channel.
- REST + OpenAPI at /docs
- Every route, every field, with a Try-It button. Same URLs the browser calls.
- WebSocket for all state
- Canvas, console, capture, assistant, lab-state. Never poll. All authenticated at the handshake.
- MCP server
- 46 tools — looking, building, running, investigating, removing, plus VNC screenshots, P4 programs, pods, hooks and link stats. Point Claude Code, Cursor, or your own MCP client at it.
- Legacy .unl importer
- Import EVE-NG topologies. Templates map onto the QEMU catalogue; vendor appliances become placeholders with warnings.
- Containerlab .clab.yml importer
- Kind + image inheritance handled the way containerlab does it. Bridge / ovs-bridge nodes become segments.
- A labtris CLI on the same API
- labtris lab, node, pod and system verbs with -o json or yaml, and shell completion. labtris login stores a token for seven days; set LABTRIS_TOKEN and LABTRIS_API_URL instead and a CI job needs no login at all.
An LLM in the room
A chat pane in the interface that can drive the lab. Your key stays in your browser, or the server holds it — your choice.
- Bring your own key
- OpenAI-compatible base URL, key, and model in Settings → Assistant. Conversation runs client-side against your provider. Labtris never sees the key.
- Server-held key fallback
- Set LABTRIS_LLM_API_KEY in the env and every user shares one instance and one bill. Browser prefers its own key and falls back to the server's.
- Streamed tokens + tool visibility
- Text arrives token-by-token. Each tool call renders its own bubble with the arguments and result — no folded-away batch at the end.
- Consent for destructive tools
- delete_lab, delete_node, stop_node, create_network, join_network pause the loop and ask before firing.
- Attach images and PDFs
- Vision-capable models see attachments directly. PDFs render server-side to per-page PNGs (first five pages) so the model always sees the same shape.
- Eyes and hands on a graphical guest
- An assistant can screenshot a VM's display, crop it, overlay a coordinate grid, click an absolute pixel and type a key combo — so installing Ubuntu Desktop or walking a vendor wizard is work a model can do. Screenshots come from a cached framebuffer in about a tenth of a second, and an OCR read turns a login prompt that costs roughly 1500 vision tokens as an image into about 15 as text.
Running the server
curl | bash on Ubuntu 24.04 or 26.04, one compose file on any other distribution, or an ISO for bare metal.
- One-line install
- curl -fsSL https://labtris.com/install | sudo LABTRIS_TOKEN=… bash on a fresh Ubuntu 24.04 or 26.04, with the token from your access invitation. Five minutes, finishes with a summary block. On 26.04 it uses the distribution's own Python 3.14 and installs a guacd built from the Apache release tarball, because Ubuntu stopped packaging Guacamole after 24.04.
- Docker, on any distribution
- One compose file, piped straight into docker compose — nothing cloned and no registry login. Verified on Ubuntu 24.04 and 26.04; other distributions are expected to work but are not ones we run. Every Labtris process shares one private container namespace, so lab bridges, taps and nftables rules land there and the host's own networking is untouched. Needs a working /dev/kvm.
- One-command upgrade
- curl -fsSL https://labtris.com/upgrade | sudo LABTRIS_TOKEN=… bash moves a native install to the latest release. It resolves the newest release tag rather than tracking main, runs pg_dump before alembic touches the schema, and verifies the services came back and the interface answers — printing the journal command, the previous commit and the dump path if they did not. Labs, pods, images and the database carry across. Compose installs use docker compose pull && up -d instead.
- Bootable ISO for air-gapped installs
- 3.8 GB image with every package, wheel and the built interface baked in. Nothing fetched during install. Installs Ubuntu 24.04, whose kernel is below the 7.1 soft-RoCE needs, so the RDMA lab is the one thing that will not transfer until you update it.
- Backup and restore
- Every lab plus the instance's settings as one .tar.gz. Restore is additive — never silently deletes.
- Google Drive integration
- OAuth device flow, drive.file scope only, refresh token stored server-side.
- Feedback flag
- Click a broken thing, describe what should have happened. Report carries the element, its state, the viewport, and a rolling buffer of console errors captured before you clicked.