macOS · local dev · TCP

One port.
Many workers.

Portshare is a tiny development load balancer for your Mac. Run several copies of your app behind a single localhost port — even apps that hardcode 3000 and can’t be told otherwise.

$ portshare run 3030 --workers 2 --intercept -- bun examples/server.ts
curl client :3030 broker worker 1→ :51842 worker 2→ :55107 worker 3→ :58830 worker 4→ :61294 app thinks it’s on :3030
How it works

SO_REUSEPORT-style balancing, on a Mac.

On Linux, several copies of an app can share one port and the kernel spreads connections between them. macOS doesn’t balance listeners that way, so Portshare does it for you: a broker owns the public port, each worker gets a private one, and every new TCP connection goes to the next ready worker.

01

The broker takes the port

A small Rust broker listens on 127.0.0.1:PORT and a private control socket.

portshare ready on 127.0.0.1:3030
control /tmp/portshare-you-3030.sock
02

Workers get private ports

Each copy gets its own loopback port — via PORT, a {port} placeholder, or native interception of a hardcoded bind().

worker 1 ready: pid 48211, 3030 -> 127.0.0.1:51842
worker 2 ready: pid 48212, 3030 -> 127.0.0.1:55107
03

Connections round-robin

Each new TCP connection goes to the next worker. Keep-alive, WebSockets and SSE stay put for their lifetime.

$ curl -s :3030 | jq .message
"Hello from 1!"
$ curl -s :3030 | jq .message
"Hello from 2!"
Interactive demo

Take it for a spin — no install.

A faithful simulation of the real CLI, running entirely in your browser. Type commands into either terminal, or use the shortcuts. Try breaking it, too: drop --intercept against the hardcoded-port example, or kill a worker.

mode
workers
3
1zsh — ~/portshare
2zsh — ~/portshare
live traffic
3portshare status 3030 --watch

          
↑ historyCtrl+C stopTab switch panetry: help, cat examples/server.ts, portshare serve 3030
Features

Small on portpose.

No config files, no daemons to install, no proxy runtime. Just one command.

Hardcoded ports, handled

--intercept loads a native helper that redirects the app’s bind() to a private port. Readiness is confirmed by an actual registered listener, not a log line.

Live terminal view

status --watch shows each worker’s PID, private port, active connections and bytes in each direction, updating while streams stay open. --json for scripts.

Clean ownership

Ctrl-C stops the workers and the broker that command owns. It never silently takes over an existing session or deletes a socket it didn’t create.

Configurable apps too

Apps that read PORT just work. CLI flags can use a {port} placeholder. Each worker also gets PORTSHARE_WORKER.

Honest TCP forwarding

Bounded 64 KiB buffers per direction and independent half-close. Binary, slow readers, streaming and keep-alive are covered by the test suite.

Separate broker, many launchers

Run portshare serve once, then attach worker groups from other terminals with --attach. Mix runtimes behind one port.

Two modes

Pick the one your app needs.

configurable port

Your app reads PORT

No interception needed. Each worker receives its own PORT, HOST=127.0.0.1 and PORTSHARE_WORKER.

$ portshare run 3000 --workers 4 -- bun server.ts
$ portshare run 3000 --workers 2 -- \
    python3 -m http.server '{port}' --bind 127.0.0.1
--intercept

Your app hardcodes the port

A macOS interposing helper rewrites the app’s listener to a private port. Every worker still believes it’s on 3030.

$ portshare run 3030 --workers 2 --intercept -- \
    bun examples/server.ts
# experimental · launch the runtime directly,
# shell wrappers can strip DYLD_*
Scope

What it is, and what it isn’t.

Portshare is a development tool, not a production proxy. It’s upfront about that.

Does

  • Connection-level round-robin across 1–128 workers
  • Local IPv4 TCP and the tested dual-stack wildcard path
  • Keep-alive, WebSocket and SSE affinity for the connection’s lifetime
  • Startup supervision with specific diagnostics per failure
  • Stops the whole group if any worker exits unexpectedly

Doesn’t

  • Health checks, retries, or graceful draining on shutdown
  • Session/user affinity or HTTP request inspection
  • IPv6-only binds, UDP, or descriptor passing
  • Automatic worker restart
  • Modify security settings or installed executables

Your app × N, on one port.

Build from source in under a minute. Requires macOS, Rust and the Xcode command-line tools.