Skip to content

guides

SDK — Python and TypeScript

Declare microVM workloads with the mvm Python and TypeScript SDKs. Decorator-style author surface; mvmctl build compile / deploy compiles the same source statically without executing it.

The mvm SDK lets you declare a microVM workload from a single decorated function in Python or TypeScript. Two routes from the same source file produce the same Workload IR:

  • In-process: python app.py or node app.ts imports mvm, the decorator records the declaration, and mvm.emit_json() / mvm.emitJson() writes the canonical IR to stdout.
  • Static compile: mvmctl build compile app.py walks the AST without importing the script. Same IR; the host never runs user code.
import mvm
@mvm.app(
name="hello-app",
source=mvm.local_path("."),
image=mvm.python_image(python="3.12"),
resources=mvm.resources(cpu_cores=1, memory_mb=256, rootfs_size_mb=512),
entrypoint=mvm.entrypoint_function(
module="app",
function="greet",
primary=True,
),
env={
"MODEL_PATH": mvm.literal("/data/model.pt"),
"API_KEY": mvm.secret("api-key", type="bearer", hosts=["api.example.com"]),
},
before_start="export HELLO_BANNER='hi'",
after_start=mvm.hook(["curl", "-fsS", "http://127.0.0.1:8080/health"]),
)
def greet(name: str) -> str:
return f"hello {name}"

Build, run, invoke:

Terminal window
mvmctl build compile examples/python/hello-app/app.py --out /tmp/hello-app
echo '[[], {"name":"ari"}]' | mvmctl machine run --entrypoint --flake /tmp/hello-app # → "hello ari"

Both SDKs ship a closed set of helpers the static parser also recognizes. Anything else in decorator kwarg position is rejected:

HelperReturnsNotes
python_image(python=)Image::NixPackagespython="3.12"python312 nix attribute
node_image(node=)Image::NixPackagesnode="22"nodejs_22 nix attribute
nix_packages([...])Image::NixPackagesdirect passthrough
resources(...)Resourcescpu/memory_mb/rootfs_size_mb
network(mode=, ports=)Networknone | bridge | host
secret(name, var=)EnvValue::SecretRefhost-mediated secret ref; guest sees opaque token only
literal(value)EnvValue::Literalparity with secret(...)
hook(cmd)HookCmdstr → Shell; list → Argv

Four phases. Each accepts a string (shell line), a list of strings (argv), a single mvm.hook(...), or a list of any of those.

PhaseRuns
before_buildInside the builder VM, after deps install and before the rootfs snapshot.
before_startAt every microVM boot, before the entrypoint dispatch.
after_startReadiness probe — polled to exit-0 before the agent accepts entrypoint calls.
before_stopAt shutdown, best-effort.

Addons (when attached via addons=[...]) contribute their own hooks; the compiler merges them into the rootfs before the consuming app’s hooks, in attachment order. The result lands in launch.json for the Nix factory to bake into /etc/mvm/hooks/<phase>.sh.

Use mvmctl build compile to produce local build artifacts that can be reviewed, signed, built, and launched through the normal mvm workflow. Treat packaging and rollout as a separate admission step, not as part of declaration parsing.

mvm.secret(...) names a managed secret reference, not a raw secret value.

  • The guest sees a normal env var name and an opaque token value.
  • The raw secret stays on the host side.
  • Request-time release is supported only on host-mediated outbound surfaces such as mvm.web_fetch and mvm.web_search.
  • Guest HTTPS CONNECT egress is not a managed-secret substitution path.

mvmctl build compile app.ts walks the AST statically for mvm.app({...}) calls and does not run user code. When a .ts script uses the record-mode Sandbox API instead (no mvm.app decorator), mvmctl compile falls back to auto-run: it spawns the script on the host with MVM_SDK_MODE=record set, and the SDK’s atexit hook writes a recording JSON that the CLI then lowers into a Workload.

That auto-run path needs a TypeScript-aware runner — plain node cannot execute .ts in mvm’s supported Node range. Three runners are supported, in priority order: tsx, bun, deno.

OSRecipe
macOS (Homebrew)brew install tsx or brew install oven-sh/bun/bun or brew install deno
Any (npm)npm install -g tsx
Any (pnpm)pnpm add -g tsx
Any (yarn)yarn global add tsx
Any (bun)curl -fsSL https://bun.sh/install | bash
Any (deno)curl -fsSL https://deno.land/install.sh | sh

Prefer the project-local install if you want a reproducible lockfile- pinned runner:

Terminal window
npm install --save-dev tsx

That puts the binary at ./node_modules/.bin/tsx, and mvmctl compile picks it up automatically — see the resolution order below.

mvmctl build compile resolves the runner in this order, picking the first hit:

  1. MVM_TSX=<path> env override — explicit pin (any binary).
  2. ./node_modules/.bin/tsx — cwd-relative, lockfile-pinned.
  3. ./node_modules/.bin/bun.
  4. ./node_modules/.bin/deno.
  5. tsx on PATH.
  6. bun on PATH.
  7. deno on PATH.

If nothing resolves, the command fails with the install hint shown above. mvmctl doctor surfaces the same hint in its Tools section — run it after a fresh checkout to confirm your host is wired up.