How I Built an Automated Git Worktree System That Ended Port Conflict Hell
A developer’s journey from manual environment switching nightmare to automated
multi-branch paradise
- The 3 AM Panic Call
- The Aha Moment: Git Worktrees
- The Problem: It’s Not Just About Git
- The Solution: Automation, Automation, Automation
- Step 1: The Port Allocation Formula
- Step 2: Environment Templates
- Step 3: Docker Integration
- Step 4: The Master Script
- Step 5: IDE Integration (The Secret Sauce)
- The Workflow Revolution
- Real-World Results
- Lessons Learned (The Hard Way)
- The Unexpected Benefit: Fearless Experimentation
- How You Can Build This Too
- The Code
- What’s Next
- Conclusion
The 3 AM Panic Call
Picture this: You’re deep in the zone, building a complex user authentication
system for your web app. Three terminal windows open, Docker containers humming,
database migrations running smoothly. Then your phone buzzes.
“Hey, the payment system is completely broken in production. Can you look at it
RIGHT NOW?”
We’ve all been there. That sinking feeling when you realize you need to:
- Stop your current work
- Stash/commit half-finished code
- Switch branches
- Rebuild Docker containers
- Deal with port conflicts because your auth system is using port 3000
- Potentially lose your database state
- Fix the urgent issue
- Reverse all of the above to get back to your authentication work
By the time you’re back to your original task, you’ve lost 30 minutes and your
entire mental context. There had to be a better way.
The Aha Moment: Git Worktrees
I’d heard about Git worktrees before but never really got them. The
documentation made them sound like some advanced Git wizardry for kernel
developers. But when I finally wrapped my head around the concept, it was like
discovering fire:
Git worktrees let you check out multiple branches simultaneously in separate
directories.
Instead of switching branches in one folder, you can have:
my-project/ # Main branch
my-project-auth/ # Authentication feature branch
my-project-payments/ # Payment system fixes
my-project-integration/ # Integration testing branch
Each directory is a complete working copy, but they all share the same Git
repository. Mind = blown.
The Problem: It’s Not Just About Git
But here’s the catch - just having multiple directories doesn’t solve the real
problem. Each branch needs its own:
- Database instance (can’t share data between features)
- Port allocation (can’t run multiple servers on port 3000)
- Environment configuration (different API keys, settings)
- IDE setup (separate debug configurations)
Manually managing all of this for each worktree? That’s just trading one
nightmare for another.
The Solution: Automation, Automation, Automation
I decided to build a system that would:
- Automatically create Git worktrees with proper branch management
- Intelligently allocate ports to avoid conflicts
- Generate isolated environments for each worktree
- Set up IDE configurations automatically
- Handle Docker integration seamlessly
Here’s how I built it, step by step.
Step 1: The Port Allocation Formula
The first challenge was ports. If my main branch uses ports 3000-3010, what
ports should my feature branches use?
I came up with this simple formula:
SERVICE_PORT = BASE_PORT + (WORKTREE_INDEX * 10) + SERVICE_OFFSET
Here’s what this means:
- Base Port: 40000 (nice high number, unlikely to conflict)
- Worktree Index: 0 for main, 1 for first worktree, 2 for second, etc.
- Service Offset: Different for each service (0=database, 1=web, 2=cache,
etc.)
So for worktree index 1:
- Database: 40000 + (1 × 10) + 0 = 40010
- Web server: 40000 + (1 × 10) + 1 = 40011
- Redis: 40000 + (1 × 10) + 2 = 40012
This gives each worktree a predictable block of 10 ports, completely eliminating
conflicts.
Step 2: Environment Templates
Next problem: each worktree needs its own environment configuration.
I created a .env.template
file:
# .env.template
APP_NAME=MyApp-${WORKTREE_NAME}
DATABASE_URL=postgresql://user:pass@localhost:${POSTGRES_PORT}/myapp
REDIS_URL=redis://localhost:${REDIS_PORT}
WEB_PORT=${WEB_PORT}
WORKTREE_INDEX=${WORKTREE_INDEX}
My script processes this template, replacing variables with calculated values:
# Generated .env for feature-auth worktree
APP_NAME=MyApp-feature-auth
DATABASE_URL=postgresql://user:pass@localhost:40010/myapp
REDIS_URL=redis://localhost:40012
WEB_PORT=40011
WORKTREE_INDEX=1
Now each worktree gets a completely isolated environment with zero manual
configuration.
Step 3: Docker Integration
The Docker integration was trickier. I wanted to:
- Share base images and volumes for efficiency
- Keep database data separate per worktree
- Avoid container name conflicts
Here’s my docker-compose approach:
services:
web:
ports:
- "${WEB_PORT:-3000}:80"
environment:
- WORKTREE_INDEX=${WORKTREE_INDEX:-0}
database:
ports:
- "${POSTGRES_PORT:-5432}:5432"
volumes:
- db-data-wt${WORKTREE_INDEX:-0}:/var/lib/postgresql/data
volumes:
db-data-wt0: # Main branch database
db-data-wt1: # Worktree 1 database
# ... etc
The ${VAR:-default}
syntax means the main branch still works with default
ports, but worktrees get their calculated ports.
Step 4: The Master Script
All of this logic went into a bash script called setup-worktree.sh
. Here’s the
workflow:
#!/bin/bash
./setup-worktree.sh feature/user-authentication
The script:
- Validates the branch name and checks Git status
- Finds the next available worktree index by scanning existing worktrees
- Calculates all the ports using the formula
- Creates the Git worktree in
../feature-user-authentication/
- Generates the
.env
file from the template - Sets up Docker containers with the new ports
- Configures IDE settings (PhpStorm run configurations)
- Tests that all services start successfully
Total time: about 30 seconds. What used to take me 10+ minutes of manual work.
Step 5: IDE Integration (The Secret Sauce)
Here’s where it gets really nice. The script also generates PhpStorm run
configurations:
<!-- Generated .idea/runConfigurations/Debug_WT1.xml -->
<component name="ProjectRunConfigurationManager">
<configuration name="Debug WT1" type="PhpWebAppRunConfigurationType">
<server name="localhost" host="localhost" port="40011" />
<path_mappings>
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
</path_mappings>
</configuration>
</component>
Now when I open PhpStorm in the worktree directory, I have ready-to-use debug
configurations with the correct ports. No manual setup required.
The Workflow Revolution
Here’s how my workflow changed:
Old Manual Process:
New Automated Process:
Before:
# Working on feature A
git stash
git checkout main
git checkout -b hotfix/payment-bug
# Stop containers, change ports, rebuild...
# 10 minutes later, finally working on the bug
# Fix the bug, commit, push
# Now reverse everything to get back to feature A...
# Another 10 minutes gone
After:
# Working on feature A, urgent bug comes in
./setup-worktree.sh hotfix/payment-bug
cd ../hotfix-payment-bug
# 30 seconds later, fully working environment
# Fix the bug, commit, push
cd ../feature-a
# Back to work immediately, nothing disrupted
The difference is night and day.
Real-World Results
After using this system for 3 months, here are the actual benefits I measured:
Time Savings:
- Environment switching: 10 minutes → 30 seconds
- Context recovery: 15 minutes → 0 minutes (no context lost)
- Bug fix interruptions: ~25 minutes → ~2 minutes
Quality Improvements:
- Zero production bugs from environment confusion
- More willing to create branches for experiments
- Better separation of concerns between features
Team Productivity:
- No more “my branch conflicts with yours” conversations
- Easy to review different branches simultaneously
- Integration testing between branches becomes trivial
Lessons Learned (The Hard Way)
Mistake #1: Cache Permission Hell
My first version shared Docker volumes between worktrees to save disk space. Bad
idea. The cache directory permissions got completely messed up because different
containers were running with different user IDs.
Fix: Each worktree gets its own cache volume, but I optimized by sharing the
composer dependency cache (which is read-only after install).
Mistake #2: Port Formula Edge Cases
My initial port formula didn’t handle deleted worktrees well. If I deleted
worktree index 2, the system would try to reuse that index but the ports might
still be in use by lingering containers.
Fix: Added port availability checking before allocating:
check_port_available() {
local port=$1
if lsof -Pi :$port -sTCP:LISTEN -t >/dev/null 2>&1; then
return 1 # Port in use
fi
return 0 # Port available
}
Mistake #3: Branch Name Assumptions
I initially assumed branch names would always be simple like feature/auth
.
Then someone created feature/user-auth-with-2FA-and-social-login
. The long
name broke my directory naming logic.
Fix: Added branch name sanitization:
sanitize_branch_name() {
echo "$1" | sed 's/[^a-zA-Z0-9-]/-/g' | cut -c1-50
}
Mistake #4: Database State Confusion
With multiple databases running, I sometimes forgot which worktree had which
data state. Was the test user account in worktree 1 or 2?
Fix: Added database naming that includes the branch:
DATABASE_URL=postgresql://user:pass@localhost:40010/myapp_feature_auth
Now each worktree gets a uniquely named database, and I can see exactly what
data belongs where.
The Unexpected Benefit: Fearless Experimentation
The biggest benefit wasn’t what I expected. Having zero-cost branch creation
completely changed how I develop.
Before, creating a new branch meant:
- “Is this experiment worth the setup time?”
- “What if I mess up my main environment?”
- “I’ll just hack this into my current branch for now…”
After:
- “Let me spin up a quick worktree to try this idea”
- “I can experiment without any risk”
- “Why not create a separate branch for each approach?”
I’m creating 3x more branches now, but each one is focused and clean. My commit
history is much better, and code reviews are easier because each PR does exactly
one thing.
How You Can Build This Too
Want to implement something similar? Here’s my advice:
Start Simple
Don’t build everything at once. Start with just the port allocation formula and
manual environment creation. Get comfortable with Git worktrees first.
Pick Your Stack
My examples use:
- Docker Compose for containerization
- PostgreSQL for database
- PHP/Symfony for the app
- PhpStorm for IDE
But the concepts work with any stack. The port formula works everywhere, and
every IDE has some form of run configuration.
Focus on Your Pain Points
What takes you the longest when switching contexts? For me it was port conflicts
and Docker rebuilds. For you it might be database migrations or frontend build
processes.
Make It Reversible
Build in cleanup from day one. It’s easy to create worktrees; make sure you can
destroy them cleanly too.
Test with Your Team
What works for one developer might not work for a team. Test your automation
with colleagues before rolling it out.
The Code
I’ve open-sourced the core script and configuration templates. The main
components are:
setup-worktree.sh
(232 lines of bash)Makefile
with worktree commands.env.template
for environment generation- Docker Compose integration
- PhpStorm configuration generation
You can find it at [project repository] and adapt it for your stack.
What’s Next
This system has been running in production for our team of 8 developers for 6
months now. We’re working on:
- Auto-cleanup for stale worktrees
- Resource monitoring to track per-branch resource usage
- Cloud integration for remote development environments
- Team synchronization to share worktree configurations
Conclusion
Building this automated Git worktree system was one of those rare projects where
the solution exceeded my expectations. I thought I was just solving port
conflicts, but I ended up transforming my entire development workflow.
The key insight is that development environments are infrastructure, and
infrastructure should be automated. Just like we don’t manually provision
servers anymore, we shouldn’t manually manage development environments.
If you’re tired of the branch-switching dance, if you’ve ever lost work to
environment conflicts, or if you just want to experiment fearlessly, give this
approach a try. Your future self will thank you.
Have you built something similar? What’s your approach to managing multiple
development environments? I’d love to hear about it in the comments or reach out
on [Twitter/LinkedIn].
Related Reading:
- Git Worktrees Official Documentation
- Docker Compose Environment Variables
- Why You Should Use Git Worktrees
Originally published on [Your Blog]. If this helped you, consider sharing it
with your team!