One ephemeral Colima VM per worktree, so you can build and test multiple worktrees of the same project in parallel — each sealed in its own Linux kernel + Docker daemon.
Perfect for allowing Claude Code to perform end to end on multiple worktrees in parallel.
A docker system prune, an OOM, or a crashed stack in one worktree can never touch another worktree or your host.
The VM is the moat: inside it you run your project's stock commands (./deploy.sh local, make up, docker compose up …) with all defaults — no port-juggling, and every existing test passes unmodified, because inside the box localhost is the stack.
Ships as both a POSIX-sh CLI and a Claude Code plugin (a skill + a /vmoat command), so a coding agent can spin up and test a worktree in isolation on its own. It is project-agnostic: everything specific to a repo lives in a small vmoat.conf.
- macOS (Apple Silicon recommended — fast
vz+virtiofsbackend), Linux, or Windows + WSL2 (Colima runs onqemu+ KVM there). colimaand adockerCLI.- Enough RAM for the stacks you run concurrently (a heavy stack ≈ several GB each).
The VM backend is auto-detected (macOS → vz+virtiofs; Linux/WSL2 → Colima's native qemu+KVM). Override per project via VM_TYPE / MOUNT_TYPE.
- macOS / Linux:
brew install colima docker - Windows (WSL2): install Colima inside your WSL2 distro (Homebrew-on-Linux or your distro's package), and enable nested virtualization in
%UserProfile%\.wslconfig:Run[wsl2] nestedVirtualization=true
vmoatinside the distro, with your worktrees on the Linux filesystem (~/..., not/mnt/c— far faster).
Homebrew (recommended):
brew install voycey/vmoat/vmoatThis installs the vmoat CLI and Colima. (You also need a docker CLI — Docker
Desktop, OrbStack, or brew install docker.)
…or from source
git clone https://github.com/voycey/vmoat ~/.vmoat
ln -s ~/.vmoat/bin/vmoat /usr/local/bin/vmoat # or ~/.local/bin/plugin marketplace add voycey/vmoat
/plugin install vmoat@vmoat
…or, for a quick test without installing: claude --plugin-dir ~/.vmoat. Once enabled, the plugin's bin/ is on $PATH automatically, the skill is model-invoked when you ask Claude to build/test a worktree in isolation, and /vmoat <cmd> is available as a slash command.
With the Claude Code plugin installed you rarely run these by hand. Just ask Claude to verify or test a branch — "verify this worktree end-to-end", "check the UI in the browser" — and the vmoat skill takes over: it spins the worktree up in its own VM, runs the tests inside it, tunnels the app to your host, and drives Chrome DevTools (MCP) against it. Browser testing and independent verification run in the isolated VM by default — in parallel across worktrees, never on your host. The skill writes a vmoat.conf for the project automatically when one is needed.
From any worktree — no config needed for a standard project:
vmoat up # create the VM + start the stack (auto-detected if no config)
vmoat tunnel # forward the stack's published ports to the host (prints each URL)
vmoat test --quick # run CMD_TEST --quick INSIDE the VM (localhost = the stack)
vmoat status # VM status, containers, health, open tunnels
vmoat down # stop the VM (keeps disk)
vmoat destroy # delete the VM entirelyEach worktree gets its own VM automatically (name derived from the worktree dir — see vmoat name). Run up in two worktrees and you have two fully isolated stacks at once.
Running N stacks on a single Docker daemon with different project names/ports works — until one worktree runs docker system prune, down --volumes, or blows up RAM/disk, and every stack dies with it. Separate VMs = separate daemons = no shared blast radius.
The bonus is that each VM is a separate host, not a separate port. Inside every VM the stack owns the default ports, so localhost:38003 is the stack and your tests need zero changes.
Each worktree gets its own VM automatically (the name is derived from the worktree directory — see vmoat name). Run up in two worktrees and you have two fully isolated stacks at once, each addressable as its own host.
- Worktree → VM: the name is derived from the worktree dir; one
colima -p <name>profile each, with its own Docker context (colima-<name>). - Code into the VM: Colima mounts
VM_MOUNTwritable at the same path, so the worktree is visible inside the VM unchanged — no copy/sync. - Deploy + test: run inside the VM via
colima ssh;localhostports resolve to the stack, so stock commands and tests Just Work. - Two ways to use it once it's up:
- You:
vmoat tunnelopens SSH port-forwards to the in-VM ports — point your browser there to see the live stack. - Your agent: the Claude Code plugin lets a coding agent bring a worktree up, run its tests, and drive the tunnelled UI through Chrome DevTools (MCP) — hands-free, in isolation, in parallel with your other worktrees.
- You:
vmoat.conf is optional. For a standard project, vmoat needs no config: it
auto-detects how to start the stack (a docker compose file → docker compose up -d;
a Makefile with an up: target → make up; a package.json dev script → npm run dev)
and auto-discovers the ports to tunnel from the running containers. So vmoat up
then vmoat tunnel Just Work with zero setup.
Add a vmoat.conf (or run vmoat init to scaffold one) only to customize — a
non-standard start command, a test command, provisioning, a health gate, or to pin
specific ports. It is shell-sourced (KEY="value"), trusted project code, and only
CMD_UP is required — which is itself auto-detected for standard layouts.
| Key | Req | Meaning |
|---|---|---|
CMD_UP |
yes | command run inside the VM to bring the stack up (e.g. docker compose up -d) |
CMD_TEST |
no | command run inside the VM for vmoat test (args appended) |
VM_CPU / VM_MEMORY / VM_DISK |
no | VM sizing (RAM is the real ceiling on parallelism) |
VM_MOUNT |
no | host path mounted writable into the VM (must contain the worktree); default $HOME |
PROVISION_APT |
no | extra apt packages the base VM lacks |
PROVISION_SCRIPT |
no | arbitrary shell to install anything apt can't (e.g. dotenvx — only if you use it) |
PROVISION_SEED |
no | gitignored files copied from the main checkout into a fresh worktree |
HEALTH_URL / HEALTH_TIMEOUT |
no | if set, up waits until this URL returns 2xx inside the VM |
EXPOSE_PORTS |
no | guest ports (bash array or space-separated string) tunnel surfaces to the host |
VM_TYPE / MOUNT_TYPE |
no | force a Colima backend (otherwise auto-detected per platform) |
vmoat.conf is committed, so it's shared by every worktree on that branch. When one worktree needs different settings — most often a different EXPOSE_PORTS because it runs different code — drop a vmoat.local.conf in that worktree's directory. It's gitignored, so it exists only in that worktree, and it's sourced after vmoat.conf, so it wins:
# .../worktrees/feature-x/vmoat.local.conf (gitignored — only this worktree)
EXPOSE_PORTS=(30001 38003 9229) # this branch adds a debug port
VM_MEMORY=24 # …and needs more RAMAdd vmoat.local.conf to your repo's .gitignore. Any key (ports, sizing, commands, health) can be overridden per worktree this way — shared defaults in vmoat.conf, per-worktree deltas in vmoat.local.conf.
Example: a RAG stack driven by ./deploy.sh
VM_CPU=6; VM_MEMORY=16; VM_DISK=80
VM_MOUNT="$HOME/github"
PROVISION_APT="git jq curl ca-certificates"
PROVISION_SCRIPT='command -v dotenvx >/dev/null 2>&1 || curl -sfS https://dotenvx.sh | sudo sh'
PROVISION_SEED=".env.keys"
CMD_UP="INSTANCE_NAME=local VERSION=local ./deploy.sh local"
CMD_TEST="INSTANCE_NAME=local VERSION=local ./deploy.sh test"
HEALTH_URL="http://127.0.0.1:38003/health"
EXPOSE_PORTS=(30001 38003)| Command | Does |
|---|---|
init |
Scaffold a vmoat.conf (optional — CMD_UP is auto-detected otherwise) |
up |
provision + run CMD_UP (auto-detected if unset) inside the VM (+ wait for HEALTH_URL if set) |
test [args] |
Run CMD_TEST [args] inside the VM |
tunnel [port…] |
SSH-forward in-VM port(s) to free host ports (default: EXPOSE_PORTS, else auto-discovered) |
provision |
Create/start the worktree's VM, install the toolchain, seed files |
untunnel |
Close all tunnels for this worktree's VM |
status |
VM status, containers, health, open tunnels |
ssh [cmd] |
Shell into the VM (or run a command in the worktree dir) |
down / destroy [-f] |
Stop (keep disk) / delete the VM |
name |
Print the VM/profile name for this worktree |
- First
upper VM is slow (~10–20 min) — it builds the project's images with no cache shared between VMs. Subsequent runs reuse them. - Disk: images are duplicated per VM. Budget
VM_DISKaccordingly. - RAM is the ceiling on how many run at once.
- Backend differs by platform: macOS gets
vz+virtiofs(fast); Linux/WSL2 useqemu+KVM. On WSL2, nested virtualization must be enabled orcolima startcan't boot the VM. - Some stacks abort the very first
upon a fresh DB volume (adepends_on: service_healthycatchinginitdbmid-restart) — just re-runup.
This repo is itself a Claude Code marketplace (.claude-plugin/marketplace.json). To distribute a plugin like it:
- Validate:
claude plugin validate . - Push to GitHub. Users add + install with:
/plugin marketplace add <owner>/<repo> /plugin install <plugin>@<marketplace-name> - For broad discovery, open a PR to the officially-endorsed community marketplace
anthropics/claude-plugins-community(auto-validated); users then/plugin install <plugin>@claude-community.
Versioning: bump version in plugin.json and git-tag releases for pinned updates, or omit version to roll updates on every commit (git SHA).
MIT © Dan Voyce
