Skip to content

wasmtime & wasmer

Two production-grade standalone runtimes dominate the WASI ecosystem: wasmtime (maintained by the Bytecode Alliance, written in Rust) and wasmer (written in Rust, cross-platform). Both implement the WASI spec, support ahead-of-time (AOT) compilation, and expose embedding APIs for Rust, Python, Go, and other languages.

Terminal window
# wasmtime — via installer script
curl https://wasmtime.dev/install.sh -sSf | bash
# wasmer — via installer script
curl https://get.wasmer.io -sSfL | sh
# Verify
wasmtime --version
wasmer --version
Terminal window
# Basic execution
wasmtime my_app.wasm
# Pass command-line arguments (-- separates runtime flags from module args)
wasmtime my_app.wasm -- hello world
# Grant access to a directory
wasmtime --dir /tmp my_app.wasm
# Grant access with a path alias (host path::guest path)
wasmtime --dir /host/data::/data my_app.wasm
# Pass environment variables
wasmtime --env DATABASE_URL=postgres://localhost/db my_app.wasm
# Combine: dir + env + args
wasmtime --dir /data --env LOG_LEVEL=info my_app.wasm -- --config /data/cfg.toml
Terminal window
# Basic execution
wasmer my_app.wasm
# Mount a directory
wasmer --dir /tmp my_app.wasm
# Pass environment variables
wasmer --env KEY=value my_app.wasm
# Run from the Wasmer registry (wapm packages)
wasmer run python/python -- --version

The real power of standalone runtimes is embedding — loading .wasm plugins at runtime without recompiling the host application.

use wasmtime::{Engine, Linker, Module, Store};
use wasmtime_wasi::{p1::WasiP1Ctx, WasiCtxBuilder};
fn run_plugin(wasm_bytes: &[u8]) -> anyhow::Result<()> {
let engine = Engine::default();
let module = Module::new(&engine, wasm_bytes)?;
// Build a minimal WASI Preview 1 context — no filesystem, no env vars
let wasi: WasiP1Ctx = WasiCtxBuilder::new().inherit_stdio().build_p1();
let mut store = Store::new(&engine, wasi);
let mut linker = Linker::new(&engine);
wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |s| s)?;
let instance = linker.instantiate(&mut store, &module)?;
let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
start.call(&mut store, ())?;
Ok(())
}

This pattern is how plugin systems work: the host application loads arbitrary .wasm files at runtime and calls exported functions while the sandbox enforces strict isolation.

sequenceDiagram
  participant Host as Host App (Rust)
  participant WT as wasmtime Engine
  participant M as Plugin (.wasm)
  Host->>WT: Engine::default()
  Host->>WT: Module::new(wasm_bytes)
  Host->>WT: Linker + WasiCtxBuilder
  Host->>WT: linker.instantiate()
  WT->>M: instantiate + link imports
  Host->>M: typed_func("process").call(args)
  M-->>Host: return value
Embedding wasmtime to run a plugin module
OptionBenefitCost
WASI capability-based security (--dir/--env)Much safer — a module wasmtime/wasmer runs only gets the exact permissions the flags grantNot fully POSIX-compatible; a binary ported from native code that assumes ambient access won’t run unmodified
Embedding wasmtime/wasmer for edge/serverlessMicrosecond-scale cold starts compared to spinning up a fresh container every timeContainer ecosystems still have more mature tooling, orchestration, and library support
  • Forgetting to pass --dir or --env at runtime, then being surprised the module can’t reach the filesystem or an environment variable — capabilities must always be granted explicitly
  • Assuming SIMD or the threads proposal is supported identically across every runtime — wasmtime and wasmer can differ in support level, so check the specific runtime version before deploying
  • Embedding an older wasmtime version alongside a component built with a newer Component Model release — the still-evolving WIT interface can break old binding code at link or instantiate time

💡 Real-world example

Fermyon Spin is built on Wasmtime to embed and run serverless Wasm modules, while Fastly Compute and Cloudflare Workers apply the same embedding-and-sandboxing principle to isolate tenant code with very low cold-start times.

Which flag grants a WASI module access to a host directory in wasmtime?
What is the purpose of WasiCtxBuilder when embedding wasmtime in Rust?
What does ahead-of-time (AOT) compilation achieve in wasmtime?