ports.tools
What’s running on your machine?
portsshows everything listening — which framework is running, which project owns it, which container it lives in, and how long it’s been up. Run it below. One static binary. One command.
PORTPROCESSPROJECTUPTIMElast scan: just now · click a row
the problem
Stop hunting PIDs.
The workflow every developer knows by heart — and none of it tells you which project you are looking at. Run both ways.
PORT PROCESS PROJECT UPTIME 3000 Next.js ~/projects/shop 14m 8000 FastAPI ~/projects/shop 14m 5432 PostgreSQL docker: shop-db 2d
One command. Context everywhere.
the model
Your ports aren’t the interesting part.
A port number is just an address. The interesting part is what answers there.
3000
a port — where a service listens
A port is just where a service listens.ports tells you what’s actually there.
the system
From socket to service.
Every row in the table is one of these chains: where it listens, what it is, who owns it, how long it’s been up.
a dev server
a database
A port is the start of the chain, not the answer. ports walks it to the end.
the interface
Four commands and a doctor.
ports, find, free, kill — and when one of them fails, ports doctor explains what your machine is actually doing. Run them right here; this terminal remembers what you stopped.
--json the same scan, structured--force skip the SIGTERM
both flags work above — this terminal is the whole interface
$ ports PORTPROCESSPROJECTUPTIME3000Next.js~/projects/shop14m5432PostgreSQLdocker: shop-db2d6379Redisdocker: shop-cache2d8000FastAPI~/projects/shop14m5173Vite~/projects/admin3h9229Node~/projects/api26m$
docker
Docker shouldn’t make this harder.
docker-proxy is all the OS reports. Behind it is the container you actually care about — toggle what each tool sees.
the port
what the OS reports
the container
what's actually running
resolved through the Docker daemon
PORT PROCESS PROJECT UPTIME 5432 PostgreSQL docker: shop-db 2d
ports follows the connection to the thing actually running it.
containers stop through the Docker daemon — never docker-proxy
the binary
Nothing running in the background.
One static Go binary. It scans when you run it and exits when it’s done — useful sixty seconds after install, on a machine in whatever state it’s in.
ONE BINARY
$ brew install pyjeebz/tap/ports $ ports PORTPROCESSPROJECTUPTIME3000Next.js~/projects/shop14m5432PostgreSQLdocker: shop-db2d6379Redisdocker: shop-cache2d8000FastAPI~/projects/shop14m5173Vite~/projects/admin3h9229Node~/projects/api26m
the contract
It observes. You decide.
ports reads the machine and reports. The only two things it will ever do to a process, it does because you said so.
$ ports
├──sees ├──identifies ├──maps └──reports
✗ starts anything
✗ supervises processes
✗ installs software
✓ stops only what you name
you
├── free └── kill
The two acts — naming a port to free, or a project to stop. Everything else is reading.
ports never starts your services. It never supervises them. It never installs anything. It tells you what’s there.
questions
Frequently asked questions.
How is this different from lsof?
lsof gives you a PID and a process name. ports replaces both with what you actually wanted: the framework, the project directory, the container behind docker-proxy, and the uptime.
read the introduction →Does it work with Docker?
Yes. docker-proxy rows are unwrapped over the Docker socket to the real container and Compose service behind them — and containers stop through the daemon, never the proxy.
see the commands →How does it know which project a port belongs to?
It walks the process working directory to the nearest repository or marker file. No configuration, no manifest — the context is inferred from the machine itself.
how it works →A port won't free. Now what?
ports doctor re-examines the port and tells you what the machine is actually doing — an uninterruptible process, a zombie, TIME_WAIT sockets, a Windows-side listener — each with the command that resolves it. It never acts; every fix is yours to run.
meet the doctor →Which platforms does it run on?
macOS, Linux, and Windows via Scoop. On WSL2 it distinguishes Windows-side listeners from WSL processes in the same table — and never offers the Windows side to the kill path.
installation →Does anything run in the background?
No. ports is one static Go binary with no daemon, no agent, and nothing you have to configure — a config file is never required. The optional ports-config.yaml is an extension layer for custom framework names and project markers, not something the tool needs to work. It scans when you run it and exits when it's done — `--watch` is the one exception, and it's still just a foreground loop that redraws the table until you interrupt it. It dies with its terminal.
the quickstart →get started
Sixty seconds from install to answers.
Install it, run it, see your machine. Nothing to configure first.