Start with one VM

Install skiff, start a Linux VM on your Mac, reach it over SSH and bring its ports to localhost.

Install

skiff needs Apple Silicon, macOS 26 or newer (what Apple's container supports), Homebrew and Go 1.26+. Check a Mac with skiff doctor.

go install github.com/sovyn-eu/skiff/cmd/skiff@latest
skiff prepare

prepare is a one-time setup of about a minute and safe to run again. It installs Apple's container through Homebrew if needed, starts its service, builds the guest image (Ubuntu 24.04 with systemd, sshd, your key and an internet-only firewall) and writes an SSH key pair to ~/.skiff.

Create and connect

skiff new dev
dev is running at 192.168.64.2
  skiff ssh dev
  ssh dev.skiff

new boots a fresh Ubuntu 24.04 in about half a second and returns once it answers SSH, about two seconds after you hit enter. Without a name, VMs are called vm1, vm2 and so on.

skiff ssh dev

You are root in an Ubuntu 24.04 with its own kernel, disk and IP. To use plain ssh, scp, rsync or your IDE, add one line to ~/.ssh/config:

Include ~/.skiff/ssh_config

Then ssh dev.skiff works from anywhere. skiff's ProxyCommand connects to the VM and verifies the host key pinned at creation, so neither port forwarding nor sudo is needed on macOS.

Size

Defaults are 2 vCPUs and 1 GiB RAM, the minimum Apple's container accepts. The disk is a sparse volume that grows as the VM writes, so there is nothing to size there.

skiff new dev --vcpus 4 --mem 2048

The guest is a plain Ubuntu 24.04 with git, curl and jq. Install what a task needs over SSH; the agent guide shows the commands for Node, Claude Code and gh.

Forward ports

Guest ports appear as localhost:PORT on the Mac. Forwards survive stop and start and go away with the VM. Host ports must be 1024 or higher.

skiff new web -p 8080:80 -p 3000
skiff forward web 5432

skiff forward web without ports lists them; skiff unforward web 5432 removes one.

What a VM can reach

The internet, through NAT, and nothing else: a firewall in the guest rejects your Mac, your LAN and other VMs. Root in the VM can lift it, so treat it as a guard rail. The only controlled way back to the Mac is the capability broker.

Command reference

Every command has --help with all flags and examples.

CommandWhat it does
doctorExplain what this Mac is missing.
prepareSet up or update Apple's container runtime and the guest image.
new [NAME] [--vcpus N --mem MiB -p PORT --grant CAP --owner O --slot S]Create and boot a VM.
new … -p [HOST:]GUEST --grant CAPForward ports and grant capabilities right away.
new … --owner NAME --slot NLabel a VM a tool manages, for gc.
ls [--json]Every VM with state, IP, size, ports, grants, owner, age and SSH destination.
inspect NAME [--json]Details of one VM.
ssh NAME [command…]Root shell, or a command line run by the guest shell. Carries the broker.
exec NAME -- cmd [arg…]Run a command with its argv passed verbatim. Carries the broker.
tunnel NAMEHold only the broker forward, for editors and agent runners.
stop NAME / start NAMEShut down keeping the disk, boot again.
rm NAMEStop and delete, including grants and token on the Mac.
gc [--owner O] [--older-than 7d] [--keep VM] [--dry-run]Remove owned VMs a tool lost track of, and leftover host state.
forward NAME [[HOST:]GUEST…] / unforward NAME PORT…List, add or remove port forwards.
grant / revoke NAME CAP…Allow or withdraw capabilities. See Grants.
grants NAMEShow what a VM may use.
cap ls / show / init / lint / addManage capability files. See Reference.
secret set / rm NAMEStore or delete a credential in the macOS Keychain.

Troubleshooting

  • Exit code 255 from ssh or exec means the connection failed; check skiff ls.
  • After a reboot of the Mac, the next skiff command starts the container service and the VMs that were running come back; ones stopped with skiff stop stay stopped. If one fails to boot, skiff start NAME retries it.
  • broken in skiff ls means damaged metadata; skiff rm NAME cleans it up.
  • Boot log of a VM: container machine logs NAME; port forwards: ~/.skiff/relay.log
  • Reset everything: skiff rm each VM, then rm -rf ~/.skiff

Before you leave

skiff stop dev

Stop keeps the disk; rm is a separate step.