machine — one isolated VM per project<br>away." />
// never run npm install on your machine again
Run code and devtools in a machine.
Reproducible, sandboxed Lima VMs for the Claude Code / Codex era.<br>No host filesystem mount. No cross-project bleed.<br>One machine per project.
brew install katspaugh/machine/machine
copy
lima · blog
$ machine up blog<br>→ profile python · cypress<br>→ image ubuntu-24.04-arm64<br>→ disk 20G<br>→ cpu 4 · mem 8G<br>✓ ssh ready · machine ssh blog
scroll
// section 01 — architecture
The machine room.
Your shell talks to the CLI. The CLI drives Lima. Lima boots a VM per project.<br>Nothing crosses a line that isn't explicitly drawn.
// host (your mac)<br>// hypervisor<br>// guest vms
host<br>your machine<br>macOS · arm64
$ shell<br>↓ machine cli
~/.ssh/agent<br>forwarded, never copied
control · ssh
hypervisor<br>Lima<br>Apple Virtualization · vz
• no host fs mount<br>• tmpfs secrets
boot · provision
vm · wallet
vm · scraper
vm · blog<br>ubuntu-24.04-arm64
running
— isolation barrier —
agent-forwarded SSH
Your ssh-agent is forwarded into the guest at exec time. The key file never leaves the host.
tmpfs-only secrets
Secrets from 1Password land in $XDG_RUNTIME_DIR/dev-secrets, a tmpfs that's wiped at shutdown.
no host fs mount
Lima's default mountType is disabled. The VM can't read your ~/ or anything else.
// section 02 — why bother
Three things, in order of importance.
01
Isolated by default.
One VM per project. The blast radius of a bad postinstall script is one VM, not your laptop.
machine up blog
02
Reproducible from TOML.
Check projects.toml and provision.toml into the repo. Your teammate runs up, gets the same box.
machine rebuild blog
03
Agent-ready, on day one.
Claude Code and Codex ship in the base image, with the official Claude marketplace and a curated plugin set wired up. Point your editor at the SSH host and let them rip.
machine ssh blog
// section 03 — threat model
Ghost in the machine.
The point of a VM is the boundary. Here's exactly what crosses it and what doesn't.<br>No vibes — the file path on the right is real.
✓ what the VM can reach
→Your project's git repo, cloned at bootgit@github.com:you/blog → ~/code/blog (inside the VM)
→Your forwarded SSH agent socket$SSH_AUTH_SOCK · used, never copied
→1Password env(s) you declared in the repo's .envrc$XDG_RUNTIME_DIR/dev-secrets (tmpfs)
→Outbound network, and a few forwarded localhost ports3000-3010 · 4200 · 5173-5180 · 8080-8099
→Its own filesystem, fullydisposable. `machine rebuild` is cheap.
✕ what the VM cannot reach
×Your dotfiles, shells, history~/.bash_history · ~/.config · ~/.aws
×Anything else in your home directory~/Documents · ~/Desktop · ~/Library
×Other projects' VMs, files, or socketsno shared volumes, no cross-VM ssh
×Your 1Password vault directlyonly items you allow-list
×The host disk, the keychain, Touch IDif it's on macOS, it stays on macOS
// section 04 — profiles
The machine shop.
Profiles stack. Pick a base, layer extras in provision.toml, or write your own from scratch in 30 lines.
cypress
Chrome (or Chromium on arm64), Xvfb, and the lib deps Cypress needs. Node lands from the base image.
chromechromiumxvfbgtk · nss
supabase-fly
The two CLIs you actually use when shipping a backend. Docker comes from the base image.
supabaseflyctl
python
Modern Python tooling on top of system python3: uv for envs and installs, ruff for lint/format, pyright for types.
uvruffpyright
rust
rustup with the stable toolchain, installed system-wide so cargo is on PATH for every user.
rustupcargostable
go
go
A pinned Go toolchain from go.dev, dropped into /usr/local/go with go and gofmt on PATH.
go 1.23gofmt
your own
A profile is a TOML file and a shell script. Copy any of the above, change three lines, commit it.
provision.tomlsetup.sh
// section 05 — demo
What it looks like.
A VHS replay of the CLI surface — list, up, ssh. The first up is the slow one; subsequent boots are warm restarts.
~/code/blog
// section 06 — lifecycle
Tend the machine.
Ten commands, no manifesto. Most days you'll use the first two.
machine up
Boot the VM. First time provisions; after that it's a warm restart.
machine ssh
Open an interactive shell. With your agent forwarded.
machine claude
Skip the shell, jump straight into claude at the primary repo.
machine update
Re-run provisioning on a running box. Tools change; the VM doesn't.
machine rebuild
Tear the VM down to the image and provision from scratch.
machine destroy
Rage-quit a machine. Disk, state, secrets, all gone.
machine doctor
Check Lima, network, agent, and disk space. Tells you what's drifted.
machine ps
List your VMs, their states, and where the SSH ports landed.
machine secrets
Render 1Password Environments into $XDG_RUNTIME_DIR/dev-secrets (tmpfs) for this project.
machine init
First run only. Writes a starter projects.toml to ~/.config/machine/ (or to the repo root in dev mode).
// section 07 — editors
Your editor doesn't...