// never run npm install on your machine again

One ready-to-work Linux machine per project.

Plenty of tools can boot you an isolated VM. machine boots one your agent can actually work in: Docker, Node, gh, signed git, Claude Code with plugins — provisioned from version-controlled templates, disposable when it gets weird. Your private keys never enter the VM; secrets live in tmpfs. Saying yes to everything is finally a safe answer.

$
brew install katspaugh/machine/machine

requires macOS 13+ · apple silicon or intel · guests are ubuntu 24.04

scroll

// section 01 — the promise

Your Mac is not the dev environment.

Your Mac is the thin client: terminal, browser, SSH agent, password manager, machine. Each project gets a real Linux VM with its own filesystem, tools, services, ports, secrets, and agent sessions. Destroy it when it gets weird. Recreate it when you need it again.

Buy a new laptop, install Homebrew and machine, and your projects come back as machines — not as a pile of global runtimes and forgotten setup steps.

// section 02 — architecture

The machine room.

Your Mac stays small. The CLI drives Lima. Lima boots one Linux VM per project. The VM gets the repo and the tools; the host keeps your keys, dotfiles, browser, and life.

// host (your mac) // hypervisor // guest vms host your machine macOS · arm64 $ shell ↓ machine cli ~/.ssh/agent forwarded, never copied control · ssh hypervisor Lima Apple Virtualization · vz • no host fs mount • tmpfs secrets boot · provision vm · wallet vm · scraper vm · blog 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 — the guest can use the keys while connected, never read them.
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 03 — why bother

Four things, in order of importance.

01

Agent-ready by design.

Claude Code and Codex ship in the base image, wired to the official Claude marketplace and a curated plugin set. They run inside the same Linux environment as the repo. Permission auto-mode is on by default — safe by construction, because there's nothing of yours in there to lose.

machine claude blog
02

Each project gets a real boundary.

One VM per project. No host filesystem mount. No shared project volumes. Your SSH agent is forwarded in for auth and signing — private keys never leave the host, though the VM can use them while you're connected (gate or drop that grant per project: restricting the forwarded agent). The blast radius of a bad dependency or over-eager agent is one disposable machine.

machine up blog
03

Your host stays clean.

No project runtimes, databases, browser deps, global CLIs, package caches, or mystery setup on your Mac. Install machine once; put the mess in project VMs.

brew install katspaugh/machine/machine
04

Reproducible when it matters.

Machines are built from version-controlled Lima templates and provision scripts. Destroy one, rebuild it, or bring the same setup to a new laptop.

machine destroy blog && machine up blog

// section 04 — threat model

Ghost in the machine.

The point of a project machine is the boundary. Your repo, selected secrets, SSH agent, and forwarded ports cross it. Your home directory, dotfiles, keychain, browser profile, and other projects do not.

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 auto-forwarded localhost portsLima forwards listening guest ports to 127.0.0.1
  • Its own filesystem, fullydisposable. machine destroy && machine up 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 05 — why not just containers?

Containers describe the project. Machines protect the host.

Devcontainers are great for declaring tools and services. machine gives them a clean place to run: a disposable Linux VM per project, with no host filesystem mount and no cross-project bleed. Use both — a project VM can run Docker and devcontainers inside Linux, while your Mac only runs machine.

And Claude Code's built-in sandbox? It fences single commands on your host; a VM hands the agent a separate machine with nothing of yours on it. The two compose — the full comparison (built-in sandboxing, devcontainers, Tart, Apple's container) is in Sandboxing Claude Code.

.devcontainer/devcontainer.json
describes the environment — the tools, services, and setup a repo expects.
machine up
decides where it safely lives: a disposable Linux VM, not your Mac.

// section 06 — profiles

The machine shop.

Profiles are the only setup you repeat. Pick a base, layer extras like Playwright, Cypress, Supabase, or Fly, or write your own as a small template + provision script. The result lives in the project machine, not in /opt/homebrew, ~/Library, or some forgotten global npm install.

cypress

Chrome (or Chromium on arm64), Xvfb, and the lib deps Cypress needs. Node lands from the base image.

chromechromiumxvfbgtk · nss
playwright

The OS libs Playwright's browsers need, pre-installed. Each repo's npx playwright install then works without sudo.

install-depschromium · firefox · webkit
supabase-fly

The two CLIs you actually use when shipping a backend. Docker comes from the base image.

supabaseflyctl
+
your own

A profile is a Lima template and a shell script. Copy any of the above, point it at a new script, reference it in projects.toml.

templates/<name>.yamlprovision/<name>.sh

// section 07 — demo

What it looks like.

A VHS replay of the CLI surface — list, up, secrets, ssh, down. The first up is the slow one; subsequent boots are warm restarts.

~/code/blog
Replay of machine list, machine up wallet, machine secrets, machine ssh, and machine down.

// section 08 — lifecycle

Tend the machine.

Twelve commands, no manifesto. Most days: up, ssh in, work, down. Machines are meant to be boring — boot one, run Claude, run tests, and destroy it when the state no longer deserves trust.

machine up <p>
Boot the VM. First time provisions; after that it's a warm restart. New names run the create wizard first.
machine create <p>
Wizard: add a project to projects.toml — repos, profiles, shell — or edit an existing entry.
machine ssh <p>
Open an interactive shell. With your agent forwarded.
machine claude <p>
Jump straight into claude in a tmux session — detach and it keeps working; re-run to reattach.
machine run <p>
Run a one-off command in the VM without opening a shell. stdio passes straight through, so full-screen TUIs work too — machine run wallet hx opens Helix to edit the project.
machine down <p>
Stop the VM. Re-provision in place with machine up — scripts re-run on boot.
machine destroy <p>
Rage-quit a machine. Disk, state, secrets, all gone. machine up to start fresh.
machine bake
Build/refresh the cached base disk in ~/.cache/machine that up reuses.
machine doctor
Preflight: Lima, SSH agent, git identity, signing key, op CLI.
machine list
List your VMs and any configured-but-not-yet-created projects.
machine secrets <p>
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 09 — editors

Your terminal, editor, and agents just see SSH.

Use VS Code Remote-SSH, Cursor, JetBrains Gateway, Emacs TRAMP, or plain ssh. The project machine is a normal SSH host, so your tools don't need a machine plugin.

Visual Studio Code — Remote-SSH: connect to host…
Select configured SSH host or enter user@host
lima-blog lima · ssh.config
lima-wallet lima · ssh.config
lima-scraper lima · ssh.config
+ Add new SSH host…

Add Include ~/.lima/*/ssh.config to ~/.ssh/config once; Lima maintains the per-VM configs. Cursor and JetBrains Gateway pick them up too. Prefer a terminal editor? machine run wallet hx runs Helix (or vim / nvim) right inside the machine — no plugin, nothing editor-shaped on the host.

Boot a machine.

Install from the Homebrew tap, run machine init, then machine up.

$
brew install katspaugh/machine/machine

Requires macOS 13 or newer, Apple Silicon or Intel — the VM layer is Apple's Virtualization framework, so Linux and Windows hosts aren't supported. Prefer to clone? git clone the repo — same code, dev mode. Read the formula or verify the release before installing.