Isolated Development Environments for Agentic Development

When you run coding agents in parallel, they all share the same machine. Git worktrees give each agent its own code, but not its own services. Things break fast:

  • agent runs docker ps and sees containers from another worktree
  • agent clears a shared Redis cache that another agent was relying on for its test fixtures
  • agent is reading logs hoping to troubleshoot something without realizing that another agent's actions affect the logs
  • agent discovers another agent's worktree and observes its state

The fix is simple: give each worktree its own VM. The agent works entirely inside it — Docker containers, Postgres, dev servers — all invisible to other agents. No shared state, no port conflicts.

OrbStack can create lightweight Linux VMs that mount your Mac's filesystem, making it perfect for this. The agent reads and writes code on the Mac while its services run inside the VM. When you're done, delete the VM.

The workflow is:

  1. Create a Git worktree for the feature or task.
  2. Spin up an OrbStack VM for that worktree.
  3. Load the coding agent into the VM.
  4. Start services inside the VM (shoutout to Tilt – another great tool for local development).
  5. The agent works entirely within the VM.

Here's the setup we use. There's a cloud-config.yaml that provisions the base VM:

#cloud-config
 
packages:
  - curl
  - jq
  - screen
  - unzip
  - direnv
 
runcmd:
  - curl -fsSL https://get.docker.com | sh
  - systemctl enable docker
  - systemctl start docker
  - chmod 666 /var/run/docker.sock

And a start.sh script that creates the VM, provisions it, and drops you into a shell inside it:

#!/bin/bash
set -e
 
MACHINE="${1:?Usage: ./start.sh <machine-name>}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
 
# Create machine
orb create ubuntu "$MACHINE" --user-data "$SCRIPT_DIR/cloud-config.yaml"
 
echo "Waiting for provisioning..."
until orb -m "$MACHINE" sudo cloud-init status 2>/dev/null | grep -q "^status: done"; do
  sleep 1
done
 
# Install Claude Code
orb -m "$MACHINE" bash -c 'curl -fsSL https://claude.ai/install.sh | bash'
 
# Copy Claude config
orb -m "$MACHINE" bash -c '
  SRC="/mnt/mac/Users/$(whoami)"
  [ -f "$SRC/.claude.json" ] && cp "$SRC/.claude.json" ~/.claude.json
  [ -d "$SRC/.claude" ] && mkdir -p ~/.claude && cp -r "$SRC/.claude"/. ~/.claude/
'
 
# You can get the token using `claude setup-token`
TOKEN=$(cat ~/.claude-token)
 
orb -m "$MACHINE" bash -c "
  echo 'export CLAUDE_CODE_OAUTH_TOKEN=\"$TOKEN\"' >> ~/.bashrc
"
 
# Ensure PATH
orb -m "$MACHINE" bash -c '
  grep -q "/.local/bin" ~/.bashrc 2>/dev/null || echo "export PATH=\"\$HOME/.local/bin:\$PATH\"" >> ~/.bashrc
'
 
# Install node, pnpm and dependencies
orb -m "$MACHINE" bash -c '
  curl -fsSL https://fnm.vercel.app/install | bash
  export PATH="/home/$(whoami)/.local/share/fnm:$PATH"
  eval "$(fnm env --shell bash)"
  fnm install
  npm install -g pnpm
  pnpm install
'
 
# Install Tilt
orb -m "$MACHINE" bash -c '
  curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash
'
 
orb -m "$MACHINE" bash -c '
  echo "eval \"\$(direnv hook bash)\"" >> ~/.bashrc
  direnv allow .
'
 
# Start Tilt
orb -m "$MACHINE" bash -c '
  screen -dmS tilt tilt up --stream --host 0.0.0.0
'
 
echo "Tilt dashboard: http://${MACHINE}.orb.local:10350/"
 
orb -m "$MACHINE"

This setup loads Claude Code into the VM. It is not a hard requirement, but it is what allows us to run the agents in complete isolation of everything else.

To spin up the environments:

./start.sh my-worktree-1
./start.sh my-worktree-2

Each machine gets its own hostname (my-worktree-1.orb.local, my-worktree-2.orb.local), its own network, its own Docker daemon, its own Postgres. The Tilt dashboard for each is accessible at its own URL. The agent in my-worktree-1 cannot see anything running in my-worktree-2, because from its perspective, nothing else exists.

You can SSH into the VMs using orb -m <machine-name>. Delete VMs using orb delete <machine-name>.

That's it.

If you haven't used Tilt, it's worth a look. Think of it as Docker Compose with a proper developer experience – live reload, a dashboard, dependency-aware orchestration. We use it to manage all of our local services.

  • Each agent gets a fresh/consistent environment – no surprises, no side-effects
  • You don't have to use complicated/dynamic port assignments
  • Agents can interact with any services within the VM without interfering with you or other agents working in other VMs
  • You can continue to edit files on the host machine using your IDE and git client of choice
  • Clear teardown – when you are done with the work orb delete <machine-name> nukes everything without any leftovers

I just started with this setup, but so far it has been working great.

If you've found anything that works better, please let me know (gajus@gajus.com)!