WVM is a lightweight runtime manager for Wasmtime. It installs, selects, discovers, validates, and executes versioned WebAssembly runtimes so that Wasmtime becomes an implementation detail rather than a prerequisite.
WVM is self-hosting: a thin native bootstrapper downloads and locks a
protected seed Wasmtime, then runs the WVM application as a WebAssembly
component on that runtime. See docs/design.md for the full
design.
curl -fsSL https://raw.githubusercontent.com/tegmentum/wvm/main/install.sh | shOr with Homebrew:
brew tap tegmentum/wvm https://github.com/tegmentum/wvm
brew install wvmOn first run, wvm downloads and locks a protected seed Wasmtime runtime and
runs as a WebAssembly component on it.
wvm list # all available versions (installed ones marked)
wvm install latest # download (over wasi:http) + verify + install
wvm default latest # default runtime for new shells
wvm exec -- --version # run the selected runtimeLong operations show a progress bar / spinner on stderr when attached to a terminal, and fall back to plain milestone lines when output is piped.
wvm default <version>sets the persistent default used by new shells.wvm use <version>switches the runtime for the current shell only (reverting when you open a new one), via aWVM_VERSIONenvironment variable.
Because wvm is a binary it can't change its parent shell directly, so per-shell
use needs a one-time shell hook (like nvm/pyenv):
wvm shell-init >> ~/.zshrc # then restart your shellAfter that, wvm use 44.0.0 applies to the current shell and wvm deactivate
reverts it to the default.
| Command | Description |
|---|---|
wvm install <version> |
Install a runtime (latest for newest, lts for the newest LTS). --default to set it as default. |
wvm list [--all] |
List all available versions; lts/installed/default/seed marked. --all includes prereleases. |
wvm uninstall <version> |
Remove an installed runtime (--force past app deps; the seed cannot be removed). |
wvm register <app-dir> |
Record an app's runtime dependency from its wvm.toml [app]. |
wvm unregister <name> |
Drop an application's registration. |
wvm apps |
List registered applications and the runtimes they depend on. |
wvm default <version> |
Set the persistent default (used by new shells). |
wvm use <version> |
Switch the runtime for the current shell (needs shell-init). |
wvm deactivate |
Clear the per-shell override, reverting to the default. |
wvm shell-init |
Print the shell hook that enables per-shell use. |
wvm current |
Print the effective version (session override, else default). |
wvm path [version] |
Print a runtime's filesystem path. |
wvm exec -- <args> |
Run the selected runtime, forwarding arguments. |
wvm verify [version] |
Validate installation integrity against manifests. |
wvm gc [--prune] |
Report (or delete) unreferenced store objects. |
wvm objects |
List stored objects with sizes and the versions referencing them. |
wvm (native bootstrapper, on PATH)
├─ ensures the protected seed Wasmtime (downloads once, then locks)
├─ handles `wvm exec` natively (resolve runtime, then exec)
└─ runs the app on the seed: wasmtime run -S http --dir WVM_HOME wvm-app.wasm -- <args>
wvm-app (wasm32-wasip2 component) — all other commands
├─ explicit wasi:cli command: include wasi:cli/imports + export wasi:cli/run@0.2.6
├─ imports wasi:http (downloads, via waki)
└─ imports sqlite:wasm/high-level (the index; composed in via `wac`)
wvm-app is a standard wasi:cli command (built as a cdylib that owns its
wasi:cli/run@0.2.6 export), so it also runs directly under any wasi:cli host:
wasmtime run -S http --dir "$WVM_HOME::$WVM_HOME" --env WVM_HOME="$WVM_HOME" \
target/wvm-app.composed.wasm -- listThe native binary embeds the composed app component; the ~50 MB runtime is downloaded on bootstrap rather than bundled.
wvm exec resolves a runtime in this order:
- Project pin — nearest
wvm.tomlwalking up from the working directory:[wvm] runtime = "44.0.0"
- Session —
WVM_VERSION, set per shell bywvm use. - Default — the persistent default set by
wvm default. - Environment override —
WASM_RUNTIME_HOMEorWASMTIME_HOME. - System / PATH — a
wasmtimealready onPATH.
Set WVM_VERBOSE=1 to print which runtime was selected.
Applications can declare which Wasmtime version(s) they were tested against, so
wvm knows whether a runtime is safe to remove and which apps are behind. An app
owns a small manifest (the [app] section of its wvm.toml) that it reads
itself — so it works with no wvm installed and may bring its own runtime:
[app]
name = "tegmentum-foo"
runtimes = ["44.0.0", "45.0.0"] # wvm-managed versions tested against
# runtime-path = "/opt/foo/bin/wasmtime" # OR a custom runtime the app supplieswvm register ./my-app # reads my-app/wvm.toml and records the dependency
wvm apps # list registered apps and their runtimesRegistration is advisory bookkeeping — apps never depend on wvm at runtime.
With it, wvm uninstall <version> refuses to remove a runtime a registered app
still needs (listing the dependents; --force overrides). An app that sets
runtime-path is fully decoupled: it's recorded for visibility but pins no
wvm-managed runtime.
WVM stores everything under ~/.tegmentum/wvm (override with WVM_HOME). Files
are kept once in a content-addressable store and referenced per version, so
multiple versions share identical files:
~/.tegmentum/wvm/
seed/
bin/wasmtime # protected seed runtime (read-only)
SEED # locked seed version
store/sha256/<ab>/<cd>/<digest> # deduplicated file objects
runtimes/wasmtime/
versions/44.0.0/
bin/wasmtime # materialized from the store
manifest.json
default # persistent default version (plain text)
downloads/
index.db # SQLite backlink/metadata index (rebuildable cache)
wvm-app.wasm # the app component
config.toml
The protected seed runtime lives in seed/, separate from user-managed
versions; WVM never lists or deletes it. The index.db SQLite database tracks
object backlinks and version metadata; it is a derived cache that wvm gc
rebuilds from disk, so a missing or stale index is never fatal.
Materialization is copy by default (symlinks are unavailable under wasm); the
store still deduplicates shared files.
Requires the Rust wasm32-wasip2 target and wac
(cargo install wac-cli).
rustup target add wasm32-wasip2
make # builds the app, composes it with the SQLite component,
# then builds the native binary (target/release/wvm)The vendored SQLite component (vendor/sqlite-core.wasm) provides
sqlite:wasm/high-level; it is built from
sqlite-wasm. The WASI WIT for the
app's wasi:cli command world is vendored under crates/wvm-app/wit/deps
(fetched with wkg wit fetch), so a normal build needs no network for WIT.
For each platform, make produces target/release/wvm; publish it on the
GitHub release as wvm-<arch>-<os> (e.g. wvm-aarch64-macos) alongside a
matching wvm-<arch>-<os>.sha256. Then bump version and the per-platform
sha256 values in Formula/wvm.rb. The install.sh script
and the Homebrew formula both consume those wvm-<arch>-<os> assets.
Wasmtime cuts an LTS every
12 releases (major divisible by 12 — 24, 36, 48, …), supported 24 months; wvm
marks these in wvm list and resolves wvm install lts to the newest one.
CI runs on GitHub Actions (.github/workflows/ci.yml): format check, the full
make build, clippy (-D warnings), and tests. Tagging v* triggers
release.yml, which builds the wvm-<arch>-<os> binaries + checksums for each
platform and attaches them to the release.
Run the same checks locally:
make ci # fmt + build + clippy + test, no Docker
make act # run the CI workflow in Docker via nektos/act (uses .actrc)make act needs a running Docker daemon (e.g. Colima:
colima start). On Apple Silicon, .actrc pins linux/amd64 to match
GitHub-hosted runners.
Apache-2.0.