Git worktrees are powerful. Conductor makes them practical. But the real question is: is your project actually ready for isolated parallel work?
Most projects assume a single running instance: one database, fixed ports, one emulator. Try running three features in parallel and you hit conflicts immediately.
Each workspace gets its own everything. Here's what runs on yarn setup:
# scripts/setup-workspace.sh
# Unique database name from workspace directory
WORKSPACE_NAME=$(basename "$PROJECT_ROOT")
DB_NAME="ws_${WORKSPACE_NAME//[^a-zA-Z0-9]/_}"
# Get workspace-specific ports
source "$SCRIPT_DIR/lib/ports.sh"
get_workspace_ports "$WORKSPACE_NAME"
# Create database
psql -c "CREATE DATABASE \"$DB_NAME\";"
# Generate .env with workspace-specific config
cat > "$BACKEND_DIR/.env" << EOF
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/${DB_NAME}"
PORT=$BACKEND_PORT
EOF
Deterministic ports based on workspace name hash—same workspace always gets same ports:
# scripts/lib/ports.sh
hash_string() {
local str="$1" hash=0
for ((i=0; i<${#str}; i++)); do
char="${str:$i:1}"
ascii=$(printf '%d' "'$char")
hash=$((hash + ascii))
done
echo $((hash % 100))
}
get_workspace_ports() {
local offset=$(hash_string "$1")
BACKEND_PORT=$((3100 + offset))
METRO_PORT=$((8100 + offset))
}
The critical piece for Expo. Global config tracks which workspace owns which simulator:
# scripts/dev-ios.sh
GLOBAL_SIMULATOR_CONFIG="$HOME/.config/conductor/ios-simulators.json"
# Find simulator not used by other workspaces
find_available_simulator() {
local assigned_sims=$(get_assigned_simulators)
for sim in "${AVAILABLE_SIMULATORS[@]}"; do
# Skip if assigned to another workspace
if ! echo "$assigned_sims" | grep -q "$sim"; then
echo "$sim"
return
fi
done
}
# Rename for visual clarity
rename_simulator "$SIMULATOR_UDID" "$SIMULATOR_NAME" "$WORKSPACE_NAME"
# "iPhone 15" becomes "iPhone 15 (feature-branch)"
When archiving a workspace, everything needs to be released:
# scripts/cleanup.sh
# Safety: only drop ws_ prefixed databases
if [[ ! "$DB_NAME" =~ ^ws_ ]]; then
log_error "Refusing to drop database for safety"
exit 1
fi
# Kill processes by port
kill_port $BACKEND_PORT "Backend"
kill_port $METRO_PORT "Metro bundler"
# Shutdown simulator and restore original name
xcrun simctl shutdown "$SIMULATOR_UDID"
xcrun simctl rename "$SIMULATOR_UDID" "$ORIGINAL_NAME"
# Remove from global config (frees simulator for other workspaces)
python3 -c "
import json
with open('$GLOBAL_SIMULATOR_CONFIG', 'r') as f:
data = json.load(f)
del data['$WORKSPACE_NAME']
with open('$GLOBAL_SIMULATOR_CONFIG', 'w') as f:
json.dump(data, f, indent=2)
"
# Drop database
psql -c "DROP DATABASE \"$DB_NAME\";"
In Conductor UI, configure the repo hooks:
| Event | Script |
|---|---|
| On workspace create | yarn setup |
| On workspace archive | yarn cleanup |
Now workspace lifecycle is fully automated. Create a workspace in Conductor—database provisioned, ports allocated, emulator assigned. Archive it—everything cleaned up, resources freed for the next workspace.
{
"scripts": {
"setup": "./scripts/setup-workspace.sh",
"cleanup": "./scripts/cleanup.sh",
"dev": "./scripts/dev.sh",
"dev:ios": "./scripts/dev-ios.sh"
}
}
The snippets above are simplified. For complete, drop-in scripts you can customize for your project:
Includes all helper functions, error handling, and configuration options.
Three features, three workspaces, three emulators running simultaneously. Review changes, create PR, archive workspace. No port conflicts. No shared state. No manual cleanup.
Choosing the right tools isn't enough—you need the right setup.
Want to 10x your development with a setup built for parallel work? .