April 7, 2026

Parallel Dev Servers in a Monorepo with Git Worktrees

I run multiple coding agents in parallel, each in its own git worktree with its own dev server. When your apps reference each other by port, you can't just let the framework pick whatever's available.

The Problem

A typical monorepo has apps that reference each other:

Spin up a second worktree and frameworks like Next.js will auto-pick a new port. But your web app doesn't know the API moved to :4001. It's still pointing to :4000 for API calls and internal URLs. Everything breaks silently.

The Fix

Each worktree gets a base port. All app ports derive from it.

Create worktrees to see port assignment
No worktrees

Here's how it works, step by step.

1. Pick a Base Port

Set up a worktree and pass in a base port:

# create a worktree
git worktree add ../my-feature feature-branch
cd ../my-feature

# install deps
bun install

# assign ports
BASE_PORT=5200 bash scripts/setup-dev-ports.sh

setup-dev-ports.sh derives each port from the base:

#!/bin/bash

if [ -n "$BASE_PORT" ]; then
  WEB_PORT=$((BASE_PORT))       # base + 0
  API_PORT=$((BASE_PORT + 1))   # base + 1
  ADMIN_PORT=$((BASE_PORT + 2)) # base + 2
else
  WEB_PORT=3000
  API_PORT=4000
  ADMIN_PORT=6060
fi

BASE_PORT=5200 gives you 5200, 5201, 5202. Second worktree, pick a different number:

git worktree add ../my-other-feature other-branch
cd ../my-other-feature
bun install
BASE_PORT=5210 bash scripts/setup-dev-ports.sh
turbo dev --parallel

Two isolated dev environments, side by side.

2. Write .env Files

The script writes .env.development.local files (highest priority in Next.js dev mode, gitignored by default):

cat > apps/web/.env.development.local <<EOF
WEB_PORT=$WEB_PORT
NEXT_PUBLIC_API_HOST=localhost:$API_PORT
EOF

cat > apps/api/.env.development.local <<EOF
API_PORT=$API_PORT
NEXT_PUBLIC_WEB_DOMAIN=localhost:$WEB_PORT
EOF

This handles both the listening port and the sibling URLs. Every app in the worktree now points to its siblings' correct ports.

3. Read Ports at Dev Time

Each app's dev script sources the env file:

{
  "scripts": {
    "dev": "sh -c '[ -f .env.development.local ] && . ./.env.development.local; next dev --port ${WEB_PORT:-3000}'"
  }
}

The :-3000 fallback means bun dev still works without the setup script.

Automating It with Conductor

I use Conductor to run a bunch of coding agents in parallel. I don't want to think about port numbers every time I spin one up.

A conductor.json at the repo root handles it:

{
  "scripts": {
    "setup": "bun install && bash scripts/setup-dev-ports.sh",
    "run": "turbo dev --parallel"
  }
}
  • setup runs once on workspace init. Installs deps and configures ports.
  • run starts the dev servers.

Conductor exposes a CONDUCTOR_PORT to each workspace, a base port from a reserved block of 10. So the setup script becomes:

#!/bin/bash

if [ -n "$CONDUCTOR_PORT" ]; then
  WEB_PORT=$((CONDUCTOR_PORT))       # base + 0
  API_PORT=$((CONDUCTOR_PORT + 1))   # base + 1
  ADMIN_PORT=$((CONDUCTOR_PORT + 2)) # base + 2
else
  WEB_PORT=3000
  API_PORT=4000
  ADMIN_PORT=6060
fi

cat > apps/web/.env.development.local <<EOF
WEB_PORT=$WEB_PORT
NEXT_PUBLIC_API_HOST=localhost:$API_PORT
EOF

cat > apps/api/.env.development.local <<EOF
API_PORT=$API_PORT
NEXT_PUBLIC_WEB_DOMAIN=localhost:$WEB_PORT
EOF

No manual port picking. Conductor guarantees non-overlapping ranges.

Now I can spin up multiple workspaces and test them in parallel. localhost:5200 shows one branch, localhost:5210 shows another, and each one talks to its own API.

Why Not Spotlight Testing?

Conductor also has Spotlight Testing, which syncs worktree changes back to your repo root so you can reuse your existing dev server.

It works great, but Spotlight allows you to run one feature at a time. With multiple agents going, I needed each workspace fully isolated with its own servers.