Lesson 7 of 7 · 9 min

The source tells you how to control it

Terminal commands, Homebrew services, containers, IDE helpers, app helpers, and macOS services have different lifecycle rules. The right owner matters more than the loudest PID.

What you'll learn

  • The mental model
  • Step 1: find the listener
  • Step 2: inspect the command

The same port problem can have different correct fixes.

If a terminal command started the listener, stop the terminal command.

If Homebrew started it, stop the Homebrew service.

If Docker started it, stop the container.

If an app or IDE started it, quit or configure the app.

The source tells you how to control it.

The mental model

Do not ask only:

Which PID owns this port?

Ask:

What system is responsible for this listener?

A PID is the current body. The source is the control panel.

Step 1: find the listener

lsof -nP -iTCP:3000 -sTCP:LISTEN

Example:

COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
node    48217 you   21u  IPv4 ...        TCP 127.0.0.1:3000 (LISTEN)

Step 2: inspect the command

ps -p 48217 -o pid,ppid,user,stat,comm,args

The args column often tells you what started the process.

Look for clues:

vite
next
react-scripts
uvicorn
manage.py runserver
redis-server
postgres
Docker
Ollama

Step 3: inspect the folder

lsof -a -p 48217 -d cwd

The folder may reveal the project that launched the listener.

Step 4: choose the right control method

Use the owner’s control method whenever possible.

SourceCluesPreferred control
Terminal commandParent is shell; command looks like npm, python, uvicorn, viteReturn to terminal and press Control-C
Frontend dev servernode, vite, next, project folderControl-C, or change the dev server port
Python dev serverpython3, uvicorn, manage.py, http.serverControl-C in the launching terminal
Homebrew serviceDatabase/server process, parent often service-likebrew services list, then brew services stop <name>
Docker containerPort appears through Docker or container toolingdocker ps, then docker stop <container>
IDE helperParent or folder points to editor toolingStop the run task inside the IDE
macOS app/helperApp name appears in command or parentQuit app or disable its background helper
launchd serviceParent is 1 or service restarts automaticallyDisable/stop the launch agent or app setting

Terminal-started process

If you started a dev server manually, the clean stop is usually:

Control-C

Use this for commands like:

npm run dev
pnpm dev
python3 -m http.server 3000
uvicorn app.main:app --reload --port 8000
python3 manage.py runserver

This lets the process shut down normally.

Homebrew service

For databases and background tools installed through Homebrew:

brew services list

Then stop the matching service:

brew services stop <service-name>

Examples:

brew services stop redis
brew services stop postgresql@16

This is better than killing the PID because it tells the service manager to stop supervising the process.

Docker container

Check running containers:

docker ps

Stop the container:

docker stop <container-name-or-id>

If a port is published by Docker, killing a random process on the host is usually the wrong move. Control the container.

IDE helper

Sometimes VS Code, JetBrains, Cursor, or another editor starts the process for you.

Clues include:

  • the parent process points to the editor
  • the folder is a project opened in the editor
  • the command includes debug or helper arguments
  • the process returns when the IDE is still running

Use the IDE’s stop button, task runner, or terminal panel.

App helper or background service

Some local services are controlled by desktop apps or menu bar apps.

Examples include databases, model runners, sync clients, and developer utilities.

If the listener returns after you kill it, look for an app-level setting:

  • Quit the app.
  • Disable “launch at login.”
  • Disable background service.
  • Stop the helper from the app’s own UI.

The safe stopping order

Use this order before reaching for force-kill:

  1. Stop it from the terminal or IDE that launched it.
  2. Stop the service manager, such as Homebrew.
  3. Stop the container, if Docker owns it.
  4. Quit the desktop app that owns it.
  5. Use normal kill <PID>.
  6. Use kill -9 <PID> only as a last resort.