Stable Port Assignments for Any Dev Server (AI Agent-Friendly)
TL;DR — Give your AI coding agent (GitHub Copilot, Claude Code, OpenAI Codex, Gemini CLI) a justfile that launches any local development server — Phoenix, Node.js, Python, Go, Ruby, or anything else that listens on a port — with stable, collision-free port assignments using phx-port. The agent can start, stop, and open your app without guessing ports or stepping on other running projects.
Despite the name,
phx-portis not Phoenix-specific. It works with any project that needs a local port.
This article is self-contained: point your coding agent at it and say "Adopt the pattern in https://cookbook.geuer-pollmann.de/elixir-beam/phx-port-and-justfile-for-ai-agents.md for my project."
Table of Contents
The Problem
When you work on multiple web projects, they tend to default to the same port — 4000 for Phoenix, 3000 for Node/Rails, 8000 for Django, 8080 for Go. You end up either:
Killing the old server before starting the new one
Manually remembering which port you assigned to which project
Passing
PORT=4007and hoping you haven't already used 4007 somewhere else
AI coding agents make this worse. When an agent needs to start your dev server to validate a change, it doesn't know which port to use. If another project is already running on that port, the server fails to bind and the agent wastes time debugging a port conflict instead of doing real work.
What we want: every project gets a stable, unique port — automatically — and the agent has a single command to start the server, open the browser, or stop the process.
The Pattern
Two pieces work together:
phx-port— a small Rust CLI that maintains a TOML registry (~/.config/phx-ports.toml) mapping project directories to port numbers. Each project gets a unique port, allocated once and reused forever. Despite its name, it is framework-agnostic — it works with Phoenix, Express, Django, Rails, Go, or any server that reads aPORTenvironment variable. Port 4000 is kept free for ad-hoc use.A
justfile— ajustcommand runner file in the project root that wires togetherphx-portand your project's start command. For Elixir/BEAM projects, it can additionally integrate distributed Erlang (--sname/--cookie) and the BEAM introspection script (scripts/dev_node.sh).
The agent (or you) runs just start and gets a server on a known, stable port — every time, on every machine.
How phx-port works
phx-port worksphx-port auto-detects behavior based on context:
Piped (e.g. PORT=$(phx-port))
Prints just the port number. Auto-registers the current directory if not yet known.
Interactive (run in a terminal with no arguments)
Shows help text. Never auto-registers accidentally.
phx-port list
Shows all registered projects as a directory tree with clickable URLs.
phx-port open
Opens the default browser at http://localhost:<port> for the current project.
phx-port register
Explicitly registers the current directory for a new port.
phx-port register debug
Registers a named port role (e.g., for a debug port, metrics endpoint, etc.).
Projects can have multiple named port roles:
The registry looks like this:
Step 1: Install phx-port
phx-portOr build from source:
Verify it works:
Step 2: Add the justfile
justfileCreate a justfile in your project root. This is the single entry point for starting, stopping, and managing the server. Below are examples for different stacks.
Generic justfile (Node.js, Python, Go, etc.)
justfile (Node.js, Python, Go, etc.)This works for any server that reads the PORT environment variable:
Replace your-start-command with what your project needs — npm start, python manage.py runserver 0.0.0.0:$PORT, go run ., bundle exec rails server -p $PORT, etc.
Elixir / Phoenix justfile
justfileFor Phoenix or other BEAM projects, the justfile can additionally wire in distributed Erlang for live introspection:
What each recipe does
just start
Starts the server in the foreground. Output goes to the terminal and run.log.
just start-bg
Same, but runs silently in the background. All output goes to run.log.
just open
Opens the app in your browser. For the Elixir version, starts the server in the background first if needed.
just stop
(Elixir) Gracefully shuts down the running BEAM node via System.halt().
just status
(Elixir) Checks whether the BEAM node is registered with epmd.
just rpc '<expr>'
(Elixir) Evaluates an Elixir expression on the running node (requires scripts/dev_node.sh from the BEAM introspection pattern).
Key design decisions
PORTrespects overrides —${PORT:-$(phx-port)}means you can still doPORT=9999 just startif needed, but the default is always the stablephx-portassignment.execreplaces the shell — the server process takes over the shell's PID, so signals (Ctrl+C) go directly to it.(Elixir-specific)
--snameis derived from the directory name — no configuration needed. The project directorymy_appbecomes nodemy_app@hostname.(Elixir-specific)
--cookie devcookie— a shared development cookie sodev_node.shcan connect for introspection.
Step 3: Update .gitignore
.gitignoreAdd runtime artifacts that shouldn't be committed:
Step 4: Add Project Instructions for Your Agent
Tell your AI coding agent about the justfile so it knows how to start and manage the server. Add this to your project's agent instructions file.
GitHub Copilot — AGENTS.md or .github/copilot-instructions.md
AGENTS.md or .github/copilot-instructions.mdFor Elixir/BEAM projects, add
just stop,just status, andjust rpc '<expression>'to the list above — see the Elixirjustfilevariant.
Claude Code — CLAUDE.md
CLAUDE.mdOpenAI Codex / Gemini CLI — AGENTS.md
AGENTS.mdSame content as the Copilot section above. Both tools read AGENTS.md at the project root.
How It All Fits Together
Here's a typical workflow, whether it's you or an AI agent:
An AI agent working on my_app simply runs just start-bg, waits for the server, and can then curl http://localhost:$(phx-port) to validate its changes — without worrying about port conflicts with the api project running in the background.
Elixir / BEAM Bonus: Combining with BEAM Introspection
This pattern is designed to work together with the BEAM Live Introspection pattern. The justfile recipes (stop, status, rpc) delegate to scripts/dev_node.sh, which provides full runtime introspection capabilities.
To set up both patterns together:
Follow this article to add
phx-portand thejustfileFollow the BEAM introspection article to add
scripts/dev_node.shand the introspection skill
The justfile becomes the high-level interface ("start my server"), while dev_node.sh provides the low-level distributed Erlang plumbing ("connect to the running node and evaluate this expression").
Last updated