Labtris
Install

One line on Ubuntu. One compose file anywhere else.

Three ways in, and one fact about your host picks it. The shell installer wants Ubuntu 24.04 or 26.04 and the ISO brings its own; Docker runs on any distribution, which is also the answer on Ubuntu 26.04 — resolute ships Python 3.14 and does not package Guacamole, so the native install has nothing to build against. No signup, no key, nothing to register.

Pick your host

Whichever you pick: the RDMA lab alone wants Linux 7.1+ on the host, where rdma_rxe gained its per-namespace UDP 4791 socket. Below that it starts and moves no data, and says so on first run. Detail and the check.

On 26.04 there is no RDP console. 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 consoles all work, and QEMU consoles are VNC. It only bites if you connect to a Windows guest over RDP, in which case use 24.04 or the compose install, which pulls a prebuilt guacd with RDP in it.

On a fresh Ubuntu 24.04 or 26.04 host

Runs about five minutes and finishes with the URL to open, where the config lives, and where the generated database password sits. Idempotent — re-run to upgrade.

curl -fsSL https://labtris.com/install | sudo bash
From a fork
The installer clones https://github.com/labtris/labtris. To install your own fork, set LABTRIS_REPO; if the fork is private, also set LABTRIS_TOKEN, which is sent as a header on the clone and written nowhere on the host.
What it does
  1. Refuses to run on anything but Ubuntu 24.04 or 26.04 x86_64 (override with LABTRIS_FORCE_OS=1).
  2. Installs git, curl, ca-certificates.
  3. Clones the repository to /opt/labtris, using the token for that one request.
  4. Hands off to packaging/install-labtris.sh, which sets up Postgres, the service user, systemd units, nginx, everything.
  5. Prints a summary block and writes /root/labtris-install-summary.txt.
Read the script first — recommended
Piping a shell script into sudo bash is a trust exercise. The installer at /install is get.sh from the repository, served here unchanged — kept tiny and readable on purpose. The real installer, packaging/install-labtris.sh, is in the clone it makes; read it there before the hand-off if you want to.
Upgrading

One command, with a backup taken first.

Goes to the latest release, dumps the database before it migrates anything, and tells you whether the result is actually serving. Your labs, pods, images and database are carried across, not replaced.

curl -fsSL https://labtris.com/upgrade | sudo bash

A specific release instead: … | sudo bash -s -- --to v0.12.0. To see what it would do and change nothing: … | sudo bash -s -- --check. The -s -- matters — without it bash takes the flags for itself rather than passing them on.

Compose install

The command above is for the native install. With the compose file, the image is the release, so upgrading is pulling a newer one. Back up the database first; the api container migrates it on start and a migration cannot be undone.

docker compose exec postgres pg_dump -U labtris labtris > labtris-$(date +%F).sql
docker compose pull
docker compose up -d

Labs, images and pods live in named volumes (postgres-data, labtris-state, labtris-images, dind-storage), not in the image, so a new image finds them where the old one left them. Container nodes keep running through the upgrade. VMs stop with the old api, because it is their parent process, and the new one starts again every node that was running, four at a time; set LABTRIS_RESTORE_RUNNING_NODES=false to leave them stopped instead. The first upgrade onto a compose file from before October 2026 restarts container nodes once too. To pin a release instead of latest, set LABTRIS_IMAGE=ghcr.io/labtris/labtris:v0.13.12 in .env next to the compose file; to roll back, set the old tag and run docker compose up -d.

What it does, in order
Resolves the newest release tag from the GitHub API, because a --depth 1 clone has no tags locally and sorting them as text puts v0.9.0 above v0.12.0. Then pg_dump to /var/backups/labtris — always, with no flag to skip, because alembic upgrade head is not reversible. Then fetch, check out, reinstall dependencies, migrate, restart. Then it verifies labtris-api and labtris-netd are active and the interface answers, and on failure prints the journal command, the previous commit and the path to the dump.
Verified on a real 0.11.0 install: upgraded to 0.12.0 with its two existing labs still present afterwards, and a restorable 7.5 KB dump on disk.
Docker installs do not use this
A compose deployment is a tag bump. From the directory holding your compose file:
docker compose pull && docker compose up -d
Volumes are untouched, and the API container runs the same migrations on start.
After install

Where to look when something breaks.

Shell installer or ISODocker
Web UIhttps://<host>/ (also :8081, plain HTTP)http://<host>:8080
Config/etc/labtris/labtris.envenvironment: in docker-compose.yml
DB password/etc/labtris/db-passwordset in docker-compose.yml
Health checklabtris system doctordocker compose exec api labtris system doctor
Logsjournalctl -u labtris-api -u labtris-netd -fdocker compose logs -f api netd
Restartsystemctl restart labtris-api labtris-netddocker compose restart

The full handbook lives at docs.labtris.com.