The problem
Every developer has been here: something is hogging port 3000 and you need to find out what.
On Linux you try ss -tlnp | grep 3000. On macOS it's lsof -i :3000. On Windows... good luck. Each gives different output, different flags, and none of them tell you how long the process has been running, how much memory it's eating, or whether it's a Docker container.
I got tired of this. So I built portview.
One command, everything you need
$ portview
That's it. Every listening port, the process behind it, PID, user, uptime, memory usage, and the full command -- in a colored table. Cross-platform. ~1.3 MB single binary. Zero runtime dependencies.
PORT PROTO PID USER PROCESS UPTIME MEM COMMAND
3000 TCP 48291 mark node 3h 12m 248 MB next dev
5432 TCP 1203 pg postgres 14d 2h 38 MB /usr/lib/postgresql/16/bin/postgres
6379 TCP 1198 redis redis 14d 2h 12 MB redis-server *:6379
8080 TCP 51002 mark python3 22m 45 MB uvicorn main:app --port 8080
No parsing lsof output through awk. No remembering whether it's -tlnp or -tulpn. Just portview.
But I didn't stop there
A port viewer that just lists ports isn't worth writing about. Here's what makes portview different:
Interactive TUI with tree view
portview watch opens a live-refreshing TUI. Navigate with j/k, press Enter to inspect a port in detail (full command, working directory, child processes, open connections), press d to kill it.
Press t to toggle tree view -- it groups child processes under their parents so you can see which workers belong to which master process:
PROCESS
node
├── node (worker)
├── node (worker)
└── node (worker)
postgres
└── postgres: autovacuum
Docker as a first-class citizen
portview watch --docker shows Docker containers as rows in the table. No more running docker ps in a separate terminal and cross-referencing ports. Press d on a container row to Stop, Restart, or tail Logs.
portview doctor
This is my favorite feature. portview doctor runs five diagnostic checks on your ports:
$ portview doctor
✓ No port conflicts
✗ postgres is listening on 0.0.0.0:5432 -- consider binding to 127.0.0.1
✓ No Docker-host conflicts
✓ No stale connections
✓ No high-resource listeners
It catches: port conflicts (two processes fighting over the same port), databases exposed on all interfaces, Docker port collisions with host processes, TIME_WAIT/CLOSE_WAIT pileups, and memory hogs. Use portview doctor --json with exit code 1 on errors for CI.
SSH remote mode
portview ssh user@server # scan remote ports
portview ssh user@server watch # full remote TUI
portview ssh user@server doctor # remote diagnostics
It shells out to your system ssh binary (inherits your config, keys, ProxyJump, everything), runs portview --json on the remote host, and renders the output locally. Kill actions in the remote TUI are forwarded over SSH. No agents, no daemons, no new ports to open.
How it works
No shelling out to lsof or ss. portview reads directly from the OS:
-
Linux: Parses
/proc/net/tcp, maps inodes to PIDs via/proc/*/fd/, reads process metadata from/proc/<pid>/statusand/proc/<pid>/stat -
macOS: Uses
libprocFFI (proc_listpids,proc_pidfdinfo,proc_pidinfo) -
Windows: Uses
iphlpapi(GetExtendedTcpTable) andkernel32(CreateToolhelp32Snapshot,GetProcessTimes)
This is why it's fast -- there's no subprocess overhead.
Install
curl -fsSL https://raw.githubusercontent.com/mapika/portview/main/install.sh | sh # Linux/macOS
brew install mapika/tap/portview # Homebrew
cargo install portview # Cargo
Or grab a binary from the releases page.
What's next
The codebase is ~6K lines of Rust. MIT licensed. Contributions welcome.
If you've ever been frustrated by lsof, give it a try and let me know what you think: github.com/Mapika/portview
Top comments (0)