The Localhost Control Plane for Modern Developers.
PortManager is a lightweight daemon that manages local TCP ports. It acts as a central authority for port allocation, preventing conflicts when running multiple services locally.
One binary. One port. API + Dashboard included.
# Install via Homebrew (macOS)
brew install bruchmann-tec/tap/portmanager
# Run any command with automatic port allocation
portctl run my-api -- npm start
# → Allocated port 8000, sets $PORT, releases on exit
# See what's running
portctl list
# Open Dashboard
open http://localhost:3030You're running 5 microservices, 2 AI agents, a database, and a frontend locally. Each needs a port. You edit .env files, restart services, get "Address already in use" errors, and waste 20 minutes figuring out who's using port 3000.
PortManager fixes this.
| Approach | When to Use | Limitation |
|---|---|---|
| Hardcoded ports | Solo monolith | Conflicts with multiple projects |
| Docker Compose | Containerized apps | Host port mapping still manual |
| Kubernetes | Production | Overkill for local dev |
| PortManager | Local development | Made for this |
- Zero Integration:
portctl runinjects$PORTautomatically - no code changes - Service Discovery: Find any service by name via API or CLI
- Persistent Leases: Survives daemon restarts (SQLite backend)
- Auto-Cleanup: Crashed processes release ports automatically (TTL-based)
- Built-in Dashboard: Visual overview at
localhost:3030 - REST API: Language-agnostic integration
- Single Binary: ~4MB, no runtime dependencies
brew install bruchmann-tec/tap/portmanager
# Starts automatically as background service
# Dashboard: http://localhost:3030# Clone and build
git clone https://github.com/bruchmann-tec/portmanager.git
cd portmanager/port_manager
cargo build --release
# Install binaries
cp target/release/daemon ~/.local/bin/portmanager-daemon
cp target/release/client ~/.local/bin/portctl
# Start daemon
portmanager-daemon# Create LaunchAgent for auto-start
cat > ~/Library/LaunchAgents/com.bruchmann-tec.portmanager.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.bruchmann-tec.portmanager</string>
<key>ProgramArguments</key>
<array>
<string>/Users/YOUR_USERNAME/.local/bin/portmanager-daemon</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
EOF
# Load and start
launchctl load ~/Library/LaunchAgents/com.bruchmann-tec.portmanager.plist# Basic usage - injects PORT environment variable
portctl run my-api -- npm start
# Custom environment variable
portctl run my-db --env-name DATABASE_PORT -- ./start-postgres.sh
# Custom TTL (10 minutes instead of default 5)
portctl run my-service --ttl 600 -- python server.pyYour app just needs to read process.env.PORT (Node), os.environ['PORT'] (Python), or std::env::var("PORT") (Rust). Most frameworks do this by default.
# Manual allocation
portctl alloc my-service
# → Allocated port: 8000
# List all active leases
portctl list
# → Port: 8000, Service: my-service, TTL: 300s
# Find a service
portctl lookup my-service
# → 8000
# Release manually
portctl release 8000Open http://localhost:3030 in your browser.
The dashboard shows all active port allocations in real-time.
All endpoints are available at http://localhost:3030.
| Method | Endpoint | Description |
|---|---|---|
POST |
/alloc |
Allocate a port |
POST |
/release |
Release a port |
POST |
/heartbeat |
Renew lease TTL |
GET |
/list |
List all leases |
GET |
/lookup?service=<name> |
Find port by service name |
GET |
/ |
Dashboard UI |
curl -X POST http://localhost:3030/alloc \
-H "Content-Type: application/json" \
-d '{"service_name": "my-api", "ttl_seconds": 300}'
# {"port":8000,"lease":{"port":8000,"service_name":"my-api",...}}# Start backend
portctl run backend -- node server.js &
# Frontend finds backend dynamically
BACKEND=$(portctl lookup backend)
curl http://localhost:$BACKEND/api/health# PortManager assigns the host port
portctl run my-redis -- docker run -p $PORT:6379 redisimport os
port = int(os.environ.get('PORT', 8080))
app.run(port=port)portctl run my-flask -- python app.pyconst port = process.env.PORT || 3000;
app.listen(port);portctl run my-express -- node server.jslet port: u16 = std::env::var("PORT")
.unwrap_or("8080".into())
.parse()
.unwrap();portctl run my-rust -- cargo run┌─────────────────────────────────────────────────────────┐
│ PortManager Daemon │
│ localhost:3030 │
├─────────────────────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────────┐ │
│ │ /alloc │ │ /release│ │ /list │ │ Dashboard │ │
│ └─────────┘ └─────────┘ └─────────┘ └───────────┘ │
├─────────────────────────────────────────────────────────┤
│ SQLite Storage │
│ ~/.portmanager/leases.db │
└─────────────────────────────────────────────────────────┘
Port Range: 8000-9000 (1000 ports available)
Default TTL: 300 seconds (5 minutes)
Cleanup: Every 10 seconds, expired leases are removed
| Setting | Default | Description |
|---|---|---|
| Port Range | 8000-9000 | Available ports for allocation |
| Default TTL | 300s | Lease duration if not specified |
| Cleanup Interval | 10s | How often expired leases are removed |
| Database | ~/.portmanager/leases.db |
SQLite storage location |
| Listen Address | 127.0.0.1:3030 |
Daemon bind address |
PM_PORT_MIN |
8000 |
Start of port range (Environment Variable) |
PM_PORT_MAX |
9000 |
End of port range (Environment Variable) |
The daemon isn't running. Start it:
portmanager-daemon
# or via launchctl
launchctl start com.bruchmann-tec.portmanagerAnother process is using the daemon port:
lsof -i :3030
kill <PID>Check if the process crashed without cleanup. List and manually release:
portctl list
portctl release <port>- SQLite persistence
- Service discovery (
/lookup) - Zero-integration wrapper (
portctl run) - Embedded dashboard
- LaunchAgent support (macOS)
- Homebrew formula
- Linux systemd service
- WebSocket for real-time dashboard
- Port range configuration
- Multi-user namespaces
Contributions welcome! Please open an issue first to discuss what you'd like to change.
MIT License - see LICENSE for details.
PortManager is developed by BRUCHMANN[TEC] INNOVATION GMBH.
- Web: bruchmann-tec.com
- Email: conrad@bruchmann-tec.com
Built with Rust. No JavaScript required to run.
