Skip to content

Server Edition

Server Edition runs the same nodeterm canvas from a headless Linux server and serves it to your browser. It boots the same core services the desktop app uses and ships the same built UI, so the React interface is unchanged — you’re just reaching it over the network instead of through a desktop window.

The server serves the same built renderer the desktop app runs and speaks a WebSocket protocol to it. A browser-side shim stands in for the desktop’s native bridge, so the canvas, the nodes and the panels behave the same. Terminals get the same tmux session continuity — they survive an app restart, and cold-restore after a reboot.

The server is a plain Node process — it runs on any Linux box: an Ubuntu or Debian server, a VPS, a homelab machine, a container host. There are two ways to run it.

On a bare Ubuntu/Debian server:

  • Node 22 (the version the official image runs)
  • git and tmuxsudo apt install tmux git. tmux is what gives terminals their session continuity; without it they fall back to a plain shell.
  • The agent CLIs you want (claude, codex, gemini) installed for the server user — agents run on the server, so this is where they live and authenticate.

The repo ships a multi-stage Dockerfile that builds everything and produces a slim runtime image with tmux, git and curl included:

Terminal window
docker build -t nodeterm-server .
docker run -d -p 8443:8443 \
-e NODETERM_SERVER_PASSWORD='choose-a-strong-one' \
-v nodeterm-data:/data \
nodeterm-server

/data must be a volume — auth, sessions, workspace and scrollback snapshots live there; without it every restart forgets the password and the canvas. On Dokploy (or any Dockerfile-based PaaS): create an app from the repo, attach a volume at /data, set NODETERM_SERVER_PASSWORD, and point a domain at container port 8443 — the platform’s proxy terminates TLS.

Agent CLIs aren’t baked into the image — install them into the running container (or extend the image) and sign in from a terminal node.

Terminal window
npm run server:dev

That builds the renderer and the server bundle, then starts it. On a repeat run where the renderer is already built, npm run server:start skips straight to launching. Useful flags: --port (default 8443), --host (default 127.0.0.1), --data-dir (default ~/.nodeterm-server).

First run prints a one-time setup URL to the console:

Setup: http://127.0.0.1:8443/setup?token=<token>

Open it, choose a password (minimum 8 characters), and you’re signed in. The setup token is single-use and lives only in memory. For containers or CI, seed the password out-of-band with NODETERM_SERVER_PASSWORD on first boot and skip the setup URL entirely.

Auth is single-user: one password, hashed with scrypt and stored on the server; on login the server sets an HttpOnly session cookie scoped to the browser. There are no accounts or roles.

Most of the app runs in the browser:

  • Terminals — spawn, I/O, resize, and tmux continuity.
  • Editor & diff nodes — Monaco reads and writes files; ⌘S saves.
  • Source Control — stage / unstage / discard, diff, branch switch, commit + push, and the ✦ AI commit message.
  • Explorer & folder picker — there’s no native OS dialog in a browser, so “Open folder…” uses an in-app server-directory browser that navigates the server’s own filesystem.
  • Agent observability — agent-status badges, subagent cards, and the context-window meter all work, driven by the same hook pipeline as the desktop app.

Two surfaces are not yet wired into the browser bridge:

  • Chat nodes — the SDK-driven Claude chat node is deferred.
  • Canvas control — the agent canvas-control verbs are not wired over the server.
  • Single user. One password; sessions are per-browser, with a lockout after repeated failed logins.
  • Origin check. The WebSocket upgrade requires a valid session cookie and, when the browser sends one, an Origin that matches the host — this blocks cross-site WebSocket hijacking.
  • Loopback hook server. The agent-status hook server binds only 127.0.0.1, and a forged hook POST is defended by jailing the transcript path it carries before anything reads it.