Runtimes
Lynx is language-agnostic. It executes whatever command you give it as a child
process and supervises the PID. The --runtime flag and file-extension
auto-detection are convenience shortcuts — they never restrict what you can
actually run.
Verification status. The following runtimes are exercised end-to-end through
lynxpm startin a clean systemd-nspawn container with the Debian package installed:Node 18, Bun 1.3, Deno 2.7, Python 3.12 (system / venv / uv / uvx), Go (source + compiled binary), Rust, C, C++, OCaml, Haskell, Nim, Java 21, Ruby 3.2, Perl 5.38, PHP 8.3, Lua 5.4, R 4.3, Erlang, Elixir 1.14, Tcl 8.6, Bash 5.2.
Docker-as-managed-process and Kotlin/Scala are shape-correct per each tool’s docs but not part of the automated matrix.
Two things matter:
- The daemon must see the binary you’re asking for. In system mode the
daemon runs as the
lynxuser and searches its ownPATH. If your interpreter lives under~/.local/bin,~/.bun/bin, or an fnm/nvm shell-managed directory, runlynxpm install-tools(user mode) orsudo lynxpm install-tools --systemto symlink the important ones into a place lynxd will find them. - The cwd must be accessible to the daemon user. In system mode the
daemon runs as
lynxand cannot read/rootor other users’$HOME. Pass--cwdto a directory the daemon can enter (e.g./var/lib/lynx-pm,/srv/yourapp,/tmp).
Node.js
Section titled “Node.js”# Single file, auto-detected by extensionlynxpm start server.js
# Explicit runtimelynxpm start app.mjs --runtime node
# With argslynxpm start "node --inspect server.js" --name api
# Package.json scriptslynxpm start "npm run start" --name api --cwd /srv/api --shelllynxpm start "pnpm start" --name api --cwd /srv/api --shelllynxpm start "yarn start" --name api --cwd /srv/api --shell
# Version-managed Node (fnm / nvm)# Best: resolve the binary once and pass the full path.lynxpm start "$(fnm current-path)/node server.js" --shell
# Cluster / multi-instancelynxpm start server.js --name worker --scale 4--scale N exposes LYNX_INSTANCE=0..N-1 to each child so your app can
bind to different ports (const port = 3000 + Number(process.env.LYNX_INSTANCE)).
lynxpm start "bun run server.ts" --name api --cwd /srv/apilynxpm start "bun dev" --name dev-serverlynxpm start "deno run --allow-net server.ts" --name api --cwd /srv/apiPython
Section titled “Python”System interpreter
Section titled “System interpreter”lynxpm start app.py --runtime python3# or explicitlynxpm start "python3 -u app.py" --name api --cwd /srv/apiThe -u flag keeps stdout unbuffered so lynxpm logs streams in real time.
Virtualenv (venv)
Section titled “Virtualenv (venv)”# Option 1: point directly at the venv's pythonlynxpm start "/srv/api/.venv/bin/python app.py" --cwd /srv/api --name api
# Option 2: activate and run inside a shelllynxpm start "source .venv/bin/activate && python -u app.py" \ --cwd /srv/api --shell --name apiuv / uvx
Section titled “uv / uvx”uv manages its own envs. Use uv run to
execute within the project’s lockfile-pinned env without pre-activation:
# Run a script via uvlynxpm start "uv run app.py" --cwd /srv/api --name api
# Run a tool ad-hoc via uvxlynxpm start "uvx --from 'httpie' http :8080/health" --name probe --shell
# Pin a Python versionlynxpm start "uv run --python 3.12 app.py" --cwd /srv/api --name apilynxpm start "$(pyenv which python) app.py" --cwd /srv/api --shell --name apiFastAPI / uvicorn / gunicorn
Section titled “FastAPI / uvicorn / gunicorn”lynxpm start "uv run uvicorn main:app --host 0.0.0.0 --port 8080" \ --cwd /srv/api --name api --restart always# Source file, auto-detected (uses `go run`)lynxpm start main.go
# Compiled binary (preferred for production)go build -o /srv/api/bin/api ./cmd/apilynxpm start /srv/api/bin/api --cwd /srv/api --name api --restart always
# `go run` with argslynxpm start "go run ./cmd/api --config /srv/api/config.yml" --cwd /srv/apiIn production you almost always want the compiled binary — go run
re-compiles every restart.
# Compiled (release)cargo build --releaselynxpm start ./target/release/api --cwd /srv/api --name api
# cargo run (dev only)lynxpm start "cargo run --release" --cwd /srv/api --shell --name apiRuby / Rails
Section titled “Ruby / Rails”# System rubylynxpm start "bundle exec rails server -e production" --cwd /srv/api --shell
# rbenvlynxpm start "$(rbenv which bundle) exec rails s" --cwd /srv/api --shellJava / JVM (Spring, Kotlin, Scala)
Section titled “Java / JVM (Spring, Kotlin, Scala)”lynxpm start "java -Xmx512m -jar app.jar" --cwd /srv/api --name api
# With JAVA_HOME from env-fileecho "JAVA_HOME=/opt/jdk-21" > /srv/api/.envlynxpm start "/opt/jdk-21/bin/java -jar app.jar" \ --cwd /srv/api --env-file /srv/api/.env --name apiShell scripts
Section titled “Shell scripts”lynxpm start /srv/api/start.sh --name api
# Inline commandlynxpm start "bash -c 'while true; do date; sleep 5; done'" --shell --name clockThe --shell flag wraps your command in sh -c '…', which you need for
glob expansion, pipes, &&, variable interpolation. Security note:
--shell is rejected in system-mode daemons for hardening reasons. In
user mode it works as expected.
Docker container as a managed process
Section titled “Docker container as a managed process”You can make Lynx babysit a specific docker run:
lynxpm start "docker run --rm --name myapp nginx" --name myapp --restart always…but for most workloads a native binary + --isolation sandbox gives you
stronger isolation without the daemon overhead.
Environment files
Section titled “Environment files”Every language flow accepts --env-file to inject variables:
cat > /srv/api/.env <<EOFDATABASE_URL=postgres://…PORT=8080EOFlynxpm start "uv run app.py" --cwd /srv/api --env-file /srv/api/.envIn --isolation dynamic the env-file is delivered via systemd
LoadCredential= — secrets never appear in /proc/<pid>/environ.
Isolation mode quick picker
Section titled “Isolation mode quick picker”| Goal | Mode | Works in |
|---|---|---|
| Default, fast, lean | --isolation self (default) | user + system |
| Strongest isolation with DynamicUser | --isolation dynamic | system only |
| Unprivileged sandbox (landlock + user ns) | --isolation sandbox | user + system |
See SECURITY.md for the threat-model behind each mode.
Startup
Section titled “Startup”Make Lynx and your apps survive reboots:
sudo systemctl enable --now lynxd # system mode# orlynxpm startup # user mode — wires the user systemd unitWhen lynxd starts it calls manager.Restore() which re-reads the specs in
~/.config/lynx/apps and re-spawns any app that was running before.
What lynxpm install-tools does
Section titled “What lynxpm install-tools does”Scans for common dev tools (bun, node, npm, pnpm, yarn, go, python3, pip,
ruby, cargo, java, deno) and symlinks them into ~/.local/bin (default) or
/usr/local/bin (with --system). This is a convenience for getting the
daemon’s PATH lookups to succeed when your interpreter lives under
~/.bun/bin or an fnm shim directory.