Guides
From dev loop to attested image
Start from an OCI image or a Nix flake, capture what your workload actually needs, and end with a sealed, hashed, recorded artifact.
The path from “does this even run?” to “this is an attested artifact” is meant to be one line of travel: pick a base, iterate until it works, then seal and record the result.
This page documents that path as it works today, and is explicit about the parts that are designed but not yet built.
1. Pick a base — OCI or Nix, both are first class
Section titled “1. Pick a base — OCI or Nix, both are first class”# OCI: fastest start, no flake and no host Nixmvmctl machine run --image python:3.12 -- python -c "print(2 + 2)"
# Nix flake: reproducible, minimal, carries only what you declaremvmctl machine run --flake . -- ./appBoth compile to the same thing — a signed image plus a launch plan — and boot identically on every backend. They do not carry the same provenance story; see Nix and OCI for the difference.
2. Declare what the workload needs
Section titled “2. Declare what the workload needs”Dependencies are declared alongside the workload, and the lockfile must be hash-pinned. An entry without an integrity hash is rejected at compile time rather than resolved at build time:
import mvm
@mvm.app( name="detector", source=mvm.local_path("."), image=mvm.python_image(python="3.12"), resources=mvm.resources(cpu_cores=1, memory_mb=512, rootfs_size_mb=1024), dependencies=mvm.python_deps(lockfile="uv.lock", tool="uv"),)def detect(path: str) -> int: import cv2 # opencv, resolved from the pinned lockfile return len(cv2.imread(path))mvmctl build compile app.py --out ./outmvmctl machine build --flake ./outuv.lock, yarn.lock and the other supported formats are each checked for
per-entry integrity hashes. This is what makes the resulting dependency set
reproducible rather than merely recorded.
3. What you get: a sealed dependency volume
Section titled “3. What you get: a sealed dependency volume”Building installs the declared dependencies into a sealed volume, not into the image. That volume carries:
- the installed content, hash-locked
- an SBOM (
sbom.cdx.json) - a CVE scan (
cve.json) - a fetch log
- a hash-chained
meta.jsonbinding all of the above together
mvmctl deps inspect HASH # read the sidecars without booting a VMmvmctl deps auditThe supervisor verifies this volume before launch and refuses a tampered one, so
the dependency set is part of the workload’s admitted identity — not a
convention. --prod additionally fails closed on high or critical CVE findings,
and on a stub SBOM or CVE report.
4. Run it
Section titled “4. Run it”mvmctl machine run --entrypoint --flake ./outThe image, the environment it boots in, and the sealed dependency volume are all pinned into the signed execution plan, and every admission is recorded in the chain-signed audit log.
Designed, not yet built
Section titled “Designed, not yet built”The following are specified in specs/plans/291-develop-build-deploy-attested.md
and do not exist yet. They are listed here so the intended shape is legible,
not because they can be run:
mvmctl deploy— seal, compute the artifact’s BLAKE3 identity alongside its SHA-256 interop digest, and write a deploy record. If a remote (mvmd) is configured it ships the recorded artifact; if not, you still end up holding a sealed, recorded artifact locally.mvmctl watch— rebuild on source change during development, skipping no-op rebuilds by content address.- Capture from the sandbox — install a dependency inside a dev sandbox and capture it into the sealed volume, then emit it back out as a declaration you can commit. The capture path is designed to converge on the declared path above, keeping the same hash-pin requirement, rather than becoming a second way to specify dependencies.
Until those land, the declared route in step 2 is the supported way to get a dependency into an attested workload.
Why two digests
Section titled “Why two digests”Deployed artifacts are designed to carry both BLAKE3 and SHA-256, and the distinction is deliberate:
- BLAKE3 is identity. It is a tree hash, so a large rootfs can be verified incrementally and in part rather than read end to end.
- SHA-256 is interop. OCI registry digests, cosign signatures, and in-kernel dm-verity all specify SHA-256, and dm-verity has no BLAKE3 support at all — so verified boot keeps its SHA-256 roothash.
Anything that pins a digest states which of the two it means.