Local development server
Run the Remote Lab Manager on your laptop on localhost:8000. Foreground process; install once, start when you want a lab, Ctrl+C when you don’t.
Run the Remote Lab Manager on your laptop on localhost:8000. Foreground process; install once, start when you want a lab, Ctrl+C when you don’t.
Wrong page?
Already have a Remote Lab Manager you can reach? Skip this and use Quickstart directly. Standing up a multi-user shared host? Jump to Operator runbook.
Before you start
- Ubuntu 22.04+ (or another Linux with rootless Docker). macOS works for the client side but not the server — Containerlab and Netlab need Linux.
- Python 3.12+ with
pipxavailable (sudo apt install pipxon Ubuntu 24.04+;pipx ensurepathto put~/.local/binonPATH). - Netlab and Containerlab installed rootlessly. Non-trivial on a fresh machine; the canonical walkthrough is Netlab host setup. Run that page once, then come back.
Verify Netlab is reachable before installing the server:
This boots a tiny FRR topology, confirms Containerlab and the kernel
modules are happy, then tears down. If it fails, fix Netlab first —
neops-remote-lab will refuse to start without netlab on PATH.
1. Install the server
The server ships as the neops-remote-lab Python distribution. Install it
as a tool — isolated from your project’s environment, on your shell’s
PATH. The package declares a neops-remote-lab console script that
points at neops_remote_lab.__main__:main.
uv tool install drops
the CLI in ~/.local/bin (or uv tool dir) inside an isolated
environment that uv manages.
pipx installs into a per-app virtualenv
under ~/.local/pipx/venvs/. pipx ensurepath puts
~/.local/bin on PATH; re-login afterward.
Verify:
You should see the install path and the full CLI surface (--debug,
--host, --port, --log-level, --log-config, --version).
If you see command not found, run pipx ensurepath and re-login (or
source your shell’s RC file).
2. Start the server on localhost
For a development session, the simplest run is foreground with debug logging:
This binds 0.0.0.0:8000, sets the log level to DEBUG, and streams
Netlab subprocess output to the log in real time (the equivalent of
NEOPS_NETLAB_STREAM_OUTPUT=1).
You’ll see the server come up:
INFO | remote-lab-server | Server starting (pid=12345, version=0.x.y)
INFO | remote-lab-server | Lock acquired at /tmp/neops_remote_lab_server.lock
INFO | remote-lab-server | netlab CLI found at /home/you/.local/bin/netlab
INFO | remote-lab-server | Uvicorn running on http://0.0.0.0:8000
If you see Another Remote Lab Manager instance is already running.
instead, a previous server is still holding the singleton lock — see
Administration → Stale-lock recovery.
Confirm the server is reachable from a second terminal:
A 204 No Content (the body is empty; && echo OK runs because curl
exits 0) is the liveness signal. Anything else means the server didn’t
bind, the port is taken, or your firewall is blocking loopback — none of
which the fixture can debug for you.
Bind only to loopback if you don’t want others to reach it
The default --host 0.0.0.0 binds on every interface. On a shared
development machine, prefer --host 127.0.0.1 so the server is only
reachable from your own user:
http://localhost:8000 work either way; this just
closes the open port to anyone else on the network.
3. Point your tests at localhost
Set REMOTE_LAB_URL in the shell where you run pytest:
The fixture and RemoteLabClient both read this variable; the fixture
fails fast at session setup if it’s missing.
Drop it into a .env if you load one with python-dotenv or direnv,
so you don’t have to remember the export each session.
From here, follow the Quickstart from
step 3 (Write a minimal topology)
onward. The test code is identical to the remote case — pytest doesn’t
know or care whether the server is on the same host or across the
network.
4. Tear down between sessions
When you’re done with a development session, stop the server with Ctrl+C
in the terminal where it’s running. The shutdown sequence:
- SIGINT triggers the server’s signal handler, which sets
_SHUTDOWN_EVENT. - The lifespan context manager cancels the cleanup loop, deletes any
tracked sessions, and runs a final
LabManager.cleanupto tear down the running lab. - The
atexithook fires as the interpreter exits, as a last-resort cleanup if anything in step 2 was missed. - The singleton filelock is released.
Confirm there’s nothing lingering:
docker ps # no clab-* containers
ls /tmp/neops_remote_lab_server.* # no lockfile or metadata
netlab status default # "No active lab instance"
If a container, lockfile, or netlab default instance is still alive, the server didn’t tear down cleanly — see Administration → Forced cleanup of a stuck lab.
When to graduate to a remote server
Local-dev is the right shape for: writing tests, debugging failures quickly, exploring topology shapes. It’s the wrong shape when:
- More than one developer or CI job needs the lab. The one-server- per-host invariant means a second developer cannot run their own server on your machine; a shared VM (with Headscale VPN in front of it) is the multi-user shape.
- You need the lab to outlive your shell session. Local-dev assumes you stop the server when you stop working. A long-lived service belongs under systemd on a dedicated host.
- Test runs are heavy enough to interfere with your laptop. Containerlab
is greedy with CPU and RAM during
netlab up; offloading to a dedicated VM keeps your editor responsive.
When you cross any of these thresholds, the
Deployment section walks through standing up
a shared host. The test code and REMOTE_LAB_URL switch is the only
change on the consumer side.
Where to go next
- Quickstart — write your first test against the server you just started.
- Architecture — the high-level picture of how the local server is structured (it’s the same server you would run on a shared host; only the network topology differs).
- Operator runbook — the operator reference, including stale-lock recovery, the security posture you sign up for, and the systemd unit if you want the server to come back after reboot.
- Headscale VPN — when local is no longer enough, the recommended VPN enclosure for a shared host.