Bind 2 Port 0
Port conflicts for local environments are largely a convention failure. The OS has a port allocator. Bind to port 0, ask the server which port it was allocated, and the majority of EADDRINUSE errors disappear.
Default static ports are (now) a silly convention. Worktrees and agents need parallel local environments that don't collide. Port conflicts (mostly) disappear if you stop defaulting to a hardcoded value.
The fix
- Default your services on port
0. - Query each bound socket for its assigned port.
- Pass those addresses to anything that depends on them.
Orchestrate with real addresses instead of configs. If you can't for whatever reason, you can wrap it with a utility. [1]
Bind to nothing static
Broadly speaking, the same principle extends to database names, temp directories, lockfiles, and socket paths. Anything where two environments might collide. If a resource identifier is hardcoded, it's a serialization point. Let the system assign it, then publish the result.
[1]: Here's a lil b2p0 utility for illustration:
require "socket"
def with_ports(count)
servers = count.times.map { TCPServer.new("127.0.0.1", 0) }
yield servers.map { |s| s.addr[1] }
ensure
servers&.each(&:close)
end
with_ports(3) do |redis_port, api_port, web_port|
# Three services, zero collisions, zero configuration
end
An atomically correct version would utilize file-descriptors, require framework coordination, a longer blog, and would likely be more interesting. But, this snippet is good enough for local environments.