Every worktree gets its own ports, database, cache, and containers — automatically.
The full wtc workflow: an isolated Docker stack per worktree, several AI agents building in parallel, compared live on their own ports — then promote the winner.
npm install -g worktree-composenpx wtc list
┌───────┬───────────────┬────────┬────────────────────────┬─────────────────────────────────────────────────────────┐
│ Index │ Branch │ Status │ URL │ Ports │
├───────┼───────────────┼────────┼────────────────────────┼─────────────────────────────────────────────────────────┤
│ - │ main │ - │ - │ postgres:5434 redis:6380 backend:8000 frontend:5173 │
├───────┼───────────────┼────────┼────────────────────────┼─────────────────────────────────────────────────────────┤
│ 1 │ feature-auth │ up │ http://localhost:25174 │ postgres:25435 redis:26381 backend:28001 frontend:25174 │
├───────┼───────────────┼────────┼────────────────────────┼─────────────────────────────────────────────────────────┤
│ 2 │ fix-billing │ down │ http://localhost:25175 │ postgres:25436 redis:26382 backend:28002 frontend:25175 │
└───────┴───────────────┴────────┴────────────────────────┴─────────────────────────────────────────────────────────┘
- Why wtc?
- How is this different from
docker compose -p? - Quick Start
- Usage
- Preparing your docker-compose.yml
- How It Works
- Commands
- Configuration (Optional)
- MCP Server
- Agent Skill
- Full Example
- Requirements
- Troubleshooting
- Contributing
- License
Spin up multiple developers or AI agents on the same repo — each in its own git worktree — and they immediately fight over a single Docker Compose setup: port conflicts, a shared database, a shared cache, colliding containers. You can't run two stacks side by side.
wtc reads your existing docker-compose.yml and turns every worktree into a fully isolated, side-by-side environment — unique host ports, separate containers, networks, and volumes — with zero configuration.
- ⚡ Zero config — reads your existing
docker-compose.yml, no new files required - 🔌 Automatic ports — unique, collision-free host ports per worktree
- 📦 Full isolation — separate containers, networks, and volumes for each stack
- 🧪 True side-by-side — run
Nstacks at once and compare them in the browser - 🔄 One-command promote — pull a worktree's changes back into your branch
- 🤖 Built-in MCP server — let AI agents start, stop, and manage their own stacks
Without wtc | With wtc |
|---|---|
git worktree add ../app-feat feat
# hand-pick non-colliding host ports
# edit ../app-feat/.env per service
# copy compose file + Dockerfiles over
docker compose -p app-feat up -d --build
# ...remember to tear it all down later |
git worktree add ../app-feat feat
wtc start
# ...and when you're done:
wtc clean |
docker compose -p only namespaces the project. You'd still hand-pick free host ports, hand-edit each worktree's .env, copy infra files in, and tear everything down yourself. wtc builds on that same project-name isolation and automates the rest: collision-free port allocation, per-worktree .env injection, infra-file sync, one-command promote, and a built-in MCP server so AI agents can drive it all themselves.
# 1. Install
npm install -g worktree-compose
# 2. Create a worktree for some parallel work
git worktree add ../myapp-feature feature-branch
# 3. Boot an isolated stack for it
npx wtc start
# 4. See everything that's running
npx wtc list# Start isolated stacks for all worktrees
npx wtc start
# Start specific worktrees
npx wtc start 1
npx wtc start 1 2 3
# See what's running
npx wtc list
# Stop worktrees
npx wtc stop
npx wtc stop 1
# Restart (re-sync files, rebuild containers)
npx wtc restart 1
# Pull a worktree's changes into your current branch
npx wtc promote 1
# Tear down everything (containers, worktrees, volumes)
npx wtc cleanFor wtc to isolate a service's port, the host port must use the ${VAR:-default} pattern:
# wtc CAN isolate this
ports:
- "${BACKEND_PORT:-8000}:8000"
# wtc CANNOT isolate this (hardcoded)
ports:
- "8080:8080"If wtc finds hardcoded ports, it warns you and suggests the fix:
⚠ Service "nginx" uses a raw port mapping (8080:80).
To enable port isolation, change it to: "${NGINX_PORT:-8080}:80"
# Standard
- "${BACKEND_PORT:-8000}:8000"
# Same var for host and container
- "${FRONTEND_PORT:-5173}:${FRONTEND_PORT:-5173}"
# IP-bound
- "127.0.0.1:${API_PORT:-3000}:3000"
# With protocol
- "${BACKEND_PORT:-8000}:8000/tcp"
# Multiple ports per service
- "${BACKEND_PORT:-8000}:8000"
- "${DEBUG_PORT:-9229}:9229"
# Long-form syntax
- target: 8000
published: "${BACKEND_PORT:-8000}"
protocol: tcpIf your compose file pulls in others via the top-level include: directive, wtc follows them recursively — services defined in included files get the same port isolation as top-level ones, and the included files are copied into each worktree alongside the main one.
include:
- ./db/compose.yml
- path: ./services/api/compose.ymlBuild contexts in an included file resolve relative to that file's own directory, just as Docker Compose does. (Included files that live outside the repo still have their ports isolated, but aren't copied into the worktree.)
Each worktree N gets unique ports: 20000 + default_port + (worktree_index × portStride)
portStride defaults to 1, so by default the table below holds:
| Service | Main (default) | Worktree 1 | Worktree 2 | Worktree 3 |
|---|---|---|---|---|
| postgres | 5434 | 25435 | 25436 | 25437 |
| redis | 6380 | 26381 | 26382 | 26383 |
| backend | 8000 | 28001 | 28002 | 28003 |
| frontend | 5173 | 25174 | 25175 | 25176 |
The table is illustrative — every value follows the formula above. (If an allocation would exceed 65535, wtc falls back to default_port + 100 × index.)
With the default stride of 1, two worktrees collide on a port whenever a pair of services has default host ports whose difference equals the worktrees' index difference. Projects with clustered default ports (e.g. mocks on 8081–8086) therefore collide even between adjacent worktrees, and the second docker compose up fails to bind.
When wtc detects such a collision across your current worktrees it prints a warning naming the colliding ports and the recommended stride. Set portStride in your config (.wtcrc.json or package.json#wtc) to spread each worktree's ports far enough apart:
{ "portStride": 100 }A stride of 100 gives every worktree a 100-port band, which is collision-free unless some pair of default ports differs by an exact multiple of the stride. The recommended value in the warning is the smallest stride that is collision-free over a generous worktree horizon. The canonical docker compose up (no wtc env) is unaffected — only each worktree's .env override block changes.
Each worktree gets its own COMPOSE_PROJECT_NAME — {repo}-wt-{index}-{branch}, lowercased and sanitized to a Docker-safe name. That means separate containers, networks, and volumes. Nothing is shared.
Before starting, wtc copies infrastructure files from main into each worktree: the compose file (plus any files it pulls in via include:), every Dockerfile referenced by a service's build, and your base .env (falling back to .env.example, or an empty file if neither exists). This keeps every worktree on the latest Docker setup.
After copying .env, wtc appends an idempotent block with allocated port overrides:
# existing .env content stays untouched...
# --- wtc port overrides ---
POSTGRES_PORT=25435
REDIS_PORT=26381
BACKEND_PORT=28001
FRONTEND_PORT=25174
# --- end wtc ---Re-running wtc start strips and rewrites this block, so it never accumulates.
Start Docker Compose stacks. Syncs files, injects ports, runs docker compose up -d --build.
npx wtc start # all worktrees
npx wtc start 1 # worktree 1 only
npx wtc start 1 2 3 # worktrees 1, 2, and 3Stop stacks. Runs docker compose down. Volumes are preserved.
npx wtc stop # all
npx wtc stop 1 # worktree 1 onlyFull restart: stop, re-sync files, re-inject env, rebuild, start. Use after migrations, Dockerfile changes, or config updates.
npx wtc restart 1Show all worktrees with branch, status, URL, and ports.
npx wtc listCopy a worktree's changed files — added, modified, and deleted — into your current branch as uncommitted changes. Skips .env and compose files, and aborts if any file it would touch has uncommitted local changes.
npx wtc promote 1Stop all containers, remove all worktrees, prune stale Docker resources.
npx wtc cleanwtc works zero-config. For project-specific needs, create .wtcrc.json in your repo root:
{
"sync": [".generated/prisma-client", "local-certs/"],
"envOverrides": {
"VITE_API_URL": "http://localhost:${BACKEND_PORT}"
},
"portStride": 100
}Or use a "wtc" key in package.json. (.wtcrc.json takes precedence.)
Extra files/directories to copy from main into each worktree on start. Use for gitignored or generated files that Docker needs but aren't committed — like generated clients, local certificates, or build artifacts.
Additional env vars injected into .env. Supports ${VAR} interpolation with allocated port values. Use when env vars depend on allocated ports (e.g. VITE_API_URL).
Port spacing between consecutive worktree indices (default 1). Allocated port = 20000 + default_port + worktree_index × portStride. Raise it (e.g. 100) when your project exposes clustered default ports that would otherwise collide across worktrees — see Port Allocation. wtc warns and recommends a value when it detects a collision.
Built-in MCP server so AI agents can manage their stack programmatically.
Claude Code (.claude/settings.json):
{
"mcpServers": {
"wtc": {
"command": "npx",
"args": ["wtc", "mcp"]
}
}
}Codex:
{
"servers": {
"wtc": {
"command": "npx",
"args": ["wtc", "mcp"]
}
}
}| Tool | Parameters | Description |
|---|---|---|
wtc_start |
indices?: number[] |
Start worktree stacks |
wtc_stop |
indices?: number[] |
Stop worktree stacks |
wtc_restart |
indices?: number[] |
Restart after migrations/config changes |
wtc_list |
none | List worktrees (returns JSON) |
wtc_promote |
index: number |
Pull worktree changes into current branch |
wtc_clean |
none | Tear down everything |
This repo ships a SKILL.md — an agent skill that teaches AI coding agents the wtc worktree workflow (commands, port allocation, config, and the MCP server above). Symlink it into your agent's skills directory so it loads in any project:
mkdir -p ~/.claude/skills/worktree-compose
ln -s "$(pwd)/SKILL.md" ~/.claude/skills/worktree-compose/SKILL.mdcd myapp
pnpm add -g worktree-compose
git branch agent-1-auth
git branch agent-2-auth
git worktree add ../myapp-agent-1 agent-1-auth
git worktree add ../myapp-agent-2 agent-2-auth
npx wtc start
# Worktree 1: backend:28001 frontend:25174
# Worktree 2: backend:28002 frontend:25175
# Compare side by side
# http://localhost:25174 (agent 1)
# http://localhost:25175 (agent 2)
npx wtc promote 1
git add -A && git commit -m "feat: auth from agent 1"
npx wtc clean- Node.js >= 18
- Git with worktree support
- Docker with Compose v2 (
docker compose) docker-compose.ymlwith${VAR:-default}port patterns
"No compose file found" — wtc looks in the git repo root, not the current directory.
"No extra worktrees found" — Create worktrees first: git worktree add ../my-feature my-branch
Ports not changing — Use ${VAR:-default} for host ports, not hardcoded numbers.
Stale containers — Run wtc clean, or manually: docker ps -a --filter "name=-wt-" -q | xargs docker rm -f
Issues and pull requests are welcome. Found a bug or have an idea? Open an issue — and if wtc saves you time, a ⭐ on the repo helps others find it.