Drive supported workflows directly through scripts/nvx.py; no Make wrapper
is required.
The portable workflow downloads the pinned Linux archive, verifies its
SHA-256, applies every patch in kernel/patches, and builds Linux plus the
selected guest artifacts in Docker. Alpine remains the default. OpenVMM builds
on the host:
python3 scripts/nvx.py build-guest
python3 scripts/nvx.py build-openvmmBuild every guest artifact, including the Ubuntu EROFS distro layer, with:
python3 scripts/nvx.py build-guest --guest allOn a Linux host, --native builds the kernel and the selected Alpine or Ubuntu
initramfs without Docker, using the host packages that
Setup lists. Azure Linux builds only in Docker, so a
native build rejects --guest azurelinux and --guest all. Because only
--guest all builds the Ubuntu EROFS distro layer, build it after a native
build with build-distro-layer, described below.
python3 scripts/nvx.py build-guest --native
python3 scripts/nvx.py build-guest --native --guest ubuntuBoth native image builds call the same guest-binary routine that Docker and
the nvx-rs CI job use. It can also be run directly:
python3 scripts/nvx.py build-guest-binaries --output build/guest-binariesThe routine takes the exact toolchain from rust-toolchain.toml, the
x86_64-unknown-linux-musl target, and the guest profile and feature. It
uses --locked, a fresh target directory, disabled incremental compilation,
stable source and Cargo-home path remapping, and no linker build IDs. One
Cargo invocation builds all ten programs, and every result must pass
check-static-elf. The output's manifest.json binds the exact build pins,
one digest over the nvx crate, the shared Python build recipe, and
rust-toolchain.toml, and each binary SHA-256.
The native build keeps the Linux archive, the Ubuntu Base archive, and the
supplemental Ubuntu packages under downloads/ in the cache directory,
.cache/ or $NVX_CACHE_DIR. It reuses a file there whose SHA-256 matches its
pin and downloads it again otherwise, so staging the pinned archives in that
directory lets a host without access to their download servers build natively.
The Alpine minirootfs and packages still come from the Alpine servers.
The OpenVMM restore step excludes the compatibility IGVM artifact, which NVX does not build or package, so builds do not depend on unrelated upstream workflow artifacts.
OpenVMM builds do not require access to runtime hypervisor devices. By default,
Windows builds the native MSVC executable and Linux builds the native GNU
target. Both build-openvmm and build accept --backend: kvm selects GNU,
mshv selects the statically linked musl target on Linux, and whp selects
MSVC on Windows. For example, this builds musl without requiring /dev/mshv:
python3 scripts/nvx.py build-openvmm --backend mshvThe combined build command rejects unsupported OS/backend combinations before
producing guest artifacts. Guest-only and source-only commands do not select an
OpenVMM build target.
When invoked through NVX, OpenVMM's TTRPC microVM tests use the ACPI-free,
MP-enabled build/vmlinux and build/initramfs.cpio.gz artifacts. The
phase-1 lifecycle and TTRPC interface tests continue to use OpenVMM's packaged
guest artifacts.
On a Linux host, build the Alpine or Ubuntu initramfs directly:
python3 scripts/nvx.py build-initramfs --guest alpine
python3 scripts/nvx.py build-initramfs --guest ubuntuBuild the Azure Linux initramfs through Docker:
python3 scripts/nvx.py build-initramfs --guest azurelinuxThe default build produces:
build/vmlinux
build/vmlinux.config
build/vmlinux.provenance.json
build/initramfs.cpio.gz
build/initramfs.cpio.gz.packages.json
build/initramfs.provenance.json
build/openvmm.provenance.json
openvmm/target/release/openvmm[.exe]
Programmatic callers can select the backend and override the output destination
through OpenVmmBuildConfig. Native and
musl builds normalize the newly built executable to that destination before
recording its provenance. Host OS detection and backend validation live in
build.py, not in the configuration object.
Fixed build inputs and defaults live in
build_constants.py. Its namespace
classes group kernel, OpenVMM, Rust guest-binary, Alpine, Ubuntu, initramfs,
Docker, cache-tool, and release settings. For example, Python callers use
KernelBuildConstants.VERSION and AlpineBuildConstants.MINIROOTFS_SHA256;
the previous module-level constants are not re-exported. Shared repository
and artifact directories belong to BuildConstants.
Keep per-invocation choices and overrides in the existing build configuration objects. Environment-dependent cache locations and host-dependent executable selection are still resolved by their helpers when a configuration is created, not frozen into the constants module.
The provenance sidecars bind the kernel to its pinned archive, patch set,
input configuration, generated configuration, and output hash; bind the
initramfs and package manifest to the pinned Alpine inputs, Rust guest inputs,
and source files; and bind OpenVMM to the exact clean gitlink revision and
executable hash. Packaging rejects missing, dirty, stale, or mismatched
provenance. build/nvx-rs.provenance.json likewise binds the checkout's
nvx-rs executable to the digest of its sources and its build's
Rust release, target, and features; nvx.py writes it when it builds
nvx-rs, and record-nvx-rs-provenance
writes it for a build made otherwise.
Ubuntu adds:
build/initramfs-ubuntu.cpio.gz
build/initramfs-ubuntu.cpio.gz.packages.json
build/ubuntu-distro.erofs
build/ubuntu-distro.erofs.manifest.json
build/ubuntu-smoke-scratch.ext4
Prepare the Ubuntu sandbox layer separately on Linux without replacing an existing output:
python3 scripts/nvx.py build-distro-layer \
--guest ubuntu \
--output build/ubuntu-distro.erofsPass --replace only when intentionally rebuilding that path. The native
Ubuntu build requires zstd, and EROFS conversion additionally requires
mkfs.erofs from erofs-utils. The builder verifies Ubuntu Base and every
supplemental .deb before safe extraction and never executes binaries or
maintainer scripts from the Ubuntu root. The Docker builder pins its Debian
base image by digest and installs an exact erofs-utils version so clean EROFS
builds use the same encoder. A lock that adds Python needs a build interpreter
of the root's Python minor version, which Debian's is not, so such a root
builds natively; see
Ubuntu guest support.
The sandbox scratch template, build/ubuntu-smoke-scratch.ext4, is the empty
ext4 file system of which the sandbox scenarios boot a copy as their scratch.
build-scratch-template formats it on
Linux with mkfs.ext4 from e2fsprogs, with the parameters that
UbuntuBuildConstants defines, so that the e2fsprogs releases of Debian,
Ubuntu, and Azure Linux format the same bytes:
python3 scripts/nvx.py build-scratch-templateCheck the Ubuntu outputs and the scratch template for deterministic rebuilds with:
python3 scripts/nvx.py verify-guest-determinism --guest ubuntuAzure Linux builds only in Docker and adds:
build/initramfs-azurelinux.cpio.gz
build/initramfs-azurelinux.cpio.gz.packages.json
Run the two test layers separately:
python3 scripts/nvx.py test-openvmm --backend kvm
python3 scripts/nvx.py test-microvm --backend kvm
python3 scripts/nvx.py test-microvm --backend kvm --guest ubuntuBoth commands need the standard guest build outputs above. The second writes
complete per-scenario logs under
build/test-results/microvm by default.
All three initramfses install the same ten Rust executables under /sbin:
nvx-console-pending, nvx-device-io, nvx-exit, nvx-ingress, nvx-init,
nvx-managed-agent, nvx-mmio-write, nvx-reseed, nvx-time, and
nvx-time-probe. Docker builds them once in a Rust
1.97.1 image pinned by digest and copies the identical bytes into Alpine,
Ubuntu, and Azure Linux; the kernel targets do not depend on that build. The
Ubuntu determinism target instead rebuilds them from fresh targets in both
attempts. Native image builds also rebuild them fresh.
The Alpine initramfs additionally includes the sandbox PID-1 bootstrap and
its container namespace scripts. nvx-time-probe measures TSC and
clock read latency, reports the guest's clock, clocksource, TSC flags, and
hypervisor identity, and runs the cross-vCPU warp test that the
time ABI uses to enforce its 1 µs
skew bound. nvx-time implements the time ABI's
guest obligations: the conformance
checks, the violation watcher, the restore packet v1 and time-sample v1
parsers, the snapshot agent's time steps, and the wall-clock discipline, which
applies the host's clock correction. Before shell-ready it only steps the
clock to host UTC and sets its rate; the other boot checks run in
the background, and the production console shows no time ABI line except a
violation event. nvx-time status prints every recorded check (boot, capture,
and restore) and the runtime state on demand, after waiting for pending
checks, for CI and test harnesses. Its boot check
also fails when the kernel's boot-time W+X audit reports a writable and
executable mapping. nvx-time exhaustive runs the CI-only exhaustive check
(checks X1 to X6 on every online CPU); it reports and exits, and production
boots never run it. The
--report-only flag (or the nvx_time_abi=report-only kernel command-line
token) reports violations without powering off, for runs against VMMs that
predate the ABI. All ten executables link the musl supplied by the pinned
Rust target. Every package manifest records the shared Rust source digest and
each installed binary digest. The
kernel configuration checks cover the kernel
features that the sandbox bootstrap needs. The APK manifest records the
blkid and util-linux tools used by the bootstrap along with every
guest-binary digest.
The Ubuntu initramfs uses Ubuntu userland with the NVX kernel. It is not an Ubuntu-kernel or systemd VM. Its distribution-neutral package manifest records the Ubuntu Base and supplemental binary/source identities, license metadata, rootfs SHA-256, and NVX guest-binary provenance.
The kernel build, build-kernel, which the Docker build also runs, caches the
verified and patched source under .cache/linux, uses O=build/linux, runs
olddefconfig, stops unless the generated config passes the
kernel configuration checks and is the one that
the kernel feature inventory generates, and exports it
as build/vmlinux.config. The time-ABI settings keep CONFIG_HYPERVISOR_GUEST
and CONFIG_PARAVIRT on and CONFIG_HYPERV, CONFIG_KVM_GUEST, and
CONFIG_CPU_FREQ off. The guest identifies the
hypervisor only through the time ABI's Hyper-V identity, so the KVM guest code,
kvmclock, and the haltpoll idle driver would be dead code. Without cpufreq,
intel_pstate cannot probe MSRs the microVM does not implement, so no #GP
traces are printed with interrupts disabled during boot. CONFIG_SCHED_MC_PRIO
is off as well because it selects both cpufreq and intel_pstate. Production
kernels must not enable the soft-lockup or hung-task detectors. Changing an
archive hash or patch invalidates both source and object caches; changing the
input configuration invalidates the object cache.
The kernel keeps the code it generates at run time read-only. On x86, only
CONFIG_STRICT_MODULE_RWX makes such memory read-only and executable
(CONFIG_ARCH_HAS_EXECMEM_ROX), and it requires CONFIG_MODULES. Without it,
the thunk pages of the Indirect Target Selection (ITS) mitigation stay writable
and executable when the mitigation patches indirect branches at boot, which it
does unless Spectre v2 uses retpolines. The kernel therefore supports modules
but never builds or ships one:
- the build fails if any option is set to
m; CONFIG_MODPROBE_PATHis empty, so the kernel never starts a module helper;CONFIG_TRIM_UNUSED_KSYMSdrops every symbol export, about 200 KB that would otherwise push the kernel's data past the next 2 MiB boundary;- init writes 1 to
/proc/sys/kernel/modules_disabledbefore anything else runs.
When the mitigation patches indirect branches, the read-only executable cache
takes one 2 MiB block of guest memory. CONFIG_DEBUG_WX audits the kernel page
tables at boot, and the nvx-time boot check fails if the audit reports a
writable and executable mapping.
kernel/config-microvm is generated, never edited
by hand: it is make allnoconfig with the settings of the kernel feature
inventory, kernel_features.py, as
its KCONFIG_ALLCONFIG fragment. Every symbol that a user could set is
therefore off unless a feature of the inventory sets it, and each feature says
why NVX needs it, which NVX components consume it, and which checks and
scenarios exercise it. Kconfig sets every other symbol because of those
settings, and kernel/config-microvm.derived
records each one with the rule that sets it:
| Rule | The symbol is set because |
|---|---|
selected by X, Y |
the enabled symbols X and Y select it |
implied by X |
its prompt is hidden and the enabled X implies it |
choice default |
it is the default member of its choice |
default |
it is a value that keeps its default |
default, prompt hidden by C |
its prompt is hidden while C does not hold, and its default holds |
no prompt |
no user can set it: its architecture, its toolchain, or other symbols decide it |
So every symbol that the configuration sets is either an inventory setting or
derived from one by a Kconfig dependency or default, and every other feature is
off. The inventory sets CONFIG_EXPERT, which turns the
options that Linux otherwise forces on, such as the virtual terminal, the PC
speaker, and obsolete system calls, into settings like any other; it selects
CONFIG_DEBUG_KERNEL, which only shows the debug options, and every one that
the inventory does not set stays off. The kernel build runs make without
ARCH, so ARCH is x86 and CONFIG_64BIT is one of the inventory's
settings too.
A symbol that only some toolchains offer stays as the inventory decides on
every toolchain: when the toolchain that generates the configuration hides a
symbol that the inventory keeps off, such as CONFIG_GCC_PLUGINS, which GCC
offers where its plugin headers are installed, the generator appends its
"is not set" line, which olddefconfig applies where another toolchain shows
the symbol.
Two checks keep the configuration and the inventory together:
scripts/nvx.py check-kernel-configneeds no kernel source. It requires every setting and expectation of the inventory to hold inkernel/config-microvm, and every other symbol that the configuration sets to be recorded inkernel/config-microvm.derivedwith the same value. A newly enabled symbol that neither the inventory nor the record accounts for, such as a line added by hand, fails it with a message that names the symbol. CI's quality job runs it on every change,scripts/nvx.py verifyruns it, and so do the unit tests oftest_kernel_config.py.- The production kernel build generates the configuration again, from the
same source with the same toolchain, in
build/linux-inventory, and requires the configuration thatolddefconfigresolved for the build to equal it, and the derived record to match it. The record's first line names the toolchain that generated it: the compiler's version text and the versions of the assembler, the linker, and the other tools that Kconfig probes. Where the build's toolchain is that one, as in CI's Docker build, the record must match exactly; another toolchain may detect other capabilities, so there only the entries of symbols with a prompt must match, and every other entry must name a symbol without one. CI's kernel builds run it whenever a kernel input or the inventory changes.
To enable or disable a kernel feature:
-
Add its settings to the feature of the inventory that needs it, or add a feature, with a one-line summary, the rationale, the NVX consumers, and the validation that exercises it. Set only symbols with a prompt; list a derived symbol that the feature relies on under
expects. To remove a feature, remove its settings; the derived symbols that it pulled in go with it. -
Regenerate both files:
python3 scripts/nvx.py kernel-config # in the Docker build's image python3 scripts/nvx.py kernel-config --native # on Linux, with this host's toolchain
The default runs in the image that builds CI's kernels, so the files record CI's toolchain. Generation fails if a setting's dependencies are unmet; set them too.
-
Review the diff of
kernel/config-microvm.derived: every symbol that the change adds or removes is there, with the rule that sets it. -
Run
python3 scripts/nvx.py check-kernel-configand build the kernel, and commit the inventory and both files together. The kernel configuration checks below derive their groups from the inventory, so the build and packaging check the new settings too.
A kernel update regenerates both files the same way, and the record's diff shows what the new release derives.
The guest kernel does not provide /dev/kvm: the inventory leaves
CONFIG_VIRTUALIZATION off. Running nested VMs inside the guest is not a
supported workload (#256). This is distinct from running NVX itself on a KVM
host, which remains supported.
The inventory was built from the configuration that CI's kernel build
resolved before it existed, which set 1,131 symbols, 1,055 of them y and
503 with a prompt: it keeps what an NVX component, contract, or workload
needs, and leaves the rest off (#344). The generated configuration sets 769
symbols: 175 inventory settings and 594 derived ones, 53 of which have a
prompt. The checked-in file had also drifted from what olddefconfig
resolved: it omitted 130 symbols that the build set, such as the wireless and
Ethernet vendor menus, and listed four that the build never set, among them
CONFIG_SERIAL_EARLYCON, which no symbol selects without a serial driver, so
the earlycon framework never built patch 0001's console and earlycon=xe9
had no effect. Patch 0001 now registers the xe9 early console from
earlycon=xe9 itself, as an x86 boot console that CONFIG_EARLY_PRINTK
builds, so it needs neither the serial core nor CONFIG_SERIAL_EARLYCON.
| Removed | Why no NVX path needs it |
|---|---|
Hardware that the machine lacks: PC Card, PS/2 keyboard and mouse, the input layer and virtio-input, the virtual terminal and VGA console, the PC speaker, the IOMMU, LEDs and backlight, the VIA RNG and the hardware RNG core, NVRAM, EEPROM, DMA engines, DMI, firmware maps and loading, Macintosh, Surface, and x86 platform drivers, Ethernet and wireless vendor menus, PTP and PPS |
The microVM has only its allowlisted chipset and virtio-mmio devices |
Kexec, crash dumps, /proc/vmcore, /proc/kcore, KASLR (RANDOMIZE_BASE, RANDOMIZE_MEMORY) and the relocatable kernel |
OpenVMM loads vmlinux at its link address and never sets boot_params' KASLR flag, so randomization never ran |
| The crypto API, keyrings, X.509 and PKCS#7 parsers, the security module framework and integrity | No guest component uses them; the kernel's own users, such as module signing, are off |
Filesystems and formats: ISO 9660, autofs, quotas, hugetlbfs, NLS code pages, MBR partitions, EROFS's file backing and LZMA and DEFLATE decompression, FUSE's io_uring and passthrough, binfmt_misc, and every initramfs compression but gzip |
NVX's layers are EROFS (LZ4 or Zstandard) on virtio-blk, raw or GPT, its scratch ext4, its shares virtio-fs, and its initramfses gzip |
| Cgroup controllers and namespaces that the sandbox never enables: CPU, cpuset, cpuacct, devices v1, freezer v1, hugetlb, RDMA, misc, perf, and the I/O controller's policies, and the IPC and time namespaces | The sandbox enables memory and process-count limits and creates mount, PID, and UTS namespaces; workloads have no capabilities to create others |
Swap, the block layer's debugfs files, scheduler statistics, SLUB debugging, memory-init debugging, BIOS-corruption checks, process accounting, relayfs, PM and its debugging, the CPU idle framework, the I/O schedulers, /dev/cpu/*/cpuid |
No guest runs swap or reads them; with no cpuidle driver the kernel idles with HLT either way, and virtio-blk uses no scheduler |
Obsolete interfaces: 16-bit UIDs, sgetmask, 16-bit segments, TIOCSTI for unprivileged users, line-discipline autoloading, writes to mounted block devices |
No supported workload uses them |
| CPU vendors other than Intel and AMD | CPU profiles serve Intel and AMD hosts |
x86 platform options without an effect on the microVM: extended platforms, IRQ rerouting quirks, bus-lock detection, the AMD uncore PMU, ISA DMA and its memory zone, CPU isolation, devtmpfs automounting, the firmware build options, the vhost menu, accelerated RFS, ethtool's netlink API, serial sysrq, runtime tests, the decompressor's verbose boot, and the version suffix from Git |
Their hardware, boot path, or consumer is absent |
The image, the span of guest memory that the kernel's loadable segments cover,
vmlinux, and its main sections, of the kernels that CI's Docker toolchain
built:
| Kernel | Image | vmlinux |
.text |
.rodata |
.data |
.bss |
|---|---|---|---|---|---|---|
| Before the inventory | 20.2 MiB | 25,101,064 B | 9.95 MB | 2.12 MB | 1.32 MB | 0.70 MB |
| The inventory's | 18.1 MiB | 21,593,672 B | 8.64 MB | 1.69 MB | 1.23 MB | 1.52 MB |
An A/B measured the inventory while the kernel still built in KVM for nested
virtualization (#256), which both kernels of the A/B had and which this
configuration no longer has. It ran the e2e benchmark,
nvx.py benchmark --suite e2e --warmups 3 --runs 21 --skip-build, with one
OpenVMM build and one Alpine initramfs, booting the kernel with KVM from before
the inventory and then the inventory's with KVM, in the order before, after,
after, before. A default microVM cold-booted the inventory's kernel 12% to 25%
faster, and its OpenVMM process peaked 7 to 8 MiB lower; its snapshots and
restores took as long as before. KVM alone had cost a default microVM's cold
boot about 3% to 5% and 4.7 MiB of peak RSS, so against the kernel before the
inventory above, which never had KVM, the gain is smaller than these figures;
the A/B has not run again without it:
| Host | Cold boot p50, before | Cold boot p50, after | Peak RSS p50, before and after |
|---|---|---|---|
| Bare-metal KVM, Xeon Silver 4114 | 311.6 and 314.9 ms | 272.9 and 277.2 ms | 86.5 and 79.5 MiB |
| KVM in an Azure VM, Xeon Platinum 8573C | 349.6 and 352.6 ms | 310.2 and 308.9 ms | 85.1 and 78.1 MiB |
| MSHV in an Azure VM, Xeon Platinum 8573C | 251.1 and 253.8 ms | 219.6 and 222.1 ms | 87.7 and 79.7 MiB |
| WHP in an Azure VM, Xeon Platinum 8370C | 520.4 and 528.3 ms | 390.5 and 395.3 ms | 82.5 and 74.5 MiB |
The kernel build checks the configuration that olddefconfig generates
against fixed groups of settings, so a configuration or version change cannot
silently drop a feature that the machine profile or a guest needs. The groups
live in KernelBuildConstants in
build_constants.py, each the
settings and expectations of the inventory features
that KERNEL_CONFIG_CHECKS in
kernel_features.py names, whose
rationales say why NVX needs them.
| Group | Inventory features | Purpose |
|---|---|---|
| Direct boot | direct-boot: ACPI, PVH, and PCI off; MP-table parsing, local APIC, IOAPIC, and virtio-mmio devices from the command line |
Boot through the ACPI-free MP-table loader |
| Virtio console | virtio-console: virtio console and HVC driver over virtio-mmio |
Drive the virtio consoles |
| Sandbox | block-storage, sandbox-filesystems, sandbox-isolation, guest-internal-networking, unix-sockets: virtio-blk with GPT partitions; ext4; EROFS with zstd compression; overlayfs that does not always follow redirects; tmpfs extended attributes; memory, PID, and writeback cgroups; BPF and cgroup BPF; seccomp filters; mount, PID, and UTS namespaces without user namespaces; Unix-domain sockets; network namespaces and veth |
Layer EROFS over ext4 scratch and confine the workload in the sandbox bootstrap |
| Network | network: IPv4 with TCP socket diagnostics; IPv6 without the SIT tunnel driver; virtio-net |
Configure the dual-stack NIC of the portable network profile; SIT would add an sit0 device that decapsulates IPv6 from IPv4 packets |
| Shared interrupt status | shared-interrupt-status: CONFIG_VIRTIO_MMIO_SHARED_STATUS from the kernel patches |
Read shared virtio interrupt status |
| Time ABI | time-abi: hypervisor-guest and paravirtualization support on; Hyper-V, KVM guest, and cpufreq off; tickless idle at 100 Hz with high-resolution timers; the RCU stall timeouts; a 256 KiB log buffer |
Meet the time ABI, as described above |
| Hardening | hardening: strict kernel and module RWX with read-only executable memory, the ITS mitigation, an empty modprobe path, trimmed symbol exports, the boot-time W+X audit, stack protection, and no option built as a module |
Keep runtime code read-only, as described above |
| Watchdogs | Soft-lockup and hung-task detectors off, or on in the CI debug kernel | Keep the detectors out of production kernels |
build-kernel runs every check, and packaging runs them again on the
packaged config. scripts/nvx.py verify is a lighter source check. It checks
the checked-in kernel/config-microvm against the whole inventory, as
check-kernel-config does, and looks for the settings of the
direct-boot, sandbox, network, time-ABI, and hardening groups, plus the xe9
console and virtio-fs. It does not reject options built as modules; only the
build and packaging checks do. When build/vmlinux.config exists, verify
also checks that file for the direct-boot, sandbox, and network groups plus the
xe9 console.
CI proves that snapshot restores never trip the kernel watchdogs with a debug variant of the same kernel:
python3 scripts/nvx.py build-guest --debug-kernel
python3 scripts/nvx.py build-kernel --debug # native, Linux onlyThe variant applies the
kernel/config-microvm-debug fragment on top
of kernel/config-microvm. Each assignment in the fragment replaces the
matching base assignment before olddefconfig. It enables the soft-lockup and
hung-task detectors and extra RCU stall diagnostics. Production already builds
DEBUG_KERNEL, which CONFIG_EXPERT selects, with every debug option that the
inventory does not set off; the fragment repeats DEBUG_KERNEL, keeps off the
debug options that it would otherwise enable by default, and pins the
production RCU stall timeouts, so that it never depends on the inventory for
them. The build fails if the
detectors are missing from the generated config. CI can shorten the soft-lockup
and hung-task thresholds for one boot with watchdog_thresh= and
sysctl.kernel.hung_task_timeout_secs=. The RCU stall timeout stays at the
production value because the time ABI's conformance checks cover the
stall-detector settings
and require it.
The variant builds in build/linux-debug, so it never invalidates the
production object cache, and it produces:
build/vmlinux-debug
build/vmlinux-debug.config
build/vmlinux-debug.provenance.json
Its provenance has the production fields plus a debug_config_fragment path
and SHA-256. The debug kernel is a CI artifact and is never packaged. The
build-guest-artifacts action caches and builds it only when its
components input names debug-kernel.
Release packaging needs the OpenVMM binary, the guest artifacts in the layout
below, and the OpenVMM, kernel, and Alpine initramfs provenance sidecars, so run
build-openvmm and build-guest --guest all first. It also needs the release
build of nvx-rs at nvx-rs/target/release/nvx-rs[.exe], a static
x86_64-unknown-linux-musl executable on Linux and an
x86_64-pc-windows-msvc one on Windows, with the provenance that
record-nvx-rs-provenance writes to
build/nvx-rs.provenance.json. A source-inclusive package
does not need the Azure Linux artifacts. Packaging stages and verifies a
complete output before replacing an existing dist/ version, which requires
--force. Its SOURCE-MANIFEST.json records the package version and exact
hashes for OpenVMM, Linux, the generated kernel config, each packaged guest
initramfs and its package manifest, the Ubuntu distro layer and its manifest,
and the sandbox scratch template, under scratch_template_sha256 in its
ubuntu section. Its nvx_rs section records the nvx-rs executable's
SHA-256, the digest of the nvx-rs sources that built it, and the build's Rust
release, target, and features. Its guest_rust section pins Rust 1.97.1 and
x86_64-unknown-linux-musl, and each guest's guest_sources includes nvx-rs
and rust-toolchain.toml. The OpenVMM section advertises microVM ABI 1, time
ABI 1, restore packet 1, control-session protocol 1, and contract
nvx-microvm-v2-control-v2; product guest-agent metadata is intentionally not
part of this platform manifest.
A binary release directory has this layout:
bin/openvmm[.exe]
bin/nvx-rs[.exe]
guest/vmlinux
guest/vmlinux.config
guest/initramfs.cpio.gz
guest/initramfs.cpio.gz.packages.json
guest/initramfs-ubuntu.cpio.gz
guest/initramfs-ubuntu.cpio.gz.packages.json
guest/ubuntu-distro.erofs
guest/ubuntu-distro.erofs.manifest.json
guest/ubuntu-smoke-scratch.ext4
guest/initramfs-azurelinux.cpio.gz
guest/initramfs-azurelinux.cpio.gz.packages.json
provenance/openvmm.provenance.json
provenance/initramfs.provenance.json
provenance/vmlinux.provenance.json
provenance/nvx-rs.provenance.json
licenses/LICENSE-OPENVMM
licenses/COPYING-LINUX
LICENSE
README.md
SOURCE-MANIFEST.json
THIRD_PARTY_NOTICES.md
SHA256SUMS
Packages built with --include-source additionally contain source/ and omit
the Azure Linux guest artifacts, whose corresponding source is not collected.
The project source archive includes nvx-rs and excludes its generated
target/ tree.
SHA256SUMS has sorted SHA256 relative/path entries using POSIX separators
for every packaged file, including SOURCE-MANIFEST.json, except
SHA256SUMS itself.
The Linux corresponding-source archive contains the patched
linux-6.18.38/ tree and the exact vmlinux.config used for the distributed
kernel.
On a Linux host with the kernel build dependencies installed, run from the archive root:
mkdir build
cp vmlinux.config build/.config
make -C linux-6.18.38 O="$PWD/build" olddefconfig
make -C linux-6.18.38 O="$PWD/build" -j"$(nproc)" vmlinuxThe normal repository workflow performs the same build through
scripts/nvx.py build-kernel or the Docker artifact target.