Skip to main content
  ██████╗  ██████╗ ██████╗ ████████╗████████╗██████╗ ███████╗███████╗
  ██╔══██╗██╔═══██╗██╔══██╗╚══██╔══╝╚══██╔══╝██╔══██╗██╔════╝██╔════╝
  ██████╔╝██║   ██║██████╔╝   ██║      ██║   ██████╔╝█████╗  █████╗  
  ██╔═══╝ ██║   ██║██╔══██╗   ██║      ██║   ██╔══██╗██╔══╝  ██╔══╝  
  ██║     ╚██████╔╝██║  ██║   ██║      ██║   ██║  ██║███████╗███████╗
  ╚═╝      ╚═════╝ ╚═╝  ╚═╝   ╚═╝      ╚═╝   ╚═╝  ╚═╝╚══════╝╚══════╝

See Through Every Open Port. Instantly.

A zero-dependency CLI socket scanner and process tree resolver that unmasks Docker containers, links active Git branches, diagnoses Windows NAT exclusions, and kills zombie worker trees in one command.

CI GitHub Marketplace Version Python Version Zero Dependencies Tests Code style: ruff License: MIT


⚡ Quickstart • 💥 Problem vs. Solution • 📊 Feature Comparison • 🤖 GitHub Actions CI • 📦 Installation • ⚙️ CLI Reference • 🐳 Docker Unmasker • 🪟 Windows NAT Diagnoser • 🌲 Graceful Tree Killer • 🔬 Architecture • 🤝 Contributing


💥 The Problem vs. Solution

Every developer has stared at:

Error: listen EADDRINUSE: address already in use :::3000

Or attempted to spin up Postgres, Vite, Next.js, Redis, or Docker Compose only to have the port rejected.

The Pain with Traditional Tools:

  1. Opaque Process Names: lsof -i :3000 says node 48291. Which project? Which repo? Which branch? Is it Next.js, nodemon, or Vitest?
  2. The Docker Proxy Blind Spot: docker-proxy 19284 reveals zero information about which container, Docker Compose project, or service is occupying port 5432 or 80.
  3. The Ghost Windows Bug: On Windows, netstat -ano | findstr :50025 returns empty, yet socket binds throw WSAEACCES / EADDRINUSE because Hyper-V / WinNAT kernel port exclusions silently locked the range.
  4. Zombie Worker Leaks: Running kill -9 <PID> terminates the master orchestrator, leaving 4 child worker processes orphaned and holding the port forever.

Side-by-Side Terminal Comparison

┌────────────────────────────────────────────────────────┐   ┌────────────────────────────────────────────────────────┐
│           BEFORE: Traditional lsof / netstat           │   │                  AFTER: With PortTree                  │
├────────────────────────────────────────────────────────┤   ├────────────────────────────────────────────────────────┤
│ $ lsof -i :3000                                        │   │ $ porttree 3000                                        │
│ COMMAND   PID USER   FD   TYPE DEVICE SIZE NODE NAME   │   │                                                        │
│ node    48291 user   23u  IPv6 0x...   0t0  TCP *:3000 │   │ PORT 3000 (TCP) [LISTEN] • 0.0.0.0:3000                │
│                                                        │   │  └─ npm run dev (PID 48280) [git: feat/auth (frontend)]│
│ (Which node? What repo? What branch? What workers?)    │   │     └─ nodemon (PID 48285)                             │
│                                                        │   │        └─ node server.js (PID 48291)                   │
│ $ lsof -i :5432                                        │   │           ├─ node worker.js (PID 48295)                │
│ docker-pr 19284 root 4u IPv4 0x... 0t0 TCP *:5432      │   │           └─ node worker.js (PID 48296)                │
│ (Which container? What service? Which compose file?)   │   │                                                        │
│                                                        │   │ $ porttree 5432                                        │
│ $ netstat -ano | findstr :50025                        │   │ PORT 5432 (TCP) [LISTEN] • 0.0.0.0:5432                │
│ (Returns nothing! Port still throws EADDRINUSE!)       │   │  ├─ 🐳 Docker: postgres-db [db @ my-saas-app] (pg:16)  │
│                                                        │   │  └─ docker-proxy (PID 19284)                           │
│ $ kill -9 48291                                        │   │                                                        │
│ (Worker 48295 is now orphaned and still locks port!)   │   │ $ porttree free 3000 -y                                │
│                                                        │   │ ✅ Successfully freed port 3000. Terminated 5 processes.│
└────────────────────────────────────────────────────────┘   └────────────────────────────────────────────────────────┘

📊 Feature Comparison

Capability PortTree lsof netstat ss fuser psutil CLI
Zero Dependencies (<15ms Cold Start) ✅ Yes ✅ Yes ✅ Yes ✅ Yes ✅ Yes ❌ Requires C wheels
Hierarchical Process Tree (Parent + Children) ✅ Yes ❌ No ❌ No ❌ No ❌ No ⚠️ Flat list
Active Git Branch & Repo Linking ✅ Yes ❌ No ❌ No ❌ No ❌ No ❌ No
Docker Container & Compose Unmasking ✅ Yes ❌ No ❌ No ❌ No ❌ No ❌ No
Windows Hyper-V / WinNAT Exclusion Detector ✅ Yes ❌ N/A ❌ No ❌ N/A ❌ N/A ❌ No
1-Command Tree Killer (porttree free) ✅ Yes ❌ No ❌ No ❌ No ⚠️ -k (no tree) ❌ Manual script
Pre-Flight Stack Collision Checker ✅ Yes ❌ No ❌ No ❌ No ❌ No ❌ No
Native Cross-Platform (Linux, macOS, Win) ✅ Yes ❌ macOS/Linux ⚠️ Windows format ❌ Linux only ❌ Linux only ✅ Yes
Rich ANSI & Machine-Readable JSON ✅ Both ❌ Plaintext ❌ Plaintext ❌ Plaintext ❌ Plaintext ⚠️ Custom code
Official GitHub Action for CI ✅ Yes ❌ No ❌ No ❌ No ❌ No ❌ No

⚡ Quickstart

Run PortTree instantly without installing using uvx:

# Scan all open listening ports and view process trees
uvx porttree

# Inspect specific port 3000
uvx porttree 3000

# Free port 3000 and all spawned child workers
uvx porttree free 3000 -y

# Pre-flight check docker-compose.yml and package.json before dev boot
uvx porttree check --fail

Or install globally via pip or pipx:

pip install porttree
# or
pipx install porttree

🐳 Docker Container Unmasking

When running Docker Desktop or Podman, binding ports like 8080:80 or 5432:5432 causes docker-proxy to listen on the host. Standard system tools only report docker-proxy (PID 19284).

PortTree unmasks the underlying container metadata:

  • Container Name (web-api-1)
  • Docker Image (postgres:16-alpine)
  • Docker Compose Project (ecommerce-backend)
  • Docker Compose Service (postgres)
  • Compose Working Directory (/home/user/apps/ecommerce)
$ porttree 5432

PORT 5432 (TCP) [LISTEN] • 0.0.0.0:5432
 ├─ 🐳 Docker: postgres-db-1 (postgres:16-alpine) [db @ ecommerce-backend]
 └─ docker-proxy (PID 19284)
      $ /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 5432 -container-ip 172.18.0.2 -container-port 5432

🪟 Windows Hyper-V / WSL2 Dynamic Port Exclusion Diagnoser

On Windows 10/11 and Windows Server, Hyper-V and WSL2 frequently reserve large dynamic port ranges for container networking. When a developer service tries to bind to a port within these ranges, the OS returns WSAEACCES / EADDRINUSE even though no process is visible in netstat or Task Manager.

PortTree detects these kernel-level exclusions and prints copy-paste remediation commands:

$ porttree winnat 50025

⚠️  Port 50025 is reserved by Windows Kernel (Hyper-V / WinNAT / WSL2)
   Range: 50000 – 50059 (TCP) [Administered]

   Root Cause:
   Windows NAT (WinNAT) or Hyper-V reserved this port range for internal container/WSL2 routing.
   No user-space PID exists in netstat or Task Manager, yet socket binds fail with EADDRINUSE (WinError 10013).

   Recommended Solutions:
   1. Restart WinNAT (instant fix, frees reserved pool):
      powershell -Command "net stop winnat; net start winnat"

   2. Reconfigure dynamic port pool (prevents future collision with dev ports):
      netsh int ipv4 set dynamicport tcp start=49152 num=16384

   3. Restart Host Network Service (if running Docker/WSL2):
      net stop hns && net start hns

🌲 Graceful Process Tree Killer (porttree free)

Modern developer stacks spawn trees of processes: npm run dev ➔ nodemon ➔ node cluster master ➔ node worker 1..4.

Killing just the PID returned by lsof frequently leaves orphaned workers still listening on the socket, or causes nodemon to respawn them immediately.

porttree free <PORT>:

  1. Discovers the socket owner and all descendant worker processes recursively.
  2. Displays the full process tree and prompts for confirmation (or use -y / --yes).
  3. Sends SIGTERM to the entire tree (children first, then parent).
  4. Monitors graceful shutdown for up to 3.0 seconds (-t 3.0).
  5. Automatically escalates to SIGKILL (or taskkill /F /T on Windows) if processes refuse to terminate.
  6. Re-scans the port to verify that the socket is 100% free.
# Preview what would be terminated without killing anything
porttree free 3000 --dry-run

# Gracefully terminate parent and workers
porttree free 3000

# Immediate force kill for unresponsive servers
porttree kill 8080 -f -y

🤖 GitHub Actions CI Integration

Stop mysterious port already allocated or EADDRINUSE failures in CI pipelines before running integration tests or starting test containers:

- name: Pre-Flight Port Collision Check
  uses: umutgungorr/porttree@v1
  with:
    check-file: 'docker-compose.yml, package.json'
    fail-on-conflict: 'true'
    summary: 'true'

Action Outputs & Features:

  • Populates $GITHUB_STEP_SUMMARY with a formatted Markdown report.
  • Provides step outputs: steps.porttree.outputs.open-ports, steps.porttree.outputs.conflict-count.
  • Fails with exit code 1 if any declared port is already occupied.

⚙️ CLI Reference

1. Port Inspection & Scanning

porttree                    # Scan all listening sockets
porttree 3000               # Inspect port 3000
porttree 3000,8080,5432     # Inspect specific comma-separated ports
porttree 8000-8010          # Inspect port range
porttree --tcp              # Filter for TCP only
porttree --udp              # Filter for UDP only
porttree --compact          # Tabular 1-line per port summary
porttree --json             # Raw machine-readable JSON
porttree --no-color         # Plaintext output (also honors NO_COLOR=1)
porttree --ascii            # Use ASCII tree branches (|--, \--)

2. Freeing & Killing Ports

porttree free 3000          # Graceful kill with confirmation prompt
porttree free 3000 -y       # Skip confirmation (non-interactive)
porttree kill 3000 -f -y    # Force kill immediately with SIGKILL
porttree free 3000 -t 5.0   # Graceful wait timeout of 5 seconds
porttree free 3000 --dry-run# Preview processes without terminating

3. Pre-Flight Stack Check

porttree check              # Auto-scans docker-compose.yml, package.json, and .env
porttree check --fail       # Exit code 1 if conflicts found (ideal for CI/git hooks)
porttree check compose.yml  # Scan explicit compose file
porttree check -p 3000,8080 # Add explicit port checks

4. Windows NAT Diagnostics

porttree winnat             # List all Windows Dynamic Port Exclusion ranges
porttree winnat 50025       # Diagnose specific port collision

🔬 Pipeline Architecture

┌────────────────────────────────────────────────────────────────────────┐
│                          PORTTREE ARCHITECTURE                         │
└────────────────────────────────────────────────────────────────────────┘

  [ Operating System Layer ]
  ┌───────────────────────┐  ┌───────────────────────┐  ┌───────────────────────┐
  │         Linux         │  │         macOS         │  │        Windows        │
  │ /proc/net/tcp, ss,    │  │  lsof -iTCP -sTCP:    │  │ netstat -ano,         │
  │ /proc/[pid]/fd socket │  │  LISTEN, lsof -p cwd  │  │ Win32_Process, netsh  │
  └───────────┬───────────┘  └───────────┬───────────┘  └───────────┬───────────┘
              │                          │                          │
              └──────────────────┬───────┴──────────────────────────┘
                                 ▼
                     [ Cross-Platform Scanner ]
                     Extracts: Protocol, IP, Port, PID
                                 │
                                 ▼
                     [ Process Tree Resolver ]
                     • Resolves parent orchestrator (npm, nodemon, cargo)
                     • Traverses child worker hierarchies
                     • Resolves CWD & inspects .git/HEAD for branch name
                                 │
                                 ▼
                     [ Docker / Runtime Unmasker ]
                     • Queries docker/podman daemon for exposed port mappings
                     • Resolves container names, images, Compose project & service
                                 │
                                 ▼
                     [ Windows NAT Exclusion Detector ]
                     • Parses dynamic port ranges from netsh
                     • Diagnoses WinNAT / Hyper-V kernel reservations
                                 │
                                 ▼
                     [ ANSI Tree / JSON Formatter ]
                     • Rich UTF-8 ANSI tree renderer with badges
                     • Compact table & JSON output modes

🤝 Contributing

Contributions are warmly welcomed! Please see CONTRIBUTING.md for local development setup, test execution guidelines, and code standards.

# Clone the repository
git clone https://github.com/umutgungorr/porttree.git
cd porttree

# Run tests
uv run --with pytest --with pytest-cov pytest

# Lint and format
uv run --with ruff ruff check .
uv run --with ruff ruff format .

⚡ Part of the Developer Diagnostics Suite

PortTree is part of the zero-dependency Developer Diagnostics Suite by @umutgungorr:

  • 🌲 PortTree: Dev socket tree scanner, Docker proxy unmasker, and process-tree killer.
  • ⚡ LogShrink: Zero-config CLI stream filter that collapses noisy polling logs and auto-boxes stack traces.
  • 🩺 RepoPulse: Git forensic health, architectural hotspot churn, and bus-factor risk engine.

📄 License

This project is licensed under the terms of the MIT License.
Copyright (c) 2026 Umut Güngör.

Metadata

Release files for porttree 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for porttree 1.0.0
File Size Uploaded
porttree-1.0.0.tar.gz 91.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for porttree 1.0.0
File Interpreter ABI Platform
porttree-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 131.4 kB

Release files / porttree-1.0.0.tar.gz

Download URL porttree-1.0.0.tar.gz
Size 91.3 kB
Tags Source
SHA-256 checksum
How to use checksums
5c699a6c2c287e2965d28c4352cccf226b88a7e1b70c6603617f137be5f8beb4
BLAKE2b-256 checksum
How to use checksums
a69890d0606242d9f7fdfd22b6e98b4288141c788bb8c34bec794b787858b210
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.4 {"installer":{"name":"uv","version":"0.12.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / porttree-1.0.0-py3-none-any.whl

Download URL porttree-1.0.0-py3-none-any.whl
Size 40.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8b2d22e39ece38adde111cb7961f720d71042f375c46372eddf87ae0d64412e8
BLAKE2b-256 checksum
How to use checksums
c37bd214f466950768299d472283315813250329a7e79a50d7e7bf72aaf92e7e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.4 {"installer":{"name":"uv","version":"0.12.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page