Skip to content

The Local Network Control Plane & Ingress Engine

Never debug an EADDRINUSE crash again. Allocate deterministic ports, audit listening sockets, gracefully kill zombie processes, and connect local servers to the Edge in a single command.
curl -fsSL https://portctl.pages.dev/install.sh | sh
irm https://portctl.pages.dev/install.ps1 | iex
cargo install portctl-cli
brew install tackelua/tap/portctl

Select a command to preview how portctl inspects sockets, executes graceful process termination, and configures Cloudflare edge ingress:

sh ~ portctl v2.1.0
$ portctl list
┌──────────────────────┬──────┬──────────┬──────────────┬────────────────────────────┐
│ APPLICATION │ PORT │ SCOPE │ STATUS │ TUNNEL DOMAIN │
├──────────────────────┼──────┼──────────┼──────────────┼────────────────────────────┤
│ web-frontend │ 3000 │ global │ ● ACTIVE │ frontend.example.com │
│ auth-service │ 4001 │ local │ ● ACTIVE │ - │
│ redis-cache │ 6379 │ internal │ ● PROTECTED │ - │
│ billing-worker │ 4004 │ local │ ○ IDLE │ - │
│ api-gateway │ 8080 │ global │ ● ACTIVE │ api.example.com │
└──────────────────────┴──────┴──────────┴──────────────┴────────────────────────────┘
✓ 5 services registered | 4 active sockets probed in 2.2ms
$ portctl check 3000
🔍 Inspecting TCP Socket :3000...
● Port 3000 is BUSY (Allocated to 'web-frontend')
├─ PID: 84920
├─ Process: node (/Users/dev/.nvm/versions/node/v24.22.0/bin/node)
├─ User: gith
├─ Memory: 118.4 MB RSS
└─ Ingress: https://frontend.example.com (Cloudflare Tunnel)
$ portctl kill 3000
⚡ Identified process 'node' (PID 84920) holding port 3000
==> Sending SIGTERM (graceful shutdown sequence)...
==> Polling process termination (3s grace period)...
==> Process 84920 terminated gracefully. Socket :3000 released!
✓ Port 3000 is now free for allocation.
$ portctl expose web-frontend -d frontend.example.com
==> Validating RFC 1123 domain syntax: frontend.example.com... OK
==> Verifying socket security guardrails (port 3000)... OK
==> Checking Cloudflare DNS routing record... OK
==> Updating ingress rules in ~/.cloudflared/config.yml... OK
==> Reloading cloudflared daemon... OK
✨ Service 'web-frontend' is now LIVE on the Edge!
🔗 https://frontend.example.com ➔ http://127.0.0.1:3000
$ portctl doctor --fix
🩺 Running portctl diagnostics suite v2.1.0...
[PASS] Lockfile integrity (~/.config/portctl/.lock)
[PASS] Canonical app registry valid (canonical-apps.json)
[WARN] Dangling ingress route found for removed service 'temp-poc'
[WARN] File permissions on ports.json is 0644 (expected 0600)
==> [FIX] Purging dangling ingress rule 'temp-poc'... DONE
==> [FIX] Updating ~/.config/portctl/ports.json mode to 0600... DONE
✓ System health 100% OPERATIONAL. Zero collisions detected.

⚡ Sub-Millisecond Speed

Written in pure Rust with zero daemon overhead and zero runtime dependencies. Queries complete in < 3ms with sub-megabyte memory footprint.

🛡️ POSIX Advisory Locking

Atomic writes and fd-lock file advisory locks eliminate race conditions across concurrent terminal sessions. Strict 0600 permissions and built-in protection against exposing sensitive ports (22, 5432, 6379).

🧠 Polyglot Auto-Detection

Automatically identifies project names from package.json, Cargo.toml, pyproject.toml, go.mod, pom.xml, composer.json, or Git remotes without manual naming.

🌐 Automated Edge Ingress

1-click Cloudflare Tunnel automation. Validates ingress syntax, configures DNS routing, and updates daemons with zero YAML wrangling.


Execution latency comparison for resolving and probing local socket allocations:

Tool Runtime Architecture Average Latency Daemon RAM
portctl (Rust) Native Binary Zero-Daemon < 2.4 ms 0 MB
Custom Bash / lsof Script Shell /bin/bash Child Processes ~18.5 ms 0 MB
Legacy Node.js CLI V8 / Node.js JIT Runtime ~165 ms ~45 MB
Desktop Container Apps Electron / Hypervisor Background Daemon > 500 ms > 2,000 MB