Git Worktrees für automatisierte Development-Umgebungen nutzen

Wie ich ein automatisiertes Git Worktree System gebaut hab, das Port-Konflikte für immer beendet

Die Reise eines Entwicklers vom manuellen Umgebungs-Switching-Albtraum zum automatisierten Multi-Branch-Paradies

Der 3-Uhr-Morgens-Panik-Anruf

Stell dir vor: Du bist voll im Flow, baust grad ein komplexes User-Authentication-System für deine Web-App. Drei Terminal-Fenster offen, Docker-Container surren vor sich hin, Datenbank-Migrationen laufen smooth. Dann vibriert dein Handy.

“Hey, das Payment-System ist komplett broken in Production. Kannst du SOFORT schauen?”

Wir kennen das alle. Dieses sinkende Gefühl, wenn dir klar wird, dass du jetzt:

  1. Deine aktuelle Arbeit stoppen musst
  2. Halbfertigen Code stashen/committen musst
  3. Den Branch wechseln musst
  4. Docker-Container neu bauen musst
  5. Mit Port-Konflikten kämpfen musst, weil dein Auth-System Port 3000 nutzt
  6. Möglicherweise deinen Datenbank-State verlierst
  7. Den dringenden Bug fixen musst
  8. Das ganze Zeug rückgängig machen musst, um wieder zu deiner Authentication-Arbeit zurückzukommen

Bis du wieder bei deiner ursprünglichen Aufgabe bist, hast du 30 Minuten verloren und deinen kompletten mentalen Kontext. Es musste doch einen besseren Weg geben.

Der Aha-Moment: Git Worktrees

Ich hatte schon mal von Git Worktrees gehört, aber nie wirklich verstanden, was das Ding kann. Die Dokumentation ließ es klingen wie irgendeine advanced Git-Zauberei für Kernel-Entwickler. Aber als ich das Konzept endlich gecheckt hab, war’s wie die Entdeckung des Feuers:

Git Worktrees lassen dich mehrere Branches gleichzeitig in separaten Verzeichnissen auschecken.

Anstatt Branches in einem Ordner zu wechseln, kannst du haben:

my-project/              # Main branch
my-project-auth/         # Authentication feature branch
my-project-payments/     # Payment system fixes
my-project-integration/  # Integration testing branch

Jedes Verzeichnis ist eine komplette Working Copy, aber alle teilen sich das gleiche Git Repository. Mind = blown.

Das Problem: Es geht nicht nur um Git

Aber hier ist der Haken – nur mehrere Verzeichnisse zu haben löst das echte Problem nicht. Jeder Branch braucht seine eigene:

  • Datenbank-Instanz (kann keine Daten zwischen Features teilen)
  • Port-Allokation (kann nicht mehrere Server auf Port 3000 laufen lassen)
  • Umgebungs-Konfiguration (verschiedene API-Keys, Settings)
  • IDE-Setup (separate Debug-Konfigurationen)

Das alles manuell für jeden Worktree zu managen? Das ist nur ein Albtraum gegen einen anderen eingetauscht.

Die Lösung: Automatisierung, Automatisierung, Automatisierung

Ich hab beschlossen, ein System zu bauen, das:

  1. Automatisch Git Worktrees erstellt mit ordentlichem Branch-Management
  2. Intelligent Ports allokiert um Konflikte zu vermeiden
  3. Isolierte Umgebungen generiert für jeden Worktree
  4. IDE-Konfigurationen automatisch aufsetzt
  5. Docker-Integration nahtlos handled

So hab ich’s gebaut, Schritt für Schritt.

Schritt 1: Die Port-Allokations-Formel

Die erste Challenge waren die Ports. Wenn mein Main Branch Ports 3000-3010 nutzt, welche Ports sollten meine Feature Branches nutzen?

Ich kam auf diese simple Formel:

SERVICE_PORT = BASE_PORT + (WORKTREE_INDEX * 10) + SERVICE_OFFSET

Das bedeutet:

  • Base Port: 40000 (schöne hohe Nummer, unwahrscheinlich dass sie kollidiert)
  • Worktree Index: 0 für main, 1 für ersten Worktree, 2 für zweiten, etc.
  • Service Offset: Unterschiedlich für jeden Service (0=Database, 1=Web, 2=Cache, etc.)

Also für Worktree Index 1:

  • Database: 40000 + (1 × 10) + 0 = 40010
  • Web server: 40000 + (1 × 10) + 1 = 40011
  • Redis: 40000 + (1 × 10) + 2 = 40012

Das gibt jedem Worktree einen vorhersagbaren Block von 10 Ports, eliminiert Konflikte komplett.

Schritt 2: Environment Templates

Nächstes Problem: Jeder Worktree braucht seine eigene Environment-Konfiguration.

Ich hab eine .env.template Datei erstellt:

# .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}

Mein Script verarbeitet dieses Template, ersetzt Variablen mit berechneten Werten:

# Generierte .env für 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

Jetzt kriegt jeder Worktree eine komplett isolierte Umgebung ohne manuelle Konfiguration.

Schritt 3: Docker Integration

Die Docker-Integration war kniffliger. Ich wollte:

  • Base Images und Volumes für Effizienz teilen
  • Datenbank-Daten pro Worktree separat halten
  • Container-Namen-Konflikte vermeiden

Hier ist mein docker-compose Ansatz:

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

Die ${VAR:-default} Syntax bedeutet, dass der Main Branch immer noch mit Default-Ports funktioniert, aber Worktrees ihre berechneten Ports bekommen.

Schritt 4: Das Master-Script

Die ganze Logik kam in ein Bash-Script namens setup-worktree.sh. Hier ist der Workflow:

#!/bin/bash
./setup-worktree.sh feature/user-authentication

Das Script:

  1. Validiert den Branch-Namen und checkt den Git Status
  2. Findet den nächsten verfügbaren Worktree Index durch Scannen existierender Worktrees
  3. Berechnet alle Ports mit der Formel
  4. Erstellt den Git Worktree in ../feature-user-authentication/
  5. Generiert die .env Datei aus dem Template
  6. Setzt Docker Container mit den neuen Ports auf
  7. Konfiguriert IDE Settings (PhpStorm Run Configurations)
  8. Testet, dass alle Services erfolgreich starten

Gesamtzeit: etwa 30 Sekunden. Was früher 10+ Minuten manuelle Arbeit war.

Schritt 5: IDE Integration (Die Geheimzutat)

Hier wird’s richtig geil. Das Script generiert auch PhpStorm Run Configurations:

<!-- Generierte .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>

Wenn ich jetzt PhpStorm im Worktree-Verzeichnis öffne, hab ich ready-to-use Debug-Konfigurationen mit den richtigen Ports. Kein manuelles Setup nötig.

Die Workflow-Revolution

So hat sich mein Workflow geändert:

Alter manueller Prozess:

Neuer automatisierter Prozess:

Vorher:

# Arbeite an Feature A
git stash
git checkout main
git checkout -b hotfix/payment-bug
# Container stoppen, Ports ändern, neu bauen...
# 10 Minuten später, endlich am Bug arbeiten
# Bug fixen, committen, pushen
# Jetzt alles rückgängig machen um zu Feature A zurückzukommen...
# Nochmal 10 Minuten weg

Nachher:

# Arbeite an Feature A, dringender Bug kommt rein
./setup-worktree.sh hotfix/payment-bug
cd ../hotfix-payment-bug
# 30 Sekunden später, voll funktionsfähige Umgebung
# Bug fixen, committen, pushen
cd ../feature-a
# Sofort wieder bei der Arbeit, nichts unterbrochen

Der Unterschied ist wie Tag und Nacht.

Real-World Ergebnisse

Nach 3 Monaten mit diesem System, hier sind die tatsächlichen Benefits, die ich gemessen hab:

Zeitersparnis:

  • Umgebungswechsel: 10 Minuten → 30 Sekunden
  • Kontext-Recovery: 15 Minuten → 0 Minuten (kein Kontext verloren)
  • Bug-Fix-Unterbrechungen: ~25 Minuten → ~2 Minuten

Qualitäts-Verbesserungen:

  • Null Production-Bugs durch Umgebungs-Verwirrung
  • Mehr Bereitschaft, Branches für Experimente zu erstellen
  • Bessere Trennung der Concerns zwischen Features

Team-Produktivität:

  • Keine “mein Branch kollidiert mit deinem” Gespräche mehr
  • Easy verschiedene Branches gleichzeitig zu reviewen
  • Integrationstests zwischen Branches werden trivial

Lessons Learned (auf die harte Tour)

Fehler #1: Cache Permission Hell

Meine erste Version hat Docker Volumes zwischen Worktrees geteilt um Speicherplatz zu sparen. Schlechte Idee. Die Cache-Verzeichnis-Permissions wurden komplett durcheinander gebracht, weil verschiedene Container mit verschiedenen User IDs liefen.

Fix: Jeder Worktree kriegt sein eigenes Cache Volume, aber ich hab optimiert, indem ich den Composer Dependency Cache teile (der ist read-only nach der Installation).

Fehler #2: Port-Formel Edge Cases

Meine initiale Port-Formel hat gelöschte Worktrees nicht gut gehandled. Wenn ich Worktree Index 2 gelöscht hab, würde das System versuchen, diesen Index wiederzuverwenden, aber die Ports könnten noch von hängenden Containern benutzt werden.

Fix: Port-Verfügbarkeits-Check vor der Allokation hinzugefügt:

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
}

Fehler #3: Branch-Namen-Annahmen

Ich bin initial davon ausgegangen, dass Branch-Namen immer simpel sein würden wie feature/auth. Dann hat jemand feature/user-auth-with-2FA-and-social-login erstellt. Der lange Name hat meine Verzeichnis-Benennungs-Logik gesprengt.

Fix: Branch-Namen-Sanitization hinzugefügt:

sanitize_branch_name() {
    echo "$1" | sed 's/[^a-zA-Z0-9-]/-/g' | cut -c1-50
}

Fehler #4: Datenbank-State-Verwirrung

Mit mehreren Datenbanken am Laufen, hab ich manchmal vergessen, welcher Worktree welchen Daten-State hatte. War der Test-User-Account in Worktree 1 oder 2?

Fix: Datenbank-Benennung hinzugefügt, die den Branch inkludiert:

DATABASE_URL=postgresql://user:pass@localhost:40010/myapp_feature_auth

Jetzt kriegt jeder Worktree eine einzigartig benannte Datenbank, und ich kann genau sehen, welche Daten wohin gehören.

Der unerwartete Benefit: Furchtloses Experimentieren

Der größte Benefit war nicht, was ich erwartet hatte. Zero-Cost Branch-Erstellung hat komplett geändert, wie ich entwickle.

Vorher bedeutete einen neuen Branch zu erstellen:

  • “Ist dieses Experiment die Setup-Zeit wert?”
  • “Was wenn ich meine Main-Umgebung vermassele?”
  • “Ich hack das erstmal in meinen aktuellen Branch…”

Nachher:

  • “Lass mich schnell einen Worktree aufspinnen, um diese Idee zu testen”
  • “Ich kann ohne Risiko experimentieren”
  • “Warum nicht einen separaten Branch für jeden Ansatz erstellen?”

Ich erstelle jetzt 3x mehr Branches, aber jeder ist fokussiert und clean. Meine Commit-History ist viel besser, und Code Reviews sind einfacher, weil jeder PR genau eine Sache macht.

Wie du das auch bauen kannst

Willst du was Ähnliches implementieren? Hier ist mein Rat:

Fang simpel an

Bau nicht alles auf einmal. Starte nur mit der Port-Allokations-Formel und manueller Umgebungs-Erstellung. Werd erstmal mit Git Worktrees vertraut.

Wähl deinen Stack

Meine Beispiele nutzen:

  • Docker Compose für Containerisierung
  • PostgreSQL für die Datenbank
  • PHP/Symfony für die App
  • PhpStorm für die IDE

Aber die Konzepte funktionieren mit jedem Stack. Die Port-Formel funktioniert überall, und jede IDE hat irgendeine Form von Run Configuration.

Fokussier dich auf deine Pain Points

Was dauert bei dir am längsten beim Kontext-Wechsel? Für mich waren’s Port-Konflikte und Docker-Rebuilds. Für dich sind’s vielleicht Datenbank-Migrationen oder Frontend-Build-Prozesse.

Mach es reversibel

Bau Cleanup von Tag eins ein. Es ist easy, Worktrees zu erstellen; stell sicher, dass du sie auch sauber zerstören kannst.

Test mit deinem Team

Was für einen Entwickler funktioniert, funktioniert vielleicht nicht für ein Team. Test deine Automatisierung mit Kollegen, bevor du sie ausrollst.

Der Code

Ich hab das Core-Script und die Konfigurations-Templates open-sourced. Die Hauptkomponenten sind:

  • setup-worktree.sh (232 Zeilen Bash)
  • Makefile mit Worktree-Commands
  • .env.template für Environment-Generierung
  • Docker Compose Integration
  • PhpStorm Konfigurations-Generierung

Du findest es im [Projekt-Repository] und kannst es für deinen Stack anpassen.

Was kommt als Nächstes

Dieses System läuft jetzt seit 6 Monaten in Production für unser Team von 8 Entwicklern. Wir arbeiten an:

  • Auto-Cleanup für veraltete Worktrees
  • Resource-Monitoring um Per-Branch-Resource-Usage zu tracken
  • Cloud-Integration für Remote Development Environments
  • Team-Synchronisation um Worktree-Konfigurationen zu teilen

Fazit

Dieses automatisierte Git Worktree System zu bauen war eines dieser seltenen Projekte, wo die Lösung meine Erwartungen übertroffen hat. Ich dachte, ich löse nur Port-Konflikte, aber ich hab am Ende meinen kompletten Development-Workflow transformiert.

Die Key-Erkenntnis ist, dass Development-Umgebungen Infrastruktur sind, und Infrastruktur sollte automatisiert werden. Genau wie wir keine Server mehr manuell provisionieren, sollten wir Development-Umgebungen nicht manuell managen.

Wenn du den Branch-Switching-Tanz satt hast, wenn du jemals Arbeit durch Umgebungs-Konflikte verloren hast, oder wenn du einfach furchtlos experimentieren willst, probier diesen Ansatz aus. Dein zukünftiges Ich wird dir danken.


Hast du was Ähnliches gebaut? Was ist dein Ansatz für das Management mehrerer Development-Umgebungen? Ich würd’s gern in den Kommentaren hören oder meld dich auf [Twitter/LinkedIn].

Weiterführende Literatur:


Ursprünglich veröffentlicht auf [Your Blog]. Wenn das dir geholfen hat, überleg’s mit deinem Team zu teilen!