muxtop
A modern, multiplexed system monitor for the terminal.
muxtop replaces the htop + iftop + ctop workflow with a single tabbed interface.
Think htop, but with multiplexer-style UX (à la tmux/zellij) and a VS Code-style command palette.
Installation
Via crates.io
Via Homebrew (macOS / Linux)
Via APT (Debian / Ubuntu)
# Add the repo (one time)
|
|
# Install
Pre-built binary (Linux / macOS)
|
From source
# Binary available at target/release/muxtop
MSRV: Rust 1.88
Platforms. Linux and macOS (x86_64 and aarch64) are the supported targets — those are what the release pipeline publishes. The workspace also builds and passes its tests on Windows so the project can be developed there, but no Windows binaries are published and the process actions (
F7/F8renice,F9/F10kill) are POSIX-only and return an error.
Features
| Feature | Detail |
|---|---|
| Tabs | General, Processes, Network, Containers and Kubernetes — Alt+1 / Alt+2 / Alt+3 / Alt+4 / Alt+5 |
| Network tab | Interface table with RX/s, TX/s, totals, errors + real-time sparklines |
| Containers tab | Docker/Podman via bollard — CPU/memory/network/IO table, CPU+RX sparklines, F9 stop / F10 kill / F11 restart actions, automatic socket detection |
| Kubernetes tab | Read-only Pods / Nodes / Deployments via kube-rs — switch sub-views with P / N / D, sort with s, filter with /. Auto-detects $KUBECONFIG / ~/.kube/config / in-cluster ServiceAccount; graceful fallback when metrics-server is absent (CPU/MEM render —). Lists cluster-wide by default; --kube-namespace <NS> scopes Pods and Deployments to one namespace so a namespace-bound Role is enough, and A toggles between the two at runtime (see Kubernetes permissions) |
| Command palette | Ctrl+P — kill firefox, sort memory, stop nginx, restart postgres, etc. |
| htop shortcuts | F1–F5 sort columns, F7/F8 renice, F9 kill, F10 force kill |
| Fuzzy search | Powered by nucleo (from the Helix editor) |
| Tree view | F5 toggles the parent/child hierarchical display |
| Renice | + / - to adjust process priority |
| Remote monitoring | --remote host:port + --token to monitor a remote server over encrypted TLS |
| Native TLS | rustls encryption (TLS 1.3-only since 0.3.1), self-signed cert auto-generation (--tls-generate), mandatory token auth |
| Async collection | tokio-based — the UI never blocks, even at 3000+ processes |
| Tokyo Night theme | Native TrueColor, automatic fallback for ANSI/16-color terminals |
| Static binary | Single musl binary, no system dependencies |
| Zero telemetry | No client-side network calls, ever (see Privacy) |
Kubernetes permissions
By default muxtop lists Pods, Nodes and Deployments across every namespace, which needs cluster-scoped list on all three. On a shared cluster you usually don't have that.
Pass --kube-namespace <NS> to scope Pods and Deployments to a single namespace — that works with a plain Role bound to it, no cluster-wide grant required. Press A in the Kube tab to switch between the scoped and cluster-wide views at runtime (local mode only; in --remote mode the server's --kube-namespace decides).
Nodes are cluster-scoped in Kubernetes — there is no namespaced variant of the resource, so the Nodes sub-view always needs cluster-wide access and renders empty without it. The Pods and Deployments views are unaffected. The same split applies to metrics: pod CPU/MEM follows the namespace scope, node CPU/MEM does not.
A minimal namespace-scoped Role:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: muxtop-readonly
namespace: my-namespace
rules:
- apiGroups:
resources:
verbs:
- apiGroups:
resources:
verbs:
- apiGroups:
resources:
verbs:
Privileges
Access to the Docker socket (/var/run/docker.sock) is equivalent to root access on the host machine: any user in the docker group can launch a privileged container and break out. To run muxtop with a minimal privilege budget, use rootless Podman — the user-scoped socket ($XDG_RUNTIME_DIR/podman/podman.sock) is isolated per user and muxtop detects it automatically. Avoid running muxtop-server as root on an exposed host: prefer a service account with only the rootless Podman socket mounted read/write.
Usage
# Containers tab — by default muxtop checks $DOCKER_HOST, /var/run/docker.sock,
# then the Podman sockets. Pass a path to force, or disable entirely:
# Kubernetes tab — by default muxtop reads $KUBECONFIG, then ~/.kube/config,
# then falls back to in-cluster ServiceAccount credentials. Override or disable:
# Run the server (TLS + auth required)
# Remote monitoring (TLS)
MUXTOP_TOKEN="my-secret-16chars"
Keyboard shortcuts
| Key | Action |
|---|---|
Ctrl+P |
Command palette |
Alt+1 … Alt+5 |
Switch tab (General / Processes / Network / Containers / Kubernetes) |
Tab / Shift+Tab · ← / → |
Cycle to the next / previous tab |
q · Ctrl+C |
Quit |
j / k · ↑ / ↓ |
Navigation (vim-style) |
g / G · Home / End |
Jump to first / last row |
PageUp / PageDown |
Scroll by 20 rows |
/ |
Filter (applies to the active tab) |
Esc |
Clear the active filter |
t |
Tree view (Processes) |
s |
Cycle sort field (active tab) |
S / I |
Toggle sort direction (active tab) |
F1 … F5 |
Sort processes by PID / name / CPU / memory / user |
F7 / F8 |
Renice — lower (+1) / raise (−1) priority, Processes tab, local mode |
F9 |
Kill process, SIGTERM (Processes) · Stop container (Containers) |
F10 |
Force kill, SIGKILL (Processes) · Kill container (Containers) |
F11 |
Restart container (Containers) |
P / N / D |
Switch Kube sub-view to Pods / Nodes / Deployments (Kubernetes tab only) |
A |
Toggle namespace scope — one namespace ↔ All namespaces (Kubernetes tab, local mode) |
There is no built-in help screen yet —
Ctrl+Plists every command with its shortcut.
Benchmarks
Tested on macOS with 500+ processes (Thomas benchmark):
| Metric | Target | muxtop |
|---|---|---|
Startup (--about) |
< 100 ms | ~12 ms |
| Binary size | < 10 MB | 5.3 MiB (LTO + strip) |
| FPS (TUI) | > 30 | ~60 (event-driven, idle ≈ 0 redraws) |
| Peak RSS (30 s) | < 15 MiB | 11.3 MiB (htop ~15, btop ~40) |
Run the benchmark yourself:
# or
Architecture
muxtop/
├── src/ # Entry point (clap CLI + tokio bootstrap)
└── crates/
├── muxtop-core/ # System collection, data models, actions
│ ├── src/collector.rs # 3 async loops: sysinfo 1 Hz, containers 0.5 Hz, cluster 0.2 Hz
│ ├── src/process.rs # Sort, filter, process tree
│ ├── src/system.rs # CPU / memory / load snapshots
│ ├── src/network.rs # Network interfaces + history
│ ├── src/containers.rs # Container model (ContainerSnapshot, states, engine)
│ ├── src/container_engine.rs # Async trait + Docker/Podman socket detection
│ ├── src/docker_engine.rs # Concrete bollard-backed implementation
│ ├── src/kube.rs # Pod / Node / Deployment / Cluster snapshots
│ ├── src/cluster_engine.rs # Async trait + kubeconfig detection
│ └── src/kube_engine.rs # Concrete kube-rs-backed implementation
├── muxtop-tui/ # ratatui interface
│ ├── src/app.rs # State machine, event handling
│ └── src/ui/ # Tabs General/Processes/Network/Containers/Kube, palette, theme
├── muxtop-proto/ # Wire protocol and binary serialization
└── muxtop-server/ # TCP daemon for remote monitoring
Development
Roadmap
| Version | Goal |
|---|---|
| v0.1 ✓ | htop replacement — tabs, command palette, tree view |
| v0.2 ✓ | Network tab (replaces iftop) + client/server architecture (muxtop-server, --remote) |
| v0.3 ✓ | Docker / Podman Containers tab (via bollard) + Stop/Kill/Restart actions |
| v0.3.1 ✓ | TLS 1.3 hardening, per-IP rate limit, ANSI sanitizer, event-driven render, lto=fat build sweep |
| v0.4 ✓ | Kubernetes Pod tab (read-only) via kube-rs, kubeconfig auto-detection, metrics-server graceful degradation |
| v0.5 | GPU monitoring (NVIDIA / AMD / Apple Silicon) + interactive docker exec (PTY) |
| v1.0 | WASM plugin system + themes + configuration file |
Privacy & telemetry
muxtop collects NO telemetry, NO statistics and contacts NO ONE. Ever.
It makes no outbound network calls of its own. It is designed for air-gapped production servers. If you observe outbound activity from muxtop that isn't tied to a feature you've enabled, that is a bug — please report it.
Read-only by design
When the Kubernetes or Containers tab is active, muxtop only reads from the corresponding API:
- Kubernetes :
LISTon Pods / Nodes / Deployments +GETonmetrics.k8s.io/v1beta1, scoped to one namespace or cluster-wide per Kubernetes permissions. NoCREATE/UPDATE/DELETE/PATCHis ever issued. Write actions (Delete pod, Scale deployment, Rollout restart) are explicitly out of scope for v0.4. - Containers :
GET /containers/json+/stats?stream=false. The Stop / Kill / Restart actions are gated behind a confirmation dialog and are local-only — disabled in--remotemode.
Remote mode and credentials
In --remote mode, the server is the only side that opens kubeconfig or Docker socket files. Credentials never traverse the wire — only the digested snapshots do. Anti-leak guards in the test suite (muxtop-proto/tests/integration.rs) verify byte-for-byte that no BEGIN PRIVATE KEY, Bearer token, or client-key-data: field appears in any encoded frame.
Contributing
Contributions are welcome! See CONTRIBUTING.md for prerequisites, code conventions, the branch workflow and PR submission instructions.
License
Available under either of the following licenses, at your option:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT License (LICENSE-MIT)