Lifecycle
heyvm create --name demo --image default # create a sandbox (--name is required)
heyvm create --name demo --backend firecracker --image default
heyvm start <id> # start a stopped sandbox
heyvm stop <id> # stop it
heyvm restart <id>
heyvm get <id> # inspect one
heyvm rm <id> # delete (aliases: delete, destroy)
| Command | Purpose |
|---|---|
create |
Create a sandbox (--name required; --image, --backend — alias --backend-type — …) |
start / stop / restart |
Lifecycle control |
get |
Inspect a single sandbox |
rm |
Delete (aliases delete, destroy) |
fork |
Fork a sandbox into a copy |
list / list-inactive |
List sandboxes |
prune |
Remove stopped sandboxes |
checkpoint / snapshot |
Capture state |
resize |
Change CPU / memory / disk |
edit-ttl |
Change time-to-live |
edit-start-command |
Change the persistent start command |
update |
Replace a deployed sandbox's mount contents from an archive |
wait-for |
Block until a sandbox reaches a state |
backends |
List available backends on this host |
wt |
Create a git-worktree sandbox and open a shell in it |
rm prompts for confirmation and refuses to prompt when stdin is not a TTY —
always pass -y (--yes) from scripts and CI.
Backend support for the duplication commands varies, and is not uniform across platforms:
| Command | Supported on |
|---|---|
snapshot |
apple_virt, and the Linux backends. On apple_container it needs Apple's container commit/container import, which many container builds omit. |
checkpoint / restore |
Linux only (libvirt, firecracker, kvm). Not available on either macOS backend. |
fork |
Linux only, and requires a sandbox created with --project-snapshot. |
edit-start-command |
Saves the new command but does not restart the sandbox — run heyvm restart <id> for it to take effect. |
--project-snapshot is honored only by the bubblewrap backend; on any other
backend it is accepted and ignored. See
macOS Capabilities for the full macOS
matrix.
Exposing ports
--open-port maps a guest port to a host port when the sandbox is created. The
argument is HOST:GUEST, or a single value to use the same port on both sides:
heyvm create --name api --open-port 18734:5000 # host 18734 -> guest 5000
heyvm create --name api --open-port 8080 # host 8080 -> guest 8080
The mapping is fixed at creation time — it cannot be added to a running sandbox.
To expose a port afterwards, use heyvm port-forward on a backend with SSH. Note
that apple_virt ignores --open-port: its guests get a host-routable NAT IP
and are reached directly. See
Networking & Sharing.
Running code
heyvm exec <id> -- <command> # run a command, capture output
heyvm sh <id> # interactive shell
heyvm run-host -- <command> # run on the host
heyvm logs <id> # view logs
# Run an isolated code snippet (experimental, hyperlight-based — no sandbox id)
heyvm run --lang js --code 'console.log(1 + 1)'
heyvm run --lang python --file script.py
| Command | Purpose |
|---|---|
exec |
Execute a command inside a sandbox |
sh |
Open an interactive shell |
run-host |
Run a command on the host machine |
logs |
Show sandbox logs |
run |
Execute an isolated JS/Python snippet with the experimental hyperlight runner (--lang, --code/--file) — not tied to a sandbox |
exec semantics
Everything after -- is the command. exec does not run a shell for you, so
pipes, redirects, and compound commands need an explicit wrapper:
heyvm exec <id> -- ls -la
heyvm exec <id> -- sh -c 'ls -la | wc -l' # pipes need sh -c
heyvm exec <id> --env KEY=value -- printenv KEY
heyvm exec <id> --cwd /srv --user root -- whoami
execdoes not forward stdin. Piping into it (echo … | heyvm exec <id> -- bash -s) sends nothing to the guest. To run a local script remotely, pass it as an environment variable and decode it guest-side:B64=$(base64 < script.sh) heyvm exec <id> --env B64="$B64" -- sh -c 'echo "$B64" | base64 -d | bash'
Background processes survive the call that launched them, so a long-running
service can be started with exec and left running:
heyvm exec <id> -- bash -c 'nohup my-server & disown'
Use heyvm sh <id> when you need a real interactive terminal.
Transferring VMs
heyvm transfer moves a VM to another machine over P2P: disks, config, and
(where the backend supports it) the memory snapshot travel directly to the
destination daemon, and the source sandbox is destroyed once the destination
verifies the restore.
# Move a VM to one of your registered daemons (by name or id),
# or directly to a daemon's heyo:// connection ticket
heyvm transfer demo --to office-mac
heyvm transfer demo --to heyo://…
# Copy instead of move — the source keeps running
heyvm transfer demo --to office-mac --keep-source
# No cloud account? Serve the bundle and pull it from the other machine
heyvm transfer demo --serve # prints a sync ticket
heyvm sync pull <ticket> # run this on the destination
# Skip the memory snapshot — or insist on it
heyvm transfer demo --to office-mac --disk-only
heyvm transfer demo --to office-mac --require-memory
# Rename on arrival and leave it stopped
heyvm transfer demo --to office-mac --name demo-2 --no-start
| Flag | Purpose |
|---|---|
--to <daemon> |
Destination: the name or id of a daemon registered to your account, or a daemon's heyo:// connection ticket |
--serve |
No-cloud fallback: package the VM, serve it over P2P, and print a ticket for heyvm sync pull on the destination |
--keep-source |
Keep the source sandbox running (copy instead of move) |
--disk-only |
Transfer disk + config only; skip the memory snapshot |
--require-memory |
Fail (and roll back the destination) if the memory snapshot can't be restored there, instead of degrading to disk-only |
--name <name> |
Name for the sandbox on the destination (defaults to the source name) |
--no-start |
Don't start the restored VM on the destination |
--timeout-secs <n> |
How long to wait for the destination to pull + restore before resuming the source (default 1800) |
-r, --relay <url> |
Relay URL for short-code ticket registration/resolution |
Memory snapshots travel on the Firecracker backend; KVM and macOS
(Apple Virtualization) transfers carry disk + config only. If the memory
snapshot can't be restored on the destination, the transfer degrades to
disk-only — pass --require-memory to make that a hard failure instead.