Announcing:
██████╗ ███████╗██╗ ██╗██████╗ ██████╗ ██████╗ ████████╗███████╗ ██╔══██╗██╔════╝██║ ██║██╔══██╗██╔═══██╗██╔══██╗╚══██╔══╝██╔════╝ ██║ ██║█████╗ ██║ ██║██████╔╝██║ ██║██████╔╝ ██║ ███████╗ ██║ ██║██╔══╝ ╚██╗ ██╔╝██╔═══╝ ██║ ██║██╔══██╗ ██║ ╚════██║ ██████╔╝███████╗ ╚████╔╝ ██║ ╚██████╔╝██║ ██║ ██║ ███████║ ╚═════╝ ╚══════╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ Port allocation manager for multi-project developmentan npm package that automatically manages port allocation and service configuration across development projects and git worktrees. Install it globally with
npm install -g devportsor check it out on NPM and GitHub.
If you're working with multi-service development environments and git worktrees, you've probably run into the same problem I did. You've got your main branch with Postgres on 5432, your API server on 8000, your web app on 3000. You create a worktree for a feature, and suddenly you need different ports for every service to avoid conflicts, whether they're running in Docker containers, as local processes, or in development VMs.
I found myself maintaining a text file to track it all, and remembering to ignore configuration changes in .env files, docker-compose.yml, and various config files when committing work back. I accepted the manual labour on top of every feature I worked on as a cost of doing business. It wasn't ideal, but it also wasn't the end of the world.
Lately, though, I've been a getting a little more frustraed. Now that I'm making use if LLM-based coding assistance, and with the rise of AI-driven spec-driven development tooling like Kiro, Tessl, and Spec-Kit, it's not just me creating these local branches. An LLM is doing it, and its ability to update the configuration files to avoid clashed is about as successful as you'd imagine.
So I started building helper tools.
The Band-Aid Phase
My first attempt was pragmatic: a few scripts that the LLM could call. One that would keep track of used ports and return an unused one, another to search and replace port numbers in my .env files, and a third that would update service configurations—whether that meant Docker container names, database connection strings, or API endpoint URLs. Nothing sophisticated, just enough to unblock the LLM from doing its work autonomously.
It worked. Sort of. But it was a mess of shell scripts that was becoming harder to maintain. The scripts grew. They became more interconnected. I started adding configuration files to define port range blocks and reserved ports that I didn't want to clobber.
Building the Actual Solution
I was done patching around the edges. Building these scripts out over the course of a few different projects was a great way to iteratively work out what the problem actually looked like, and how I could solve it.
It was time to build something that unified all of this.
A single tool that could:
- Track port allocations across worktrees and projects
- Generate unique, deterministic service identifiers
- Render configuration files from templates using those values
- Work programmatically so an LLM agent could call it without intervention
The approach is straightforward. You define your configuration once - which services need ports, what the templates look like, how to name things. Then you create allocations: "this worktree gets these ports, this service name gets this identifier." devports manages the allocations, and when you need to render your configs, it does the substitution.
It's essentially a small system for tracking state and templating configuration. Nothing fancy, but it handles the messy part cleanly.
The Real Problem (With Real Port Numbers)
Let me show you exactly what I mean. Say you're working on an e-commerce platform with your main branch running:
- Postgres database: port 5432
- API server: port 3000
- Frontend app: port 8080
- Redis cache: port 6379
You create a worktree for a payment integration feature. Now you need different ports for that feature branch to avoid conflicts. Without devports, you'd manually pick:
- Postgres database: port 5433 (hope it's not taken)
- API server: port 3001 (hope it's not taken)
- Frontend app: port 8081 (hope it's not taken)
- Redis cache: port 6380 (hope it's not taken)
Then you'd update your .env file, docker-compose.yml, and remember not to commit those changes. Create another worktree? Repeat the whole process, keeping track of what ports you've used across all branches.
With devports, it becomes:
devports allocate payment-feature api --type api
# ✅ Allocated port 3002 for payment-feature/api
devports allocate payment-feature database --type postgres
# ✅ Allocated port 5434 for payment-feature/database
devports allocate payment-feature frontend --type app
# ✅ Allocated port 5002 for payment-feature/frontend
No guessing, no conflicts, no manual tracking. The tool knows what's available and allocates accordingly.
Why This Matters Now
The thing is, git worktrees already solve branch management well. But they expose this gap: how do you manage the local configuration that changes per worktree without manual intervention?
With LLM agents in the mix, that gap becomes a blocker. An agent can create a worktree and start working, but if it can't autonomously configure the ports and service endpoints, it either has to wait for you to intervene or it fails. That's not useful.
devports fills that gap. An agent can call it to allocate ports for a new worktree, render the configuration files for whatever services you're running, and keep working. You're not sitting there fixing things up after every run.
How It Actually Works
The workflow is dead simple. First, see what's currently allocated:
devports list
This shows you a beautiful table of all your current port allocations, grouped by project:
🏗️ my-main-app
┌──────┬──────────────────┬──────────────────┬──────────────────────────┐
│ Port │ Service │ Type │ Allocated │
├──────┼──────────────────┼──────────────────┼──────────────────────────┤
│ 3002 │ api │ api │ 11/19/2025, 04:28:49 PM │
│ 5002 │ frontend │ app │ 11/19/2025, 04:28:55 PM │
│ 5434 │ database │ postgres │ 11/19/2025, 04:28:53 PM │
└──────┴──────────────────┴──────────────────┴──────────────────────────┘
When you need ports for a new feature or worktree:
# Allocate specific service types
devports allocate feature-auth api --type api
devports allocate feature-auth database --type postgres
devports allocate feature-auth cache --type redis
Template-Based Configuration
The real power comes from devports' template system. Create configuration templates with .devports extensions:
# docker-compose.yml.devports (template file that gets rendered)
services:
api:
name: {devports:project}-api
ports:
- "${API_PORT}"
database:
name: {devports:project}-database
ports:
- "${DATABASE_PORT}"
frontend:
name: {devports:project}-frontend
ports:
- "${FRONTEND_PORT}"
# .env.devports (template file that gets rendered)
API_PORT={devports:api:api}
DATABASE_PORT={devports:postgres:database}
FRONTEND_PORT={devports:app:frontend}
Then get started in seconds:
# Set up your project
# - finds all *.devports files, allocates ports, renders configs
devports setup
# Renders both files with actual port numbers and unique project names
# Example output:
# ✅ Rendered docker-compose.yml.devports → docker-compose.yml
# ✅ Rendered .env.devports → .env
For git worktrees, it's even better:
# Creates worktree, generates unique project name, re-renders all templates
devports worktree add ../feature-auth -b feature/auth
# Both templates get new port numbers and unique container names automatically
# No naming conflicts between worktrees!
This approach means you define your configuration templates once, and devports handles the port allocation and rendering across all your worktrees.
When you're done with a worktree, clean up is one command:
devports worktree remove ../feature-auth # Removes git worktree AND releases all ports
From Scripts to Package
Once I had something that worked, I realised it was useful enough to package up properly. I turned it into an npm package so other people dealing with the same problem could just install it and use it without having to maintain their own collection of scripts.
The result is on GitHub and NPM. It's designed to be minimal - just enough to solve the actual problem without being opinionated about how you structure your projects - and will run globally or as a dependency in your project.
The LLM Angle
The scale changes when LLM agents are involved. Manually managing a few worktrees isn't terrible—you create one, set up the ports, work, clean up. But when agents are creating and destroying branches frequently, the manual steps become a bottleneck.
LLM agents handle shell commands and JSON responses well, but they're not good at tracking state between conversations. devports manages the state, so the agent can just call commands.
If you try devports out, I'd love to hear how it works for you. Does it solve your port management problems? Are there features missing that would make it more useful? Hit me up with feedback - it's a small tool but I want to make sure it actually solves the problem well.
Have a burning question or comment? Find me on LinkedIn or Bluesky. I'd love to hear from you.
Top comments (0)