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.

Get startedGitHub ↗
localhost● live — 4 listening
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.

ports find shop3 services · 1 project
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 processNext.js
a project~/projects/shop
uptime14m

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

:3000
Next.js
~/projects/shop
14 minutes

a database

:5432
PostgreSQL
docker: shop-db
2d

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 — your machine6 listening
$ ports
PORTPROCESSPROJECTUPTIME3000Next.js~/projects/shop14m5432PostgreSQLdocker: shop-db2d6379Redisdocker: shop-cache2d8000FastAPI~/projects/shop14m5173Vite~/projects/admin3h9229Node~/projects/api26m$
click a command or type one

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.

:5432

the port

docker-proxy

what the OS reports

shop-db

the container

PostgreSQL

what's actually running

resolved through the Docker daemon

ports find postgresresolved
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

NO DAEMONNO CONFIGNO AGENTNO SUPERVISOR
install and first runsixty seconds
$ 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.

GitHub ↗Documentation

alsocurl — any POSIX machineScoop — Windows