Resolve development ports dynamically while preserving explicit environment configuration.
This library is framework-agnostic and can be used from config/runtime.exs
in Phoenix (or any Elixir app that needs predictable local port allocation).
WARNING: This library is intended for development servers only.
Do not use it in production deployments.
Typical behavior:
- If a relevant env var is present, explicit values win.
- If no explicit env vars are present, probe from a starting port.
- When the default is busy, return the next available port (or contiguous block).
Summary
Functions
Check if a TCP port is available on localhost.
Resolve a contiguous block of ports.
Resolve a single port.
Types
@type block_result() :: %{ports: [pos_integer()], source: source()}
@type single_result() :: %{port: pos_integer(), source: source()}
@type source() :: :explicit | :default | :fallback
Functions
@spec port_available?( pos_integer(), keyword() ) :: boolean()
Check if a TCP port is available on localhost.
@spec resolve_block( map(), keyword() ) :: block_result()
Resolve a contiguous block of ports.
Options
:env_vars- ordered env var keys for explicit ports:default_port- default base port for the first entry (default:4000):start_port- first base port to probe (defaults to:default_port):block_size- number of contiguous ports to allocate:port_available?- custom availability function for testing:ip- bind address for availability checks (default:{127, 0, 0, 1})
If any :env_vars key is present, allocation is explicit and probing is
skipped. Missing explicit keys use implied defaults based on the first port.
@spec resolve_port( map(), keyword() ) :: single_result()
Resolve a single port.
Options
:env_var- env var key used for explicit value (default:"PORT"):default_port- default preferred port (default:4000):start_port- first port to probe when no explicit env var exists:port_available?- custom availability function for testing:ip- bind address for availability checks (default:{127, 0, 0, 1})
Examples
iex> DevPortAllocator.resolve_port(%{}, default_port: 4000, port_available?: fn p -> p == 4000 end)
%{port: 4000, source: :default}
iex> DevPortAllocator.resolve_port(%{"PORT" => "5050"})
%{port: 5050, source: :explicit}