A fast, modern SSH + container manager for your terminal.
SSHM is a TUI & CLI tool written in Rust to manage and connect to SSH hosts, Docker containers, Incus instances, and Kubernetes pods — all from one keyboard-driven interface.
Built for developers, sysadmins, pentesters, and homelab folks who live in a terminal.
- Host management — add, edit, delete, tag, organize into nested folders
- Clone host —
yduplicates the selected host (tunnels included) and drops you straight into the editor - Fuzzy search + prefix filters —
tag:prod host:10.* user:ubuntu, fzf-style scoring - Tunnels — saved per-host port forwards (local
-L, remote-R, dynamic SOCKS-D); start them in the background and watch / stop them from thetdashboard - Multi-hop ProxyJump —
bastion1,bastion2, each entry resolves against your saved hosts automatically - Identity management — push SSH public keys, generate new keys (
ed25519,ed25519-skFIDO2,ecdsa,rsa), load intossh-agent - ForwardAgent (
-A) per host — opt-in with a visible warning, badged in the list - Mosh per host — opt-in toggle; connects via
moshinstead ofssh, forwarding port / identity / ProxyJump automatically - Per-host notes — free-text reminder shown in the detail panel
- Hardware key detection —
[HW]badge for*-skkeys - Frecency sort + Recently Used —
scyclesname → MRU → most-used → favorites → frecency - Group by tag —
gtoggles between folder view and tag view - Bulk actions —
Spaceselects,Tadds tags to selection,Ddeletes,Cclears - Fan-out —
Xruns one command on every selected host over SSH, with per-host output and an ok/failed summary - Quick connect —
1-9connects to the Nth visible host - Health probes — periodic TCP + SSH banner check, latency in ms, banner version (
OpenSSH_9.6) shown inline
A dedicated tab between Hosts and Identities to manage containers and pods:
- Docker (local) — auto-detected if
dockeris on PATH and the daemon is up - Docker (remote) — pick any saved SSH host, sshm sets
DOCKER_HOST=ssh://...and tunnels everything natively. No port to open, no TLS, no socket setup - Incus (local) — auto-detected, lists containers and VMs
- Incus (remote) — auto-imported from
incus remote list - Kubernetes / K3s — auto-imported from every context in
~/.kube/configand$KUBECONFIG - One Enter to shell into any container / pod / instance —
/bin/shdirectly, no bash dance - One
lto follow logs —Ctrl+Creturns to the TUI cleanly (no app exit) - Lifecycle control —
sstarts/stops andRrestarts Docker containers and Incus instances right from the list - Pod cleanup —
don aSucceeded/Failedpod runskubectl delete pod - Section folding — clusters collapsed by default,
Enteron a header toggles - Live filter —
/fuzzy-filters containers, pods and instances across every section (force-expands while filtering) - Live discovery — background worker polls every
kluster_refresh_secs(configurable in Settings)
- i18n — UI strings translatable; English + French bundled. Pick via
SSHM_LANG=fr - Themes — fully customizable colors via
theme.toml, with an optional transparent background that uses the terminal's own - Toast notifications — non-intrusive feedback for actions
- Desktop notifications — native OS alerts (
notify-send/osascript) when a background tunnel drops or a host changes reachability - Open in a new terminal —
olaunches the SSH session in a separate terminal window (auto-detected, or setexternal_terminal) - Auto-export — optionally writes a clean
~/.ssh/configon every save - CLI mode — scriptable commands for automation
brew tap Sn0wAlice/sshm https://github.com/Sn0wAlice/sshm
brew install sshmGrab the latest binary from the Releases page.
Linux (amd64)
curl -sL https://github.com/Sn0wAlice/sshm/releases/latest/download/sshm-linux-amd64.tar.gz | tar xz
sudo mv sshm /usr/local/bin/Linux (arm64)
curl -sL https://github.com/Sn0wAlice/sshm/releases/latest/download/sshm-linux-arm64.tar.gz | tar xz
sudo mv sshm /usr/local/bin/macOS (Apple Silicon)
curl -sL https://github.com/Sn0wAlice/sshm/releases/latest/download/sshm-darwin-arm64.tar.gz | tar xz
sudo mv sshm /usr/local/bin/git clone https://github.com/Sn0wAlice/sshm.git
cd sshm
cargo build --release
sudo cp target/release/sshm /usr/local/bin/Requirements:
- Rust stable toolchain (build only)
sshclient (always)dockerCLI on PATH (for the Docker section of the Kluster tab — local and remote)kubectlon PATH (for k8s/k3s clusters)incusCLI on PATH (for Incus instances)- A terminal with UTF-8 & ANSI support
The Kluster tab degrades gracefully — sections show (unavailable) when the corresponding CLI / daemon isn't reachable.
sshmThe TUI has 6 tabs (← / → to switch):
| Tab | Purpose |
|---|---|
| Hosts | SSH host list with folders, tags, tunnels, identity management |
| Kluster | Docker / Incus / k8s containers and pods |
| Identities | Local SSH keys (~/.ssh), generate, push, load into agent |
| Settings | Defaults, health-check intervals, kluster refresh, etc. |
| Theme | Pick / customize TUI colors |
| Help | In-app help |
- Add an SSH host in the Hosts tab pointing at a machine where Docker runs.
- Make sure your SSH user is in the
dockergroup on that host (ssh user@host docker psshould work). - In the Kluster tab, navigate to the
Docker (local)header and pressn. - Pick the host from the list. Done — sshm tunnels every
dockercall over SSH.
No ports opened, no TLS to set up, no dockerd socket exposed.
sshm list [--filter "expr"] # list hosts (filter: tag:foo host:1.* user:bar name:*xyz*)
sshm connect <name> [ssh-options...] # connect to a host (alias: c)
sshm create # interactively create a host
sshm edit # edit an existing host
sshm delete # delete a host
sshm tag add <name> <tag1,tag2> # add tags
sshm tag del <name> <tag1,tag2> # remove tags
sshm load_local_conf # import hosts from ~/.ssh/config
sshm export [path] # export DB as ~/.ssh/config format
sshm add-identity <name?> [--pub key] # push pubkey to authorized_keys
sshm help # full CLI reference| Key | Action |
|---|---|
← / → |
Switch tabs |
q |
Quit |
| Key | Action |
|---|---|
↑ / ↓ |
Navigate |
Enter |
Connect to host / expand-collapse folder |
/ or any letter |
Activate fuzzy filter |
1-9 |
Quick-connect to Nth visible host |
s |
Cycle sort mode (name / MRU / most used / favorites / frecency) |
g |
Toggle group-by-folder ⇆ group-by-tag |
f |
Toggle favorite on selected host |
c |
One-shot health check on selected host |
| Key | Action |
|---|---|
a |
Add a host (or folder when on a folder row) |
e |
Edit selected host |
y |
Clone selected host (full copy, opens the editor) |
d |
Delete selected host / folder |
p |
Open port-forward menu — start a tunnel in the background (f runs it foreground) |
t |
Background-tunnels dashboard — d/x stop a tunnel, o open a local tunnel's URL |
o |
Open the SSH session in a new terminal window |
i |
Push identity to selected host |
r |
Rename folder |
Space |
Toggle host in bulk selection |
T (Shift+t) |
Bulk-add tags to selected hosts |
D (Shift+d) |
Bulk-delete selected hosts (with confirm) |
C (Shift+c) |
Clear bulk selection |
X (Shift+x) |
Fan-out: run a command on every selected host |
The available actions depend on what's under the cursor.
| Key | When | Action |
|---|---|---|
↑/↓ j/k |
always | Navigate |
/ |
always | Fuzzy-filter containers / pods / instances (Esc clears) |
Enter |
on a header | Expand / collapse the section |
Enter |
on a container / pod / instance | Open /bin/sh (Ctrl+D to exit) |
l |
on a container / pod / instance | Stream logs -f (Ctrl+C returns to TUI) |
s |
on a Docker container / Incus instance | Start it if stopped, stop it if running |
R (Shift+r) |
on a Docker container / Incus instance | Restart it |
r |
always | Force a refresh now |
n |
on a Docker header | Pick a saved host → register a Docker remote |
n |
elsewhere | Add a new k8s/k3s cluster (TUI form) |
e |
on a Cluster header | Edit cluster (kubeconfig / context / namespace) |
d |
on a Cluster header | Unlink cluster from sshm (cluster itself untouched) |
d |
on a Docker remote header | Unlink Docker remote (host still in Hosts tab) |
d |
on a Succeeded / Failed pod | kubectl delete pod (with confirm) |
| Key | Action |
|---|---|
↑ / ↓ |
Navigate keys in ~/.ssh |
/ |
Fuzzy-filter keys by file name / type / comment (Esc clears) |
g |
Generate a new key (interactive: ed25519 / ed25519-sk / ecdsa / rsa) |
p |
Push selected pubkey to a host |
a |
Add selected key to ssh-agent |
x |
Remove selected key from ssh-agent |
K (Shift+k) |
Clean a hostname from ~/.ssh/known_hosts |
r |
Rescan ~/.ssh |
| Path | Purpose |
|---|---|
~/.config/sshm/host.json |
Hosts, folders, tunnels, ProxyJump |
~/.config/sshm/kluster.json |
Saved clusters + Incus remotes + Docker remotes |
~/.config/sshm/settings.toml |
Defaults, health & kluster intervals |
~/.config/sshm/theme.toml |
TUI color theme (optional) |
~/.config/sshm/tunnels/<pid>.json |
Live background tunnels per running instance — used to clean up after a crash |
The Settings tab (Tab → Settings) exposes:
- Default Port / Default Username / Default Identity File — used when creating new hosts
- Export Path — where to write the auto-exported
~/.ssh/config(empty = disabled) - Auto Health Check — toggle the background SSH probe
- Health Refresh / Cache TTL — seconds between probe rounds
- Probe Connect Timeout — TCP connect timeout in ms (banner read uses ~1/3)
- Kluster Refresh Interval — seconds between Docker / kubectl / Incus refreshes
- Kluster Log Tail — default
--tail Nforl(logs) - Desktop notifications — toggle native OS alerts (tunnel dropped, host up/down)
The Settings tab groups these into labelled sections (Defaults, Export, Health checks, Kluster, Notifications).
All values are live: hit Save and the background workers pick up the new TTL on the next tick.
external_terminal — a settings.toml-only key (not shown in the Settings tab). It's the command prefix used by the o hotkey to open a session in a new terminal window; the SSH command is appended to it. Leave it empty to auto-detect (wezterm, kitty, alacritty, gnome-terminal, konsole, xterm, or Terminal.app on macOS). Examples:
external_terminal = "kitty -e"
external_terminal = "wezterm start --"
external_terminal = "gnome-terminal --"notification_icon — another settings.toml-only key: a path (~ allowed) to a custom icon for desktop notifications.
notification_icon = "~/.config/sshm/icon.png"On Linux it's passed straight to notify-send -i. On macOS the default osascript notification cannot override its icon (it's always osascript's) — install terminal-notifier (brew install terminal-notifier) and SSHM will use it automatically to honour the custom icon.
bg = "#1e1e2e"
fg = "#cdd6f4"
accent = "#89b4fa"
muted = "#6c7086"
error = "#f38ba8"
success = "#a6e3a1"
transparent_bg = falseSet transparent_bg = true (or tick Transparent background in the Theme
tab) to drop the bg colour entirely and let your terminal's own background —
including any transparency / blur — show through. The bg hex is kept on disk
so unticking the box restores it.
SSHM_LANG=fr sshm # French
SSHM_LANG=en sshm # English (default)Falls back to the value of LC_ALL / LANG if SSHM_LANG is unset. Unknown locales fall back to English silently.
src/
├── main.rs # CLI dispatch
├── lib.rs # crate root
├── models.rs # Host, Tunnel, Database
├── history.rs # frecency, sort modes
├── i18n.rs # localization
├── locales/ # en.toml, fr.toml
├── filter/ # fuzzy + prefix-token matcher
├── config/ # io, path, settings, export
├── ssh/ # client, keys, agent, known_hosts, proxy
├── import/ # ~/.ssh/config parser
├── kluster/ # Docker / Incus / kubectl wrappers
│ ├── docker.rs # docker ps / exec / logs (local + DOCKER_HOST=ssh://)
│ ├── incus.rs # incus list / exec / logs (local + remotes)
│ ├── kube.rs # kubectl get/exec/logs/delete pod
│ ├── shell.rs # /bin/sh constant
│ └── db.rs # kluster.json + bootstrap from kubeconfig + incus remotes
├── tui/
│ ├── app/ # main loop + worker submodules
│ │ ├── health_worker.rs
│ │ ├── kluster_worker.rs
│ │ ├── kluster_actions.rs
│ │ ├── cluster_form.rs
│ │ ├── host_form.rs
│ │ └── key_flows.rs
│ ├── tabs/ # one file per tab
│ ├── ssh/ # host detail box, modals, toast, port forward
│ └── theme.rs
└── commands/ # CLI subcommands
PRs welcome — especially for:
- Terminal UX polish
- New runtime backends (LXD, Podman, ...)
- Platform support (Windows is currently best-effort)
- More translations (just drop a
src/locales/<code>.toml)
Run cargo test before sending a PR — the suite covers parsers (filter, kubeconfig, ssh_config, JSON migrations) and a handful of pure logic units (frecency, ssh banner, ProxyJump resolver, etc.).
Made by Sn0wAlice
