App io.pilot.smolvm · delivered from the Pilot R2 artifact registry · generated by scripts/ab_report.py
| Command | Vanilla (ms) | Pilot (ms) | Δ overhead | Match |
|---|---|---|---|---|
| Version | 60 | 833 | +773 | ✓ |
| List machines | 42 | 456 | +414 | ✓ |
| Run command in an ephemeral Alpine microVM | 5854 | 5492 | -362 | ✓ |
| Compute in a Python microVM | 9374 | 9501 | +127 | ✓ |
{
"app": "io.pilot.smolvm",
"version": "1.2.0",
"description": "Run portable, lightweight microVMs (smol machines) from the app store.",
"duration_classes": {
"fast": "<~1s \u2014 status or cheap call",
"med": "~1-5s \u2014 moderate work",
"slow": "~5-30s \u2014 heavy / multi-step"
},
"methods": [
{
"method": "smolvm.version",
"kind": "utility",
"summary": "Print the smolvm version.",
"duration": "fast"
},
{
"method": "smolvm.exec",
"kind": "utility",
"summary": "Run any smolvm subcommand; payload {\"args\":[...]}.",
"params": {
"args": "array \u2014 verbatim argv forwarded to smolvm"
},
"duration": "slow"
},
{
"method": "smolvm.help",
"kind": "meta",
"summary": "This document \u2014 every method with params, kind, and duration class.",
"duration": "fast",
"typical_roundtrip": "instant (local, no backend call)"
}
]
}# smolvm — Agent Reference
A tool to build and run portable, self-contained virtual machines locally. <200ms boot time. No daemon, no Docker.
## Quick Reference
```bash
# Ephemeral (cleaned up after exit)
smolvm machine run --net --image alpine -- echo hello
smolvm machine run --net -it --image alpine -- /bin/sh # interactive shell
smolvm machine run --net --image python:3.12-alpine -- python3 script.py
# Persistent (survives across exec sessions and stop/start)
smolvm machine create --net --name myvm
smolvm machine start --name myvm
smolvm machine exec --name myvm -- apk add python3 # installs persist
smolvm machine exec --name myvm -- which python3 # still there
smolvm machine shell --name myvm # interactive shell (auto-starts if stopped)
smolvm machine stop --name myvm
smolvm machine delete --name myvm
# Image-based persistent (filesystem changes persist across exec sessions)
smolvm machine create --net --image ubuntu --name myvm
smolvm machine start --name myvm
smolvm machine exec --name myvm -- apt-get update
smolvm machine exec --name myvm -- apt-get install -y python3
smolvm machine exec --name myvm -- which python3 # still there after exit+re-exec
# SSH agent forwarding (git/ssh without exposing keys)
smolvm machine run --ssh-agent --net --image alpine -- ssh-add -l
smolvm machine create --name myvm --ssh-agent --net
# Inject secrets into workload env (referenced from host env var / file)
smolvm machine run --secret-env OPENAI_API_KEY=OPENAI_API_KEY -- ./app
smolvm machine run -s Smolfile -- ./app # Smolfile [secrets] resolves at launch
# Pack into portable executable
smolvm pack create --image python:3.12-alpine -o ./my-python
./my-python run -- python3 -c "print('hello')"
# Create machine from packed artifact (fast start, no pull)
smolvm machine create --name my-vm --from ./my-python.smolmachine
smolvm machine start --name my-vm
smolvm machine exec --name my-vm -- pip install requests
# Use local container images (CI, air-gapped, fast iteration)
docker save myapp:latest -o myapp.tar
smolvm machine run --image ./myapp.tar -- ./app # from a docker/podman save archive
docker save myapp:latest | smolvm machine run --image - -- ./app # from stdin
smolvm machine run --image ./rootfs/ -- ./app # from an unpacked rootfs dir
smolvm machine create --name myvm --image ./myapp.tar # persistent, from a local archive
```
## When to Use What
| Goal | Command |
|------|---------|
| Run a one-off command in isolation | `smolvm machine run --net --image IMAGE -- CMD` |
| Interactive shell (ephemeral) | `smolvm machine run --net -it --image IMAGE -- /bin/sh` |
| Interactive shell (persistent) | `smolvm machine shell --name NAME` |
| Persistent dev environment | `machine create` → `machine start` → `machine exec` |
| Ship software as a binary | `smolvm pack create --image IMAGE -o OUTPUT` |
| Fast persistent machine from packed artifact | `machine create --name NAME --from FILE.smolmachine` |
| Use local container images (CI / air-gapped / fast iteration) | `--image ./archive.tar`, `--image -` (stdin), or `--image ./rootfs/` |
| Use git/ssh with private keys safely | Add `--ssh-agent` to run or create |
| Inject API keys / tokens without putting them on the command line | `--secret-env`/`--secret-file` flags or Smolfile `[secrets]` |
| Minimal VM without image | `smolvm machine run -s Smolfile` (bare VM) |
| Change mounts/ports/resources on existing VM | `machine update --name NAME -v ./src:/app -p 8080:8080` |
| Declarative VM config | Create a Smolfile, use `--smolfile`/`-s` flag |
### Persistence Model
- **`machine run`** — ephemeral. All changes are discarded when the command exits.
- **`machine exec`** — persistent. Filesystem changes (package installs, config edits) persist across exec sessions for the same machine, whether bare or image-based. Changes are stored in an overlay on the machine's storage disk.
- **`machine stop` + `start`** — changes persist across restarts. The persistent overlay is remounted preserving previous changes.
- **`pack run`** — ephemeral. Each run starts fresh from the packed image.
- **`pack start` + `exec`** — daemon mode. `/workspace` persists across exec sessions and stop/start. Container overlay resets per exec (package installs don't persist — use `/workspace` for durable data).
- **`machine create --from .smolmachine`** — creates a persistent named machine from a packed artifact. Boots from pre-extracted layers (~250ms, no image pull). Full `machine exec` persistence — package installs, file writes all survive across exec and stop/start.
## CLI Structure
All commands use named flags (no positional args except `machine create --name NAME` and `machine delete --name NAME`).
```
smolvm machine run --image IMAGE [-- COMMAND] # ephemeral
smolvm machine exec --name NAME [-- COMMAND] # run in existing VM
smolvm machine shell [--name NAME] # interactive shell (auto-starts)
smolvm machine create --name NAME [OPTIONS] # create persistent
smolvm machine create --name NAME --from FILE.smolmachine # from packed artifact
smolvm machine start [--name NAME] # start (default: "default")
smolvm machine stop [--name NAME] # stop
smolvm machine delete --name NAME [-f] # delete
smolvm machine status [--name NAME] # check state
smolvm machine ls [--json] # list all
smolvm machine update --name NAME [OPTIONS] # modify stopped machine settings
smolvm machine cp SRC DST # copy files (host↔VM)
smolvm machine exec --stream --name NAME -- CMD # streaming output
smolvm machine monitor [--name NAME] # foreground health + restart
smolvm pack create --image IMAGE -o PATH # package
smolvm pack create --from-vm NAME -o PATH # pack from VM snapshot
smolvm pack run [--sidecar PATH] [-- CMD] # run .smolmachine
smolvm serve start [--listen ADDR:PORT|PATH] # HTTP API
smolvm config registries edit # registry auth
# Secrets are references to host env vars / files, resolved at launch — no
# built-in store. Attach them on the command line or in a Smolfile [secrets].
smolvm machine run --secret-env GUEST_VAR=HOST_VAR # from host env var
smolvm machine run --secret-file GUEST_VAR=/abs/path # from host file
smolvm machine create --name NAME --secret-env GUEST_VAR=HOST_VAR # persists the ref
smolvm machine exec --name NAME --secret-env GUEST_VAR=HOST_VAR -- cmd
```
## Artifact References
Artifact references follow OCI conventions and support both tags and digests:
```
python-dev # bare name (default registry + latest)
python-dev:v1.0 # name + tag
binsquare/custom:v1 # namespace + name + tag
smolmachines.com/python-dev:latest # registry + name + tag
smolmachines.com/binsquare/custom:v1 # registry + namespace + name + tag
python-dev@sha256:abcdef0123... # digest reference (immutable)
```
Default registry: `registry.smolmachines.com`. Digest references require `sha256:` followed by exactly 64 hex characters.
### Local container images
`--image` also accepts a local source — useful for CI, air-gapped hosts, and fast
local iteration. smolvm stays a microVM runtime and delegates all image work
(flatten, whiteouts, config) to container tooling (`crane`/`docker`/`podman`); the
archive is flattened with `crane export`.
```
./image.tar ./image.tar.gz ./image.tgz # a `docker save` / `podman save` archive (gzip ok)
- # the same archive streamed on stdin
./rootfs/ # an already-unpacked root filesystem directory
```
A source is treated as local when it starts with `/`, `./`, `../`, is `-`, or ends in
`.tar`/`.tar.gz`/`.tgz`; everything else is a registry reference (so bare `alpine`
still pulls). Archives are cached content-addressed by hash and re-resolved on
`machine start`. `--image -` cannot be combined with `-i`/`-t` (both read stdin).
smolvm boots images, it does not build them: a Dockerfile passed to `--image` is
rejected with a hint to build first (`docker build … && docker save … | … --image -`).
## Key Flags
| Flag | Short | Used on | Description |
|------|-------|---------|-------------|
| `--image` | `-I` | run, create, pack create | OCI image, or a local source: a `docker save` archive (`./img.tar`, or `-` for stdin) or unpacked rootfs dir (`./rootfs/`) |
| `--name` | `-n` | run, start, stop, status, exec, update | Machine name (default: "default") |
| `--net` | | run, create | Enable outbound networking (off by default) |
| `--gpu` | | run, create | Enable GPU acceleration (Vulkan via virtio-gpu) |
| `--gpu-vram` | | run, create | GPU shared-memory region size in MiB (default: 4096). Ignored without `--gpu`. |
| `--volume` | `-v` | run, create, update | Mount host dir: `HOST:GUEST[:ro]` |
| `--port` | `-p` | run, create, update | Port mapping: `HOST:GUEST` |
| `--smolfile` | `-s` | run, create, pack create | Load config from Smolfile |
| `--interactive` | `-i` | run, exec | Keep stdin open |
| `--tty` | `-t` | run, exec | Allocate pseudo-TTY |
| `--allow-cidr` | | run, create | CIDR egress filter (implies --net) |
| `--allow-host` | | run, create | Hostname egress filter, resolved at VM start (implies --net) |
| `--ssh-agent` | | run, create | Forward host SSH agent (git/ssh without exposing keys) |
## Smolfile Reference
A Smolfile is a TOML file declaring a VM workload. Use with `--smolfile`/`-s`.
```toml
# Top-level: workload definition
image = "python:3.12-alpine" # OCI image (omit for bare Alpine)
entrypoint = ["/app/run"] # overrides image ENTRYPOINT
cmd = ["serve"] # overrides image CMD
env = ["PORT=8080", "DEBUG=1"] # environment variables
workdir = "/app" # working directory
# Resources
cpus = 2 # vCPUs (default: 4)
memory = 1024 # MiB (default: 8192, elastic via balloon)
net = true # outbound networking (default: false)
gpu = true # GPU acceleration (default: false)
gpu_vram = 4096 # GPU VRAM MiB (default: 4096, ignored unless gpu=true)
storage = 40 # storage disk GiB (default: 20)
overlay = 4 # overlay disk GiB (default: 2)
# Network policy — egress filtering by hostname and/or CIDR
[network]
allow_hosts = ["api.stripe.com"] # resolved at VM start (implies net)
allow_cidrs = ["10.0.0.0/8"] # IP/CIDR ranges (implies net)
# Dev profile (used by `machine run` and `machine create`)
[dev]
volumes = ["./src:/app"] # host bind mounts
ports = ["8080:8080"] # port forwarding
init = ["pip install -r requirements.txt"] # run on every VM start
env = ["APP_MODE=dev"] # dev-only env (extends top-level)
workdir = "/app" # dev-only workdir
# Artifact profile (used by `pack create`)
[artifact]
cpus = 4 # override resources for distribution
memory = 2048
entrypoint = ["/app/run"] # override entrypoint for packed binary
oci_platform = "linux/amd64" # target OCI platform
# Health check (used by `machine monitor`)
[health]
exec = ["curl", "-f", "http://127.0.0.1:8080/health"]
interval = "10s"
timeout = "2s"
retries = 3
startup_grace = "20s"
# Credential forwarding
[auth]
ssh_agent = true # forward host SSH agent into the VM
# Secrets — references to host sources, resolved at workload launch
[secrets]
DATABASE_URL = { from_env = "PROD_DB_URL" } # host env var (at launch)
GCP_CREDS = { from_file = "/abs/creds.json" } # host file (at launch)
```
### Merge Precedence
CLI flags override Smolfile values:
```
image: --image flag > Smolfile image > None (bare Alpine)
entrypoint: Smolfile entrypoint > image metadata
cmd: trailing args (after --) > Smolfile cmd > image metadata
env: top-level env + [dev].env + CLI -e (all merged)
volumes: [dev].volumes + CLI -v (all merged)
ports: [dev].ports + CLI -p (all merged)
init: [dev].init + CLI --init (all merged)
cpus/mem: CLI flag > Smolfile > defaults (4 CPU, 8192 MiB)
```
## Networking
- **Off by default** — VMs have no outbound access unless `--net` is specified
- `--net` enables full outbound (TCP/UDP, DNS)
- `--allow-host api.stripe.com` enables egress only to resolved IPs of that hostname (implies `--net`). Also enables DNS filtering — only allowed hostnames can be resolved.
- `--allow-cidr 10.0.0.0/8` enables egress only to specified IP ranges (implies `--net`)
- `--allow-host` and `--allow-cidr` can be combined and used multiple times
- `--outbound-localhost-only` restricts to 127.0.0.0/8 and ::1 (implies `--net`)
- `-p HOST:GUEST` forwards a host port to the VM (TCP)
- Smolfile: use `[network] allow_hosts` and `[network] allow_cidrs`
### Proxy Support
Pass proxy settings into VMs with `-e` when behind a corporate proxy or VPN:
```bash
smolvm machine run --net \
-e https_proxy=http://proxy.corp:3128 \
-e http_proxy=http://proxy.corp:3128 \
-e no_proxy=localhost,127.0.0.1 \
--image alpine -- wget -q -O /dev/null https://example.com
```
Or declare them in a Smolfile:
```toml
net = true
env = [
"https_proxy=http://proxy.corp:3128",
"http_proxy=http://proxy.corp:3128",
"no_proxy=localhost,127.0.0.1"
]
```
Proxy vars are NOT forwarded automatically — each VM gets exactly the env you specify. The VM uses the host's DNS server (from `/etc/resolv.conf`) for name resolution.
## SSH Agent Forwarding
Forward the host's SSH agent into the VM so git, ssh, and scp work with your keys — without the private keys ever entering the VM.
```bash
# CLI flag
smolvm machine run --ssh-agent --net --image alpine -- ssh-add -l
smolvm machine create --name myvm --ssh-agent --net
# Smolfile
# [auth]
# ssh_agent = true
```
Inside the VM, `SSH_AUTH_SOCK` is set automatically. Any tool that uses the SSH agent protocol (git, ssh, scp) works transparently:
```bash
smolvm machine exec --name myvm -- git clone git@github.com:org/private-repo.git
smolvm machine exec --name myvm -- ssh deploy@server "systemctl restart app"
```
The host SSH agent signs challenges but never sends private keys across the boundary. Even with root inside the VM, keys cannot be extracted — this is enforced by the SSH agent protocol and the hypervisor isolation.
Requires `SSH_AUTH_SOCK` to be set on the host. If missing, smolvm exits with an error and remediation instructions.
## GPU Acceleration
Enable the host GPU inside a VM with `--gpu`. Guest Vulkan talks to the host GPU via virtio-gpu/Venus; ANGLE uses it as the WebGL/OpenGL ES backend.
**Host setup:**
- macOS — bundled, no extra installs needed.
- Linux — install virglrenderer from the system package manager before use:
- Alpine: `apk add virglrenderer mesa-vulkan-intel` (or `mesa-vulkan-ati` for AMD)
- Debian/Ubuntu: `apt install virglrenderer0 mesa-vulkan-drivers`
```bash
# One-shot GPU workload
smolvm machine run --gpu --image alpine -- sh -c '
apk add --no-cache mesa-vulkan-virtio vulkan-tools
VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/virtio_icd.x86_64.json \
vulkaninfo --summary 2>/dev/null | grep deviceName
'
# → deviceName = Virtio-GPU Venus (Intel(R) UHD Graphics ...)
# Persistent GPU machine
smolvm machine create --name browser --gpu --gpu-vram 2048
smolvm machine start --name browser
smolvm machine exec --name browser -- \
chromium --headless=new --no-sandbox --use-gl=angle --use-angle=vulkan \
--screenshot=/tmp/out.png --window-size=1280,800 https://example.com
```
The guest must set `VK_ICD_FILENAMES` so the Vulkan loader finds the virtio ICD. Put it in `env` in a Smolfile to avoid repeating it on every exec:
```toml
gpu = true
gpu_vram = 2048
env = ["VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/virtio_icd.x86_64.json"]
```
For a complete working example see [`examples/headless-browser/browser.smolfile`](examples/headless-browser/browser.smolfile).
## Secrets
smolvm stores no secret material. A secret is a *reference* to a value that
already lives on the host — a host environment variable or a host file — and is
resolved into the workload's process environment at launch time. Bring your own
secrets manager (Vault, 1Password, AWS, sops, your shell): render the value into
an env var or file, then point a ref at it. Only the reference is ever
persisted; the resolved value never lands in the VM record, the database, or a
`.smolmachine` pack.
Attach refs on the command line:
```bash
# From a host environment variable (GUEST_VAR=HOST_VAR)
smolvm machine run --secret-env OPENAI_API_KEY=OPENAI_API_KEY -- ./app
smolvm machine create --name web --secret-env DATABASE_URL=PROD_DB_URL # persists the ref
smolvm machine exec --name web --secret-env TOKEN=CI_TOKEN -- ./deploy
# From a host file (GUEST_VAR=/absolute/path)
smolvm machine run --secret-file GCP_CREDS=/abs/creds.json -- ./app
# Bridge any external manager through the env/file seam, e.g. 1Password:
op run --env-file=secrets.env -- smolvm machine run -- ./app
```
Or reference them from a Smolfile. The left-hand key becomes the env var name in
the guest workload:
```toml
[secrets]
DATABASE_URL = { from_env = "PROD_DB_URL" } # host env var (at launch)
GCP_CREDS = { from_file = "/abs/creds.json" } # absolute host file (at launch)
```
Exactly one of `from_env`, `from_file` must be set per entry; `from_file` paths
must be absolute. Resolved values are appended *after* top-level `env` and CLI
`-e` flags. Resolution is late-bound, so rotating the underlying env var or file
takes effect at the next launch with nothing to re-sync.
**Threat model:** this is defense-in-depth, not zero-knowledge. The target
process sees plaintext in its own environment, and root inside the guest can
read any `/proc/*/environ`. Use SSH agent forwarding instead when a secret must
never leave the host.
**Where they're resolved:** `machine run`, `machine create` + `machine start`,
and `machine exec` resolve refs against *this host* under a trusted-local scope.
Untrusted surfaces — HTTP API request bodies and portable `.smolmachine` packs —
are treated as untrusted callers and may carry **no** resolvable secret ref:
`from_env` would expose the server's env and `from_file` would be an arbitrary
host-file read, so both are rejected. Configure secrets locally instead.
## File Copy
Copy files between the host and a running machine using `machine:path` syntax:
```bash
# Upload a file to the VM
smolvm machine cp ./script.py myvm:/workspace/script.py
# Download a file from the VM
smolvm machine cp myvm:/workspace/output.json ./output.json
```
**Image-based VMs (--image):** Files copied with `cp` are visible to
`exec` at the same path, and vice versa. This works for any path —
`/tmp`, `/home`, `/workspace`, etc. Under the hood, `cp` routes
through the container's overlay filesystem so both commands see the
same files.
**`/workspace` shared directory:** Every machine has a `/workspace`
directory — bare VMs, image-based VMs, and machines created from
`.smolmachine` artifacts. It persists across `exec` sessions and
across `stop`/`start` cycles. It's a good default location for
scripts, data, and results. Passing `-v /host/dir:/workspace` replaces
the default storage-disk workspace with your host directory for that
run — the host mount takes priority and the storage workspace is skipped:
```bash
# Typical agent workflow: copy code in, execute, extract results
smolvm machine create --name r-sandbox --image r-base:latest --net
smolvm machine start --name r-sandbox
smolvm machine cp analysis.R r-sandbox:/workspace/analysis.R
smolvm machine exec --name r-sandbox -- Rscript /workspace/analysis.R
smolvm machine cp r-sandbox:/workspace/results.csv ./results.csv
smolvm machine stop --name r-sandbox
```
**Behavior and limits:**
- Files up to 1 MiB transfer as a single message — no perceptible
overhead beyond the agent round-trip.
- Larger files stream automatically: 1 MiB chunks for upload, 16 MiB
chunks for download. The split is asymmetric because the
host→guest direction has tighter socket-buffer headroom.
- Per-transfer cap is **4 GiB** in either direction. Files at or
above this size are rejected up front (`total_size exceeds maximum`
on upload; `exceeding the byte cap` on download). For larger
blobs, mount a host directory with `--volume` instead of copying.
- A throttled progress line prints to stderr while large transfers
run, including bytes-so-far, percentage (uploads), and rate.
Pipe captures (`> file`) only see the upload/download summary,
not the progress noise.
- Atomic on the guest side: a partially-written file never appears
at the target path. If the transfer fails or the connection drops
mid-stream, the staging file is cleaned up and the original
destination (if any) is unaffected.
Typical throughput on macOS (Apple Silicon): ~35-42 MB/s upload,
~170 MB/s download.
## Streaming Exec
Stream command output in real-time instead of buffering:
```bash
# CLI — prints output as it arrives
smolvm machine exec --stream --name myvm -- python3 train.py
# API — Server-Sent Events
POST /api/v1/machines/:name/exec/stream
Content-Type: application/json
{"command": ["python3", "train.py"]}
# Response: text/event-stream
# event: stdout
# data: Epoch 1/10...
# event: exit
# data: {"exitCode":0}
```
## Bare VM Mode
`machine run` works without `--image` when a Smolfile provides the workload config, or for direct Alpine shell access:
```bash
# Bare Alpine shell
smolvm machine run -it
# Smolfile with entrypoint/cmd (no container overhead)
smolvm machine run -s Smolfile
# Bare VM with init setup, detached
smolvm machine run -d -s Smolfile
```
Bare VMs run commands directly in the Alpine rootfs — no OCI image pull needed. Use this when you need a minimal Linux environment.
## Packed Binaries (.smolmachine)
`smolvm pack create` produces two files:
- `my-app` — stub binary with embedded VM runtime (platform-specific)
- `my-app.smolmachine` — VM payload: rootfs, OCI layers, storage (cross-platform)
The packed binary runs as a normal executable:
```bash
./my-app run -- python3 -c "print('hello')" # ephemeral, cleaned up after exit
./my-app start # persistent daemon mode
./my-app exec -- pip install x # exec into daemon
./my-app stop # stop daemon
```
Alternatively, create a named machine from the `.smolmachine` for full lifecycle management:
```bash
smolvm machine create --name my-vm --from my-app.smolmachine
smolvm machine start --name my-vm # ~250ms boot, no image pull
smolvm machine exec --name my-vm -- pip install x # fully persistent
smolvm machine stop --name my-vm
smolvm machine ls # shows my-vm
```
The `.smolmachine` manifest includes registry-oriented metadata:
- `host_platform` — host OS+arch this machine runs on (e.g., `darwin/arm64`), distinct from `platform` which is the guest
- `created` — RFC 3339 timestamp of when the machine was packed
- `smolvm_version` — version of smolvm that built it
## HTTP API
Start with `smolvm serve start --listen 127.0.0.1:8080` or `smolvm serve start --listen $XDG_RUNTIME_DIR/smolvm.sock`. Key endpoints:
```
POST /api/v1/machines Create machine
GET /api/v1/machines List machines
GET /api/v1/machines/:name Get machine
POST /api/v1/machines/:name/start Start machine
POST /api/v1/machines/:name/stop Stop machine
DELETE /api/v1/machines/:name Delete machine
POST /api/v1/machines/:name/exec Execute command
POST /api/v1/machines/:name/exec/stream Streaming exec (SSE)
PUT /api/v1/machines/:name/files/*path Upload file
GET /api/v1/machines/:name/files/*path Download file
GET /api/v1/machines/:name/logs Stream logs (SSE)
POST /api/v1/machines/:name/images/pull Pull OCI image
```
OpenAPI spec: `smolvm serve openapi`
## Important Defaults
- Machine name defaults to `"default"` when `--name` is omitted
- Network is **off** by default (security-first)
- CPUs: 4, Memory: 8192 MiB, Storage: 20 GiB, Overlay: 2 GiB
- Packed binaries use the same defaults (CPUs: 4, Memory: 8192 MiB)
- Memory and CPU are elastic via virtio balloon — the host only commits what the guest actually uses and reclaims the rest
## Important Behaviors
- **Observational commands don't stop running VMs.** `machine images`, `machine status`, `machine ls` and similar read-only commands leave a running VM in its current state. If the VM was already running before the command, it stays running after.
- **`machine prune` works on a running VM.** Regular prune only removes unreferenced layers and is safe while containers are active. `prune --all` requires the VM to be stopped first since it deletes manifests for layers that may be in use.
- **`machine exec` persists filesystem changes.** Package installs, config edits, and file writes inside `exec` survive across sessions. This works for both bare VMs and image-based VMs (created with `--image`).
- **`machine update` modifies a stopped machine.** Add/remove mounts, ports, env vars, or change CPU/memory without recreating the VM. Changes take effect on next `machine start`. Requires the machine to be stopped.
- **`machine run` is always ephemeral.** The VM is created, the command runs, and everything is cleaned up. No state carries over.
- **`-v host:/workspace` replaces the default workspace.** Every image-based VM exposes `/workspace` backed by the VM's storage disk. Mounting a host directory at `/workspace` takes priority — the host share is used instead and the storage-disk workspace is not mounted. Any other target path (e.g. `/data`, `/app`) does not affect `/workspace`.
Usage: smolvm-bin <COMMAND>
Commands:
machine Manage machines (create, start, stop, exec) [aliases: vm]
serve Start the HTTP API server for programmatic control
pack Package and run self-contained VM executables
config Manage smolvm configuration (registries, defaults)
help Print this message or the help of the given subcommand(s)
Options:
-h, --help
Print help (see a summary with '-h')
-V, --version
Print version
Agents: run `smolvm --help` for full documentation including CLI reference and Smolfile schemaenumerated method → `smolvm --version`
smolvm 1.2.0
smolvm 1.2.0
passthrough → `smolvm machine ls`
No machines found
No machines found
boots a real isolated VM (separate kernel)
hello from microVM
Linux container 6.12.87 #1 SMP Fri May 8 14:25:15 CEST 2026 aarch64 Linux
3.24.1
── stderr ──
Starting ephemeral machine (vm-c39e250d)...
Pulling image alpine...
Pulling image alpine... [====================] 100%
Pulling image alpine... [====================] 100% — syncing...
Pulling image alpine... done.hello from microVM
Linux container 6.12.87 #1 SMP Fri May 8 14:25:15 CEST 2026 aarch64 Linux
3.24.1
── stderr ──
Starting ephemeral machine (vm-3b055ee1)...
Pulling image alpine...
Pulling image alpine... [====================] 100%
Pulling image alpine... [====================] 100% — syncing...
Pulling image alpine... done.pulls python:3.12-alpine, runs Python in the VM
2**100 = 1267650600228229401496703205376
── stderr ──
Starting ephemeral machine (vm-25a8264d)...
Pulling image python:3.12-alpine...
Pulling image python:3.12-alpine... [=====> ] 25%
Pulling image python:3.12-alpine... [==========> ] 50%
Pulling image python:3.12-alpine... [===============> ] 75%
Pulling image python:3.12-alpine... [====================] 100%
Pulling image python:3.12-alpine... [====================] 100% — syncing...
Pulling image python:3.12-alpine... done.2**100 = 1267650600228229401496703205376
── stderr ──
Starting ephemeral machine (vm-215457f3)...
Pulling image python:3.12-alpine...
Pulling image python:3.12-alpine... [=====> ] 25%
Pulling image python:3.12-alpine... [==========> ] 50%
Pulling image python:3.12-alpine... [===============> ] 75%
Pulling image python:3.12-alpine... [====================] 100%
Pulling image python:3.12-alpine... [====================] 100% — syncing...
Pulling image python:3.12-alpine... done.