Multi Agent Worktree Docker Workflow
This workflow keeps the main project checkout stable while feature work happens in reusable Git worktree folders. Each worktree gets its own frontend and backend ports, its own app containers, and its own coding-agent session. MySQL, Redis, and Qdrant stay shared from the main Docker stack.
Operating model
Section titled “Operating model”The main app stays available on normal ports while feature work gets isolated folders, branches, app containers, and ports. Shared infra avoids duplicate database and vector services, and PRs stay tied to normal Git branches.
- ~/projects/your-project is the stable main slot.
- ~/projects/your-project-wt1 is feature slot 1.
- ~/projects/your-project-wt2 is feature slot 2.
- Each slot has a different Git branch checked out.
- Each slot has separate backend and frontend containers.
- The main stack owns shared infra such as MySQL, Redis, and Qdrant.
- Coding agents are launched from the exact worktree folder they should edit.
Create worktree slots
Section titled “Create worktree slots”Create worktrees from the main repo folder. The folder names stay stable, while branch names change per feature.
cd ~/projects/your-project
git worktree add ../your-project-wt1 -b feature/first-taskgit worktree add ../your-project-wt2 -b feature/second-task- Do not check out the same branch in two worktrees at the same time.
- Commit from the worktree that owns the branch.
- Use the main checkout for the stable main branch and normal main stack.
Port layout
Section titled “Port layout”| Slot | Frontend | Backend |
|---|---|---|
| main | 3000 | 8000 |
| wt1 | 3001 | 8001 |
| wt2 | 3002 | 8002 |
- Host ports must be unique across running containers.
- Container ports can stay the same because each container has its own network namespace.
- A mapping like 8001:8000 means the browser uses localhost:8001 while the backend process still listens on port 8000 inside the container.
Keep main stack running
Section titled “Keep main stack running”Start from the main checkout. The main stack is the stable reference app on normal ports, and it owns the shared Docker network and infra containers that wt1 and wt2 reuse.
cd ~/projects/your-projectdocker compose up -ddocker compose ps
docker network ls | grep your-project- Main frontend: http://localhost:3000
- Main backend: http://localhost:8000
- Shared infra services stay in the main stack.
- Feature worktrees should attach to the main network instead of creating duplicate infra containers.
The expected shared network is usually:
your-project_defaultMain Compose reference
Section titled “Main Compose reference”Use the main compose.yaml as the reference layer. It exposes the stable app ports, creates the shared infra services, and gives the worktree slots a network to join.
services: backend: build: context: ./backend env_file: - .env.dev container_name: your-project-backend working_dir: /workspace volumes: - ./backend:/workspace ports: - "8000:8000" stdin_open: true tty: true depends_on: - redis - mysql - qdrant environment: # Add only the environment variables this service needs.
frontend: build: context: ./frontend env_file: - .env.dev container_name: your-project-frontend working_dir: /workspace volumes: - ./frontend:/workspace ports: - "3000:3000" stdin_open: true tty: true
redis: image: redis:7-alpine container_name: your-project-redis
mysql: image: mysql:8 container_name: your-project-mysql env_file: - .env.dev volumes: - your_project_mysql_data:/var/lib/mysql
qdrant: image: qdrant/qdrant container_name: your-project-qdrant volumes: - your_project_qdrant_data:/qdrant/storage
volumes: your_project_mysql_data: your_project_qdrant_data:- Keep real environment keys in .env.dev or in the commented environment block.
- Keep the main app on 3000 and 8000 so it remains the stable reference.
- Let the main Compose project create the default network that worktree Compose files attach to.
- Expose infra ports only when local debugging requires host access.
Create wt1 Compose file
Section titled “Create wt1 Compose file”Create a standalone Compose file in the wt1 worktree. Standalone files keep old port mappings from the main Compose file out of the worktree slot.
cd ~/projects/your-project-wt1nano compose.wt1.yamlservices: backend: build: context: ./backend env_file: - .env.dev container_name: your-project-wt1-backend working_dir: /workspace volumes: - ./backend:/workspace ports: - "8001:8000" stdin_open: true tty: true environment: # Add only the environment variables this slot needs.
frontend: build: context: ./frontend env_file: - .env.dev container_name: your-project-wt1-frontend working_dir: /workspace volumes: - ./frontend:/workspace ports: - "3001:3000" stdin_open: true tty: true
networks: default: external: true name: your-project_default- Keep only the variables your app actually needs in this slot file.
- Use the commented environment block above as the location where real app keys would go.
- Point shared-service values at the main Compose service names used by your project.
- Add any app-specific mounts only when the feature container needs them.
- Use the same container ports as the app expects internally and change only the host-side ports for each slot.
Create wt2 Compose file
Section titled “Create wt2 Compose file”Create the wt2 file with the same structure, changing only the slot names and host ports.
cd ~/projects/your-project-wt2nano compose.wt2.yaml| wt1 value | wt2 value |
|---|---|
| compose.wt1.yaml | compose.wt2.yaml |
| your-project-wt1-backend | your-project-wt2-backend |
| your-project-wt1-frontend | your-project-wt2-frontend |
| 8001:8000 | 8002:8000 |
| 3001:3000 | 3002:3000 |
Run a worktree slot
Section titled “Run a worktree slot”Start only the app containers for the target slot. The backend reaches redis, mysql, and qdrant by service name through the shared your-project_default network.
cd ~/projects/your-project-wt1docker compose -f compose.wt1.yaml up -d --force-recreate backend frontend
docker exec -it your-project-wt1-frontend bashdocker exec -it your-project-wt1-backend bashOpen the wt1 app on the host:
http://localhost:3001http://localhost:8001Run an agent safely
Section titled “Run an agent safely”Launch the coding-agent container from the exact worktree folder. The current directory is mounted to /workspace, so this is the main project boundary.
cd ~/projects/your-project-wt1
docker run -it --rm \ --cap-drop=ALL \ --security-opt no-new-privileges \ --user "$(id -u):$(id -g)" \ -v "$(pwd)":/workspace \ -v ~/.config/claude-box/state:/home/claude/.claude \ -v ~/.config/claude-box/state/.claude.json:/home/claude/.claude.json \ claude-box- Do not launch from ~/projects because that exposes every project under it.
- Claude auth persists in ~/.config/claude-box/state.
- Files written by the agent use the host UID and GID.
- The agent edits the mounted worktree, while app runtime happens in that worktree’s Docker containers.
GitHub CLI setup
Section titled “GitHub CLI setup”Use GitHub CLI for PR creation and status checks. If the repo already uses SSH remotes, choose SSH during gh auth login.
sudo pacman -S github-cligh auth login
gh auth statusgh repo view- Use GitHub.com.
- Use SSH when Git remotes already use SSH.
- Browser login is the simplest auth flow.
- Git credential auth can be skipped when SSH already works for Git.
Commit and open PR
Section titled “Commit and open PR”Commit from the feature worktree. Push HEAD so the current branch is sent to GitHub and upstream tracking is set in one step.
git statusgit add <files>git commit -m "Add feature UI"
git push -u origin HEADgh pr create- Use main as the base branch.
- Use a short title that describes the user-facing change.
- Put summary and testing notes in the PR body.
gh pr view --webgh pr statusgh pr checksMerge and cleanup
Section titled “Merge and cleanup”For this local workflow, squash and merge is the clean default. It puts one feature commit on main while allowing messy feature-branch commits during development.
- Open the PR in GitHub.
- Choose Squash and merge.
- Delete the remote branch when GitHub offers it.
- Pull latest main in the stable checkout.
cd ~/projects/your-projectgit fetch --prunegit pullDo not switch the worktree slot to main if main is already checked out in ~/projects/your-project. Start the next feature branch directly from origin/main instead.
cd ~/projects/your-project-wt1git fetch origingit switch -c feature/next-thing origin/main
git branch -d feature/first-taskgit fetch --pruneDaily sequence
Section titled “Daily sequence”cd ~/projects/your-projectgit pull
cd ~/projects/your-project-wt1git fetch origingit switch -c feature/some-feature origin/maindocker compose -f compose.wt1.yaml up -d backend frontend
docker exec -it your-project-wt1-frontend bashdocker exec -it your-project-wt1-backend bashThen launch the agent from ~/projects/your-project-wt1, edit and validate through the wt1 containers, commit on the feature branch, push HEAD, and create a PR.
Remove old worktrees
Section titled “Remove old worktrees”Remove a disposable worktree through Git when a slot is no longer needed. If a worktree folder was deleted manually, prune stale worktree metadata.
cd ~/projects/your-projectgit worktree remove ../your-project-some-old-worktree
git worktree prune