The frustration every dev knows
It always happens at the worst time. You sit down to work, run npm run dev, and:
Error: listen EADDRINUSE :::3000
Something is already on port 3000. You don't know what. You don't know if it's important. You just know your dev server won't start.
So you do the ritual. You open a new tab. You type lsof -i :3000. You squint at a wall of columns trying to find the PID. You copy it. You type kill -9 84921. You hold your breath and hope it wasn't PostgreSQL.
I did this hundreds of times. Every single day. Sometimes multiple times a day when switching between projects. And every time I thought: why is this still a multi-step, error-prone process in 2026?
So I started looking at existing tools. There were two popular ones: kill-port and fkill. And they both had the same fundamental problem.
The problem with kill-port and fkill
kill-port does exactly what the name says. You give it a port, it kills whatever is there. No questions asked. No context about what it's killing. No safety net. It's kill -9 with a friendlier API.
Here's what happens when you run kill-port 5432:
$ npx kill-port 5432
Process on port 5432 killed
That was PostgreSQL. Your database. With active connections. No warning, no confirmation. Just gone.
fkill is better about UX. It has a nice interactive mode and can search by process name. But it still doesn't understand what services are running. It can't tell you "hey, that's your production database, maybe don't nuke it."
Both tools also share an architectural problem: they're Node.js packages. That means:
- ~50MB installed size (node_modules for a tool that kills processes)
- Slow startup because V8 has to boot every time you run the command
- Node.js required as a runtime dependency (ironic when you're debugging a Node crash)
- No service detection because they match ports to PIDs, not ports to services
- No graceful shutdown because SIGKILL is the only option
I wanted something that actually understood my dev environment. Something that knew the difference between a Next.js dev server I can safely restart and a PostgreSQL instance I definitely should not touch. Something tiny, instant, and dependency-free.
So I built portrm.
Why Rust
I didn't start with Rust because it was trendy. I started with it because the constraints demanded it.
A port manager runs in the critical path of your workflow. You're already frustrated when you reach for it. The last thing you want is to wait for a tool to boot up. It needs to be instant.
Rust gave me three things no other language could:
1. Binary size. portrm compiles to a ~1.2MB static binary. No runtime. No interpreter. No node_modules. Just a single file you drop on your PATH. Compare that to kill-port's ~50MB installed footprint.
2. Startup time. portrm starts in under 5ms. There's no VM to warm up, no JIT to kick in. You type a command, you get output. The bottleneck is your OS APIs, not the tool.
3. Zero dependencies at runtime. The binary links everything statically. It doesn't need Node.js, Python, or any shared libraries. It works on a fresh machine, in a Docker container, or in CI with nothing else installed.
Rust also gave me something less tangible but equally important: confidence in correctness. When you're writing a tool that kills processes, you really don't want use-after-free bugs or race conditions. Rust's ownership model makes entire classes of concurrency bugs impossible at compile time.
What portrm does differently
portrm isn't just kill -9 with a prettier name. It actually understands what's running on your machine.
Service identification
When you run ptrm scan, portrm doesn't just list PIDs. It classifies every listening process into one of 13+ service categories: Node.js, Python, Rust, Java, Docker, PostgreSQL, MySQL, Redis, Nginx, and more. It does this by analyzing the process binary, command-line arguments, and listening patterns.
$ ptrm scan
⚡ 4 active ports
PORT SERVICE PID SAFETY MEM UP
─────────────────────────────────────────────────
3000 Next.js 59480 SAFE 14 MB 2h
8080 Express 61023 SAFE 22 MB 1h
5432 PostgreSQL 1204 WARNING 58 MB 14d
6379 Redis 1312 WARNING 12 MB 14d
3-tier safety system
This is the feature that made me build portrm in the first place. Every process gets classified into one of three safety tiers:
- SAFE - Dev servers (Next.js, Vite, Express, Django dev, Flask dev). Kill freely.
- WARNING - Infrastructure services (PostgreSQL, MySQL, Redis, Docker, Nginx). Requires confirmation and warns you about potential data loss.
- BLOCKED - System processes (PID 0, PID 1, launchd, systemd). Cannot be killed through portrm, period.
When you run ptrm fix 5432, portrm doesn't just kill it. It tells you:
$ ptrm fix 5432
⚠ PostgreSQL (PID 1204) - WARNING tier
This is a database with potential active connections.
Killing it may cause data loss.
Continue? (y/N)
Compare that to npx kill-port 5432 which just silently kills it and prints "done."
Graceful shutdown
portrm sends SIGTERM first, waits for a configurable timeout, and only escalates to SIGKILL if the process doesn't respond. Most dev servers handle SIGTERM gracefully, flushing buffers and closing connections properly. kill -9 gives them no chance to clean up.
Project configuration
Drop a .ptrm.toml in your repo and your whole team gets the same port setup:
[project]
name = "my-app"
[services.frontend]
port = 3000
run = "npm run dev"
[services.api]
port = 8080
run = "cargo run"
[services.db]
port = 5432
run = "pg_ctl start"
Then ptrm up starts everything. ptrm down stops everything. ptrm preflight checks all your ports are free before you start. ptrm doctor diagnoses what went wrong when something crashes.
Real benchmarks
Numbers matter. Here's how portrm compares to the alternatives on real hardware (M1 MacBook Pro):
| Metric | portrm | kill-port | fkill |
|---|---|---|---|
| Startup time | <5ms | ~200ms | ~180ms |
| Install size | ~1.2MB | ~48MB | ~52MB |
| Runtime deps | 0 | Node.js | Node.js |
| Service detection | 13+ categories | ✗ | ✗ |
| Safety tiers | SAFE / WARNING / BLOCKED | ✗ | ✗ |
The startup time difference is the one you feel. 5ms vs 200ms might not seem like much, but when you're running ptrm scan or ptrm fix multiple times in a session, it adds up. The tool feels instant in a way Node.js tools simply can't.
What's next
portrm is at v2.2.0 now and the core is solid. But there's more I want to build:
ptrm ui- A full TUI dashboard is already available. Browse all your ports, kill, restart, run doctor, all from an interactive terminal interface. No commands to memorize.ptrm watch- Real-time monitoring that detects when services crash and optionally auto-restarts them. Like a lightweight process supervisor that understands your.ptrm.toml.- CI integration -
ptrm ciandptrm preflightare designed for pipelines. Check that ports are free before running tests. Fail fast with clear error messages instead of cryptic EADDRINUSE races. - VS Code extension - Already available on the Marketplace. Sidebar dashboard with one-click start/stop/fix/doctor, profile switching, and smart terminal integration.
Try it yourself
portrm installs in 5 seconds, takes 1.2MB, and doesn't need Node.js:
$ npm i -g portrm # npm
$ brew install abhishekayu/tap/portrm # Homebrew
$ cargo install portrm # Cargo
$ pip install portrm # pip
$ npx portrm scan # try without installing
Then run ptrm scan and see what's actually running on your ports. You might be surprised.
If you've ever lost work to a blind kill -9, or if you're tired of the four-step lsof ritual, give portrm a shot. It's open source, MIT licensed, and I use it every single day.
GitHub · portrm.dev · npm · crates.io