Watch
1
0
Fork
You've already forked noderig
0
Painless process supervisor with built-in observability
  • TypeScript 96.2%
  • Rust 1.2%
  • Python 1.1%
  • Shell 1%
  • JavaScript 0.3%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Martin Matejov bf782c1dbc feat: a shell into any running container from the dashboard
A Shell button on every running container of an app opens an xterm.js
terminal over the WebSocket at /api/apps/:id/containers/:name/shell,
authenticated by the token subprotocol like /ws. The server runs
`exec --interactive --tty` on a pseudo-terminal util-linux `script`
allocates (ContainerExec.terminal, ContainerBackend.shell,
Supervisor.openShell), sizes it through stty on the pty script opened,
ends it by typing ^C and ^D before signalling, and writes who opened
and closed each session into the app's log. xterm.js is loaded lazily
the first time a shell is opened.
2026-09-27 13:22:01 +02:00
.omp feat: a shell into any running container from the dashboard 2026-09-27 13:22:01 +02:00
assets feat: add the project logo, favicons and a repo icon 2026-09-01 20:56:08 +02:00
dashboard feat: a shell into any running container from the dashboard 2026-09-27 13:22:01 +02:00
docs feat: a shell into any running container from the dashboard 2026-09-27 13:22:01 +02:00
packages feat: nrig command line client signed in by the OAuth2 device grant 2026-09-26 16:56:46 +02:00
packaging docs: rewrite every document for the simplified platform 2026-09-18 10:44:09 +02:00
src feat: a shell into any running container from the dashboard 2026-09-27 13:22:01 +02:00
tests feat: a shell into any running container from the dashboard 2026-09-27 13:22:01 +02:00
.gitignore chore: ignore the local dev harness directory 2026-09-23 00:22:19 +02:00
AGENTS.md feat: a shell into any running container from the dashboard 2026-09-27 13:22:01 +02:00
CLAUDE.md docs: rewrite for the new security posture, add roadmap and IPC spec 2026-09-01 13:53:20 +02:00
config.example.yaml feat: own the runtime, the host network view and a compose project's volumes 2026-09-18 14:19:10 +02:00
package-lock.json feat: app-pushed metric series over IPC, charted per instance 2026-09-15 10:52:21 +02:00
package.json feat: a shell into any running container from the dashboard 2026-09-27 13:22:01 +02:00
README.md feat: nrig command line client signed in by the OAuth2 device grant 2026-09-26 16:56:46 +02:00
tsconfig.json refactor(core)!: an application is one container workload 2026-09-18 10:43:50 +02:00
vitest.config.ts test: add lifecycle integration test suite 2026-04-15 21:40:38 +02:00

NodeRig

NodeRig

A small self-hosted PaaS. NodeRig deploys applications from git, runs them as containers, serves static sites itself, routes every name through one ingress with automatic TLS, captures logs and metrics, and lets an application push its own widgets, metrics and structured logs back over a local socket.

An application is one workload: a container built from its own Dockerfile, pulled from a registry or declared as a compose project, or a repository whose files NodeRig serves. One record carries its declaration, its deployment history and its lifecycle, and everything it owns lives under one directory NodeRig owns.

NodeRig targets internal and self-hosted systems: one supervisor process on a Linux host, deploys triggered by webhooks, one listener that answers for every application by name, and real-time observability with no additional infrastructure. It is aiming at the gap between "run it with pm2 and hope" and "adopt Kubernetes", on one machine: a project deploys the way it would on a PaaS, the host underneath it is managed rather than hidden, and one edge answers for every name it serves. Where that is going next is docs/roadmap.md.

Status. The supervisor, the REST API, webhook deploys, the dashboard, container and static applications, the hostname-routing ingress with its automatic certificates, volume snapshots, the credential vault and the System section, which is this host's network, its container runtime and its firewall, all work today. Open work is tracked in the tickets/ tree in the repo root, which is local and untracked, so a fresh clone starts without it. Read docs/security.md before exposing a host.


Documentation

Document What is in it
docs/roadmap.md What NodeRig is for, what answers for it today, and what is deliberately next
docs/concepts.md Design goals, the app record, the two kinds, the two sources, deployments, volumes and snapshots
docs/deploys.md Remotes, the deploy pipeline, zero-downtime deploys, the readiness gate, revert, and the webhook a forge calls
docs/ingress.md Hostname routing, the dashboard's own hostname, redirects, TLS
docs/containers.md Container apps under Docker or Podman
docs/static-sites.md Serving a built directory, and the build container that produces it
docs/error-pages.md Per-application error pages: the statuses each kind can declare, the placeholders, and what is served while a workload is down
docs/icons.md What the dashboard draws for an application: the icon a deploy detects, and the one an operator picks
docs/configuration.md Every config.yaml key, the container runtime setting, signing in
docs/rest-api.md Every REST route, the query parameters, the WebSocket events
docs/cli.md nrig, the command line client: signing in from a terminal, contexts, and every command
docs/app-ipc.md Widgets, app metrics and structured logs an app pushes over IPC
docs/ipc-protocol.md The normative IPC specification
docs/dashboard.md Every page, and the route behind each element
docs/security.md What the runtime enforces, what NodeRig adds, and what is still open
docs/credentials.md Forge credentials, and how one reaches a git invocation
docs/firewall.md Listing and editing the host's own firewall
docs/system.md The System section: this host's network report, and which container runtime NodeRig drives
docs/development.md Running the suite, the dashboard dev server, the conventions

Installation

Prerequisites

  • Node.js 24 or newer, for node:sqlite
  • git on PATH
  • Docker or Podman. A workload is a container, and a static site's build runs in one too, so a host with neither can serve a repository that carries its output already built and nothing else. GET /api/host and the dashboard report which runtime is in charge, its version and whether it is rootless
  • Root, in practice: talking to the container runtime's daemon, publishing a port below 1024 and owning the state directory all need it

Install with pacman

The repository carries its own PKGBUILD, in packaging/, which builds noderig-git from the tip of the forge. This is how a host that runs NodeRig should get it: two machines stay in step by building the same commit, with nothing rsynced between them.

mkdir -p ~/Aur && cd ~/Aur
git clone https://git.raccoon.sk/martin/noderig.git
cd noderig/packaging
makepkg -si

Later versions are the same two commands from that directory: git pull && makepkg -si. Build from packaging/ rather than from the checkout root: makepkg puts its scratch directories, the forge clone and the staged install tree, next to the PKGBUILD, and at the root makepkg -C would delete the repository's own src/. The package is per-architecture, x86_64 and aarch64, because node_modules carries esbuild's binary, and every dependency is installed on the building host.

Path What
/usr/lib/noderig src/, package.json, dashboard/dist and node_modules, laid out exactly as a checkout is, plus build-info.json, the commit the package was built from
/usr/bin/noderig the launcher: tsx src/main.ts, no build step, no working directory of its own
/usr/lib/systemd/system/noderig.service the unit for the supervisor itself. It runs as root, names /etc/noderig/config.yaml in NODERIG_CONFIG, and creates /var/lib/noderig as its StateDirectory
/etc/noderig/config.yaml the configuration, 0600 and in the package's backup=, so an upgrade keeps your edits and leaves a .pacnew
/usr/share/noderig/config.example.yaml every key, documented
/var/lib/noderig the state root, installed at 0700 by the package, so pacman -Qo /var/lib/noderig names it. What is inside is NodeRig's: no upgrade touches it, and pacman -R leaves the directory in place with a notice, because uninstalling the supervisor is not a decision to destroy the applications it supervised
sudoedit /etc/noderig/config.yaml   # at least webui.password
systemctl enable --now noderig

Upgrading restarts nothing on its own. systemctl restart noderig is safe while sites are live: the supervisor detaches from its workloads on SIGTERM and adopts them again on the way back up.

What is running is v<version>+r<count>.g<commit> in the dashboard footer and the first line of journalctl -u noderig, carrying the same commit count and hash pacman -Q noderig-git prints. A ticket bumps no manifest version, so that suffix is what tells a host holding every recent change from one that never got the upgrade. A checkout has no such record and reports the manifest version alone.

Install from a checkout

For development, or on a host with no pacman:

git clone <repo>
cd noderig
npm install
npm run build:dashboard
cp config.example.yaml config.yaml
npm start

Then edit config.yaml. NodeRig refuses to start while the account is still admin/admin, and warns at startup when the password is empty, which locks the web UI rather than opening it. Every key is validated at startup: an unknown key, a wrong type, or an out-of-range value names itself and aborts rather than failing obscurely later.

The REST API and dashboard listen on webui.port, 8080 by default. State goes wherever paths.dataDir points, ./data by default, which is why a checkout keeps its state beside its code and the package does not.

Every configuration key is documented in docs/configuration.md and in config.example.yaml.


License

MIT