Skip to content

Running LeapMux

LeapMux is a single Go binary (leapmux) that you run in one of four modes: solo, hub, worker, or dev. This chapter is the administrator’s guide to those modes — what each one does, how to launch it, where it stores data, and how to run LeapMux under Docker or behind a reverse proxy.

If you only want to try LeapMux on your own machine, the desktop app or leapmux solo is all you need. To set up a shared service for a team, run a hub plus one or more worker processes. For the full list of configuration keys and storage backends referenced here, see Configuration.

Run modes at a glance

The modes that accept inbound connections — solo, hub, and dev — all default to TCP port 4327. A worker is the exception: it only makes outbound connections to a Hub and binds no listen port (its default http://127.0.0.1:4327 is the Hub URL it dials, not a port it serves). Beyond the port, the modes differ in what they run, whether they require a login, and where they listen.

ModeWhat it runsDefault listenLogin requiredConfig / data location
soloHub + Worker in one process127.0.0.1:4327 (loopback only)No — every request is auto-authenticated as the admin~/.config/leapmux/solo/
hubHub service only:4327 (all interfaces)Yes — full authentication~/.config/leapmux/hub/
workerWorker only (connects out to a Hub)n/a (no inbound port)Registers with the Hub via a registration key~/.config/leapmux/worker/
devHub + Worker in one process:4327 (all interfaces)Yes — admin bootstrapped via /setup~/.config/leapmux/dev/

Each mode reads a YAML config file named after the mode inside its config directory — for example ~/.config/leapmux/hub/hub.yaml for the Hub. The file is optional; a missing config file is silently skipped. The default data directory for each mode is the same as its config directory.

solo and dev are the same program internally — both run a Hub and a Worker together in one process. Solo binds loopback only and skips login: it injects an admin user into every request. Dev binds all interfaces and uses password authentication. Dev bootstraps its first admin through the /setup flow.
Because solo mode auto-authenticates every request as the admin, anyone who can reach its port has full admin access with no credentials. That is safe on 127.0.0.1. If you bind solo to a non-loopback address, LeapMux logs a startup warning telling you to restrict access externally or switch to leapmux hub (see Security & Threat Model for the full warning text). For a multi-user or network-exposed deployment, run leapmux hub (with separate Workers) or leapmux dev instead, both of which require a real login.

Solo mode

Solo mode is the single-user setup that runs with no required configuration. Hub and Worker run in the same process on loopback, and the UI opens straight into the workspace with no sign-in.

leapmux solo
# Then open http://127.0.0.1:4327

Inside the solo data directory, the single data_dir is split into two subdirectories: the Hub stores its database and encryption key under <data_dir>/hub/, and the in-process Worker stores its state under <data_dir>/worker/. The Worker auto-registers itself and persists its credentials and end-to-end-encryption keypair to <data_dir>/worker/state.json, so your session survives restarts.

Solo mode accepts a subset of the Hub’s flags plus the Worker flags -encryption-mode and -use-login-shell. The most important solo flags:

FlagDefaultMeaning
-listen127.0.0.1:4327TCP listen address
-data-dir. (resolves to ~/.config/leapmux/solo)Data directory
-log-levelinfodebug, info, warn, error
-encryption-modepost-quantumclassic or post-quantum
-config~/.config/leapmux/solo/solo.yamlConfig file path

Solo also accepts the SQLite tuning flags (-storage-sqlite-max-conns, -storage-sqlite-cache-size, -storage-sqlite-mmap-size), the chunked-reassembly cap (-max-incomplete-chunked), and -dev-frontend; see Configuration for those. The message-size and timeout settings are instance settings now: change them with leapmux control admin settings instead of launch flags.

The desktop app runs solo mode under the hood, but with no TCP port at all — it serves the Hub only over a local IPC socket (a Unix domain socket or Windows named pipe). See Installation for the desktop app.

Hub mode

leapmux hub runs only the Hub: authentication, workspace management, Worker registration, and the encrypted relay between Frontends and Workers. It does not run a Worker — Workers connect separately (see Running Workers). The Hub binds :4327 on all interfaces by default and requires real authentication.

leapmux hub -listen :4327

A fresh Hub has no users and (by default) sign-up disabled. Open it in a browser and every path shows the /setup form — the first person to register becomes the administrator (see Accounts & Authentication). You can also create that first admin offline with leapmux recover bootstrap create-admin, which refuses once any admin exists. Then log in as that administrator.

Enable sign-up with leapmux control admin settings set signup_enabled true, or create each account with leapmux control admin user create. The most important Hub flags:

FlagDefaultMeaning
-listen:4327TCP listen address (e.g. :4327 or 127.0.0.1:4327)
-local-listenplatform defaultLocal IPC URL (unix:<path> or npipe:<name>); defaults to unix:<data-dir>/hub.sock on Unix
-data-dir. (resolves to ~/.config/leapmux/hub)Data directory
-storage-typeempty (= sqlite)sqlite, postgres, mysql, cockroachdb, yugabytedb, or tidb
-log-levelinfoLog level
-config~/.config/leapmux/hub/hub.yamlConfig file path

The flags are the process’s bootstrap surface only. Everything behavioral — sign-up, email verification, sessions, SMTP, timeouts, per-user limits — is an instance setting in the Hub’s database, changed at runtime and without a restart:

leapmux control admin settings set signup_enabled true
leapmux control admin settings set smtp '{"host":"smtp.example.com","port":587,"from_address":"no-reply@example.com"}'

By default the Hub uses an embedded SQLite database at <data_dir>/hub.db with its encryption key ring at <data_dir>/encryption.key. For a shared, durable deployment you will usually point it at an external database via -storage-type and the matching *-dsn flag. The full reference is in Configuration; the Hub settings section of the Admin CLI chapter covers the settings table.

The Hub does not terminate TLS itself. For HTTPS you put a reverse proxy in front of it; see Reverse proxy and public URL below.

Running Workers

A Worker runs your agents and terminals and connects out to a Hub — it never accepts inbound connections, so it works behind a NAT or firewall with no port forwarding. A Worker is not a user account; it registers with the Hub using a one-time registration key minted in the Hub UI.

On first run, point the Worker at the Hub and supply a registration key:

leapmux worker -hub https://hub.example.com -registration-key <key>

After a successful registration, the Worker saves its credentials and keypair to <data_dir>/state.json and reconnects automatically on every subsequent start — you do not pass -registration-key again.

FlagDefaultMeaning
-hubhttp://127.0.0.1:4327Hub URL (http[s]://…, unix:<socket-path>, or npipe:<pipe-name>)
-registration-keyemptyRegistration key from the Hub UI; required on first run only, never persisted
-namehostnameWorker display name
-data-dir. (resolves to ~/.config/leapmux/worker)Data directory
-encryption-modepost-quantumclassic or post-quantum
-use-login-shelltrueWrap agent invocation in your login shell
-log-levelinfoLog level
-config~/.config/leapmux/worker/worker.yamlConfig file path

If a Worker has no saved credentials and no key, it refuses to start, and the error tells you to pass a registration key from the Hub UI. If you pass -registration-key to a Worker that is already registered, it stops rather than consuming the key by accident; the error tells you to remove -registration-key or to wipe the local state.

Minting registration keys, approving Workers, the trust-on-first-use (TOFU) pinning of Worker keys, and choosing which Worker a tab runs on are all covered in Managing Workers.

Dev mode

leapmux dev runs a Hub and Worker together like solo, but binds all interfaces and requires a real login. It is the all-features, network-reachable variant — and the one to use inside a container when you want a single-process Hub + Worker that is actually reachable from outside.

leapmux dev -listen :4327

Because dev mode uses real authentication, it bootstraps its first admin through the /setup flow rather than auto-authenticating. The in-process Worker’s auto-registration is deferred until the first admin signs up through /setup. Until then, the log says deferring worker auto-registration until first admin signs up via /setup. Open the URL, complete /setup to create the admin, and the bundled Worker comes online.

Dev mode accepts the same flags as solo. The most important dev flags:

FlagDefaultMeaning
-listen:4327TCP listen address
-data-dir. (resolves to ~/.config/leapmux/dev)Data directory
-log-levelinfoLog level
-encryption-modepost-quantumclassic or post-quantum
-config~/.config/leapmux/dev/dev.yamlConfig file path

Like solo, dev also accepts the SQLite tuning flags plus -dev-frontend (see Configuration).

Dev mode enables signup_enabled=true by default, because it runs the full multi-user path (a stored setting overrides it). The runtime knobs are settings rather than flags — a short session for exercising the signed-out path is:

leapmux control admin settings set session_duration_seconds 300   # the 5-minute minimum

Running under Docker

Pre-built multi-arch images are published to ghcr.io/leapmux/leapmux and supervised by s6-overlay. The single environment variable LEAPMUX_MODE selects which subcommand the container runs.

Image variants and tags

VariantTagsExample
Alpine (default):<version>, :<major>, :latest, :devghcr.io/leapmux/leapmux:latest
Ubuntu:<version>-ubuntu, :<major>-ubuntu, :latest-ubuntu, :dev-ubuntughcr.io/leapmux/leapmux:latest-ubuntu

Both variants target linux/amd64 and linux/arm64. Release tags (:latest, :<version>, :<major>) come from the release workflow; the :dev tag is rebuilt on every push to main. For production, pin a specific :<version> rather than tracking :latest or :dev.

Selecting the mode

LEAPMUX_MODE is required and must be one of hub, worker, dev, or solo. If it is unset or invalid, the container exits, and the error lists the four accepted modes.

The supervisor always invokes leapmux <mode> -config /data/<mode>/<mode>.yaml, creating an empty 0600 config file if none exists. It passes no other flags — so any additional settings must come from the YAML config file or from LEAPMUX_HUB_* / LEAPMUX_WORKER_* environment variables (see Configuration).

Volume layout

The image declares a single /data volume. Each mode keeps its files under /data/<mode>/:

PathContents
/data/<mode>/<mode>.yamlConfig file (e.g. /data/hub/hub.yaml)
/data/<mode>/hub/hub.dbHub SQLite database (when using the default SQLite backend)
/data/<mode>/hub/encryption.keyHub encryption key ring
/data/<mode>/worker/state.jsonWorker registration credentials and E2EE keypair
/data/<mode>/worker/worker.dbWorker SQLite database

In a hub or worker container the mode directory is the top level, so the files sit directly at /data/hub/… and /data/worker/…. In a dev or solo container the single process runs both sides, and the Hub and Worker subdirectories nest one level deeper (/data/dev/hub/…, /data/dev/worker/…).

Mount a named volume (or a host path) at /data so this state survives container recreation.

The /data volume laid out for a hub and a worker:

/data                          ← mounted volume (-v leapmux-data:/data)
├── hub/                       ← LEAPMUX_MODE=hub
│   ├── hub.yaml               ← config file
│   ├── hub.db                 ← Hub SQLite database (default backend)
│   └── encryption.key         ← Hub encryption key ring
└── worker/                    ← LEAPMUX_MODE=worker
    ├── worker.yaml            ← config file
    ├── worker.db              ← Worker SQLite database
    └── state.json             ← registration credentials + E2EE keypair

In a dev or solo container the same files nest under /data/dev/ or /data/solo/: dev.yaml, hub/hub.db, hub/encryption.key, worker/state.json, worker/worker.db.

Exposing the port

The image exposes port 4327, the single TCP listen port for hub, dev, and solo. (worker makes only outbound connections, so it needs no published port.) Map it with -p:

# Run as a hub (central service only)
docker run -p 4327:4327 -e LEAPMUX_MODE=hub -v leapmux-data:/data \
  ghcr.io/leapmux/leapmux:latest

# Run as Hub + Worker together (dev mode, login required)
docker run -p 4327:4327 -e LEAPMUX_MODE=dev -v leapmux-data:/data \
  ghcr.io/leapmux/leapmux:latest
Do not run LEAPMUX_MODE=solo in a container expecting to reach it from outside. Solo’s default listen address is 127.0.0.1:4327 (loopback only), which is not reachable from the host, and solo auto-authenticates every request as admin. Use dev for a single-process Hub + Worker that binds all interfaces with a real login, or run a hub container plus separate worker containers.

Running a Worker container

The supervisor passes no --hub or --registration-key flags, so a worker container needs those supplied another way — via the Worker YAML config at /data/worker/worker.yaml or via environment variables:

docker run -e LEAPMUX_MODE=worker \
  -e LEAPMUX_WORKER_HUB=https://hub.example.com \
  -e LEAPMUX_WORKER_REGISTRATION_KEY=<key> \
  -v leapmux-worker-data:/data \
  ghcr.io/leapmux/leapmux:latest

The registration key is consumed only on first registration; once the Worker’s state.json exists you can drop LEAPMUX_WORKER_REGISTRATION_KEY from later runs.

Supervision behavior

The image entrypoint is s6-overlay’s /init, which runs the leapmux process as a long-running service. If that service exits non-zero, the whole container is brought down so your orchestrator (Docker restart policy, Kubernetes, etc.) can restart it cleanly. A clean shutdown (for example, SIGTERM during a graceful stop) is treated as normal and does not trigger a restart loop.

Reverse proxy and public URL

The Hub never terminates TLS on its own. To serve LeapMux over HTTPS, put a reverse proxy (nginx, Caddy, Traefik, etc.) in front of it and tell the Hub its external address:

  1. Set public_url to the external HTTPS URL: leapmux control admin settings set public_url "https://hub.example.com".
  2. Enable secure cookies so they are __Host- prefixed and the derived base URL uses https: leapmux control admin settings set secure_cookies true. (This changes the cookie name, which signs every current session out — do it once, at setup.)
  3. Point each Worker’s -hub URL at the same external https:// address. Workers always initiate outbound connections, so they need no inbound ports of their own.

Both settings are hot: a running Hub applies them within ~30 seconds — no restart.

public_url must be a bare scheme + host — for example https://hub.example.com. Sub-path mounting (such as https://example.com/leapmux) is not supported and is rejected at write time. Give LeapMux its own hostname or subdomain.

The proxy must also forward WebSocket upgrades, since Frontend traffic and the relayed Worker streams ride over long-lived connections. For the security implications of the relay, the end-to-end-encryption boundary, and Worker TOFU pinning, see Security & Threat Model.

Upgrading

LeapMux runs database migrations automatically on startup, for both the Hub and each Worker, so there is no separate migration command to run during a routine upgrade.

  • Docker: Pull a newer image tag (a pinned :<version>, :<major>, or :latest) and recreate the container against the same /data volume. Migrations run on the next start.
  • CLI binary: Replace the leapmux binary from the newer server tarball or zip on the Releases page and restart.
  • Desktop app: Download and install the newer artifact from the Releases page.
Back up your Hub data before a major upgrade — at minimum the database and encryption.key (or your external database, if you use one). The encryption key ring is required to read encrypted data, so keep it with your backups. See Encryption & Data for backup, restore, and key-rotation details.

Checking the version

Every mode and the dedicated version command print the same build string:

leapmux version
leapmux --version

See also

  • Configuration — full flag and config-key reference, storage backends, listen addresses, env-var precedence.
  • Managing Workers — registration keys, Worker approval, TOFU pinning, Worker selection.
  • Recovery — the offline break-glass tree: first-admin bootstrap, password reset, schema migrations, and encryption-key surgery.
  • Control CLI and Admin CLI — users, sessions, workers, OAuth providers, instance settings, and tokens on a running Hub.
  • Installation — desktop app, Docker images, and building from source.
  • Security & Threat Model — trust boundaries, the E2EE relay, and solo-mode caveats.
  • CLI Reference — consolidated cheat-sheet for every subcommand.
Last updated on