mvmctl env spec
Environment Spec Reference
Overview
An env spec is a YAML file that describes an environment — networks, SSH keys, images, kernels, binaries, VMs, and the commands that configure them. Running mvm env apply spec.yaml provisions everything in dependency order. The format uses typed top-level sections (one per step type) with map keys as step names.
Syntax
Terminal
version: "1"
ephemeral: false # auto-destroy after apply (success or failure)
network:
<name>: # map key = step name
subnet: ... # step-specific fields
key:
<name>:
algorithm: ...Network
Create a network for VMs to connect to.
Terminal
network:
default:
subnet: "172.27.0.0/24"
nat_enabled: true
default: true| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| subnet | string | Yes | — | CIDR notation, e.g. "172.27.0.0/24". |
| nat_enabled | bool | No | true | Enable NAT for internet access. |
| ipv4_gateway | string | No | auto-computed | Gateway IP. |
| nat_gateways | []string | No | auto-detected | Host interfaces for NAT. |
| default | bool | No | false | Set as default network. |
Key
Generate or import an SSH key pair.
Terminal
key:
main-key:
algorithm: ed25519
default: true| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| algorithm | string | No | ed25519 | Key algorithm. Valid: ed25519, rsa, ecdsa. |
| bits | int | No | 0 (auto) | Key bits (for RSA). |
| comment | string | No | "{name}@{hostname}" | Key comment. |
| force | bool | No | false | Overwrite existing key files. |
| default | bool | No | false | Set as default key. |
Image
Download an OS image.
Terminal
image:
os-image:
type: alpine
version: "3.23"| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| type | string | Yes | — | Image type/slug, e.g. ubuntu, alpine. |
| version | string | No | latest | Version tag, e.g. 24.04. |
| force | bool | No | false | Force re-pull even if exists. |
| default | bool | No | false | Set as default image. |
| partition | int | No | 0 (auto) | Partition index. |
| skip_optimization | bool | No | false | Skip image optimization. |
| disabled_detectors | []string | No | [] | Disable detection methods. |
Image Import
Import a local image file or copy a VM's rootfs.
Terminal
image_import:
capture-base:
source: "@vm:builder"
removes:
- "@vm:builder"| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| source | string | Yes | — | File path, or "@vm:<name>" to import a VM's rootfs. |
| format | string | No | auto | Format override: raw, qcow2, tar. |
| version | string | No | Version tag for the imported image. | |
| force | bool | No | false | Overwrite existing image. |
| default | bool | No | false | Set as default image. |
| skip_optimization | bool | No | false | Skip filesystem optimization. |
| disabled_detectors | []string | No | [] | Disable specific detectors. |
Kernel
Download or build a kernel.
Terminal
kernel:
fc-kernel:
type: firecracker| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| type | string | Yes | — | Kernel type: firecracker (pre-built) or official (build from source). |
| version | string | No | latest | Version tag. |
| jobs | int | No | CPU count | Build parallelism (official only). |
| keep_build_dir | bool | No | false | Keep build directory. |
| clean_build | bool | No | false | Force clean build. |
| kernel_config | string | No | Path to custom kernel config file. | |
| default | bool | No | false | Set as default kernel. |
| features | string | No | Comma-separated features, e.g. kvm,nftables. Use all or * to enable all features. |
Binary
Download Firecracker binaries.
Terminal
binary:
fc-bin:
version: "1.16.0"
default: true| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| type | string | No | firecracker | Binary type. |
| version | string | Yes | — | Version tag, e.g. 1.16.0. |
| git_ref | string | No | Build from git ref instead of downloading. | |
| default | bool | No | false | Set as default binary. |
| force | bool | No | false | Force re-download. |
VM
Create a virtual machine.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| network | string | No | Default network | Network step reference. |
| key | string | No | Default key | SSH key step reference (shorthand for ssh_keys). |
| ssh_keys | []string | No | [] | List of SSH key step references. |
| image | string | No | Default image | Image step reference. |
| kernel | string | No | Default kernel | Kernel step reference. |
| binary | string | No | Default binary | Binary step reference. |
| vcpu | int | No | config | vCPU count. Range: 1-32. |
| mem | string | No | config | Memory size (512M, 1G, or bare MiB). |
| disk_size | string | No | config | Disk size, e.g. 10G. |
| user | string | No | config | SSH user. |
| pci_enabled | bool | No | config | Enable PCI. |
| nested_virt | bool | No | config | Enable nested virt (requires PCI). |
| cpu_template | string | No | Path to CPU template JSON. | |
| console_enable | bool | No | config | Enable serial console. |
| logging_enable | bool | No | config | Enable logging. |
| metrics_enable | bool | No | config | Enable metrics. |
| guest_ip | string | No | Request specific guest IP. | |
| guest_mac | string | No | Request specific MAC. | |
| boot_args | string | No | config | Custom kernel boot args. |
| volumes | []string | No | [] | Volume step references to attach. |
| count | int | No | 1 | Batch count. |
| atomic | bool | No | false | Atomic batch (all or nothing). |
| skip_cleanup | bool | No | false | Skip cleanup on failure. |
| skip_deblob | bool | No | false | Skip image deblobbing. |
| vsock_port | int | No | config | Vsock port. Default: 1024. |
| writeback | bool | No | config | Writeback cache mode for drives. |
Exec
Run a command inside a VM via the vsock guest agent. Imperative — always re-runs on re-apply.
Terminal
exec:
bootstrap:
target: dev-vm
cmd: "./deploy.sh"
user: root
timeout: 30
depends_on:
- "@vm:dev-vm"| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| target | string | Yes | — | VM step reference (bare name or "@vm:<name>"). |
| cmd | string | Yes | — | Command to execute. Wrapped in sh -c. |
| user | string | No | config | User to run the command as. |
| timeout | int | No | 0 | Command timeout in seconds. 0 = no timeout. |
| port | int | No | 0 | Vsock agent port override. 0 = default. |
| env | map | No | {} | Environment variable overrides for the command. |
| ignore_errors | bool | No | false | Continue workflow if the command exits with non-zero code. |
SSH
Run a command on a VM via SSH. Imperative — always re-runs on re-apply.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| target | string | Yes | — | VM step reference (bare name or "@vm:<name>"). |
| user | string | No | config | SSH user. |
| key | string | No | config | Key name or file path. |
| cmd | string | No | Command to execute. | |
| timeout | int | No | 0 | Connection timeout in seconds. |
| env | map | No | {} | Environment variable overrides. |
| ignore_errors | bool | No | false | Continue workflow if the command exits with non-zero code. |
Copy
Copy files between host and VM via vsock binary frame protocol. Imperative — always re-runs on re-apply.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| src | string | Yes | — | Source path(s). Single string or array. |
| dest | string | Yes | — | Destination in vm-name:/remote/path format. |
| force | bool | No | false | Force overwrite existing files. |
References
Cross-resource references use the @type:name format. The @ prefix distinguishes references from literal string values. The type prefix (network, key, image, etc.) disambiguates steps with the same name under different types.
Terminal
depends_on:
- "@network:default"
- "@key:main-key"
- "@image:os-image"
vm:
dev-vm:
network: "@network:default"
key: "@key:main-key"
image: "@image:os-image"Destroy Behavior
| Step Type | Behavior on Destroy |
|---|---|
| network | Deleted — NAT rules, bridge/tap, DB record removed |
| key | Deleted — key files removed, DB record removed |
| vm | Deleted — Firecracker killed, TAP removed, lease released, volumes detached, DB record deleted |
| image | Preserved — cached asset, shared across environments |
| image_import | Preserved — image file stays in cache, DB record kept |
| kernel | Preserved — cached asset, shared across environments |
| binary | Preserved — cached asset, shared across environments |
| ssh | No-op — ephemeral side-effect |
| exec | No-op — ephemeral side-effect |
| copy | No-op — ephemeral side-effect |
Full Example
Terminal
version: "1"
network:
default:
subnet: "172.27.0.0/24"
nat_enabled: true
default: true
key:
main-key:
algorithm: ed25519
default: true
image:
os-image:
type: alpine
version: "3.23"
kernel:
fc-kernel:
type: firecracker
binary:
fc-bin:
version: "1.16.0"
default: true
vm:
dev-vm:
network: "@network:default"
key: "@key:main-key"
image: "@image:os-image"
kernel: "@kernel:fc-kernel"
binary: "@binary:fc-bin"
vcpu: 2
mem: 2048
disk_size: 10G
depends_on:
- "@network:default"
- "@key:main-key"
- "@image:os-image"
- "@kernel:fc-kernel"
- "@binary:fc-bin"
exec:
bootstrap:
target: dev-vm
cmd: "curl -sS https://example.com/bootstrap.sh | sh"
depends_on:
- "@vm:dev-vm"Real-World Examples
Production env specs from the mvmctl project itself. These demonstrate real usage patterns including multi-step copy pipelines, nested virtualization, and complex dependency ordering.
Release Candidate Test Environment (rc-env.yaml) — old format
Terminal
version: "1"
network:
rc-net:
subnet: 10.8.0.0/24
nat_enabled: true
default: true
key:
rc-key:
algorithm: ed25519
default: true
force: true
image:
ubuntu-noble:
type: ubuntu
version: noble
default: true
binary:
fc-16:
type: firecracker
version: 1.16.0
default: true
kernel:
rc-vmlinux:
type: official
version: 7.0.11
default: true
features: kvm,nftables,tuntap
vm:
rc-vm:
depends_on:
- "@network:rc-net"
- "@key:rc-key"
- "@image:ubuntu-noble"
- "@binary:fc-16"
- "@kernel:rc-vmlinux"
disk_size: 15G
vcpu: 6
mem: 4G
nested_virt: true
user: runner
network: "@network:rc-net"
image: "@image:ubuntu-noble"
kernel: "@kernel:rc-vmlinux"
binary: "@binary:fc-16"
copy:
copy-mvm:
depends_on: ["@vm:rc-vm"]
src: ./dist/mvm
dest: rc-vm:/root/
copy-firecracker:
depends_on: ["@vm:rc-vm"]
src: ~/.cache/mvm-asset-mirror/firecracker-v1.16.0-x86_64.tgz
dest: rc-vm:/mnt/
copy-kernel:
depends_on: ["@vm:rc-vm"]
src: ~/.cache/mvm-asset-mirror/kernel-cache-7.0.11-eddb0bcef9946d7d.vmlinux
dest: rc-vm:/mnt/
copy-tests:
depends_on: ["@vm:rc-vm"]
src: ./tests/
dest: rc-vm:/home/runner/tests/
exec:
install-packages:
depends_on: ["@vm:rc-vm"]
target: rc-vm
user: root
timeout: 120
cmd: |
apt-get update && apt-get install -y qemu-utils python3-pip \
docker.io cloud-image-utils build-essential &&
pip3 install pytest pytest-timeout &&
groupadd --force mvm && usermod -aG mvm runner
install-mvm:
depends_on: ["@vm:rc-vm", "@copy:copy-mvm"]
target: rc-vm
user: root
timeout: 60
cmd: |
cp /root/mvm /usr/bin/mvm
import-assets:
depends_on: ["@vm:rc-vm", "@exec:install-mvm"]
target: rc-vm
user: root
timeout: 300
env:
MVM_ASSET_MIRROR: /mnt
cmd: |
mvm init --non-interactive --skip-host
mvm kernel pull --type firecracker --default
mvm image pull ubuntu:noble
mvm bin pull firecracker --version 1.16.0 --defaultPackage Builder Environment (packaging/builder.yaml)
Terminal
version: "1"
network:
builder:
subnet: "172.30.0.0/24"
nat: true
key:
builder:
algorithm: ed25519
force: true
image:
ubuntu-builder:
type: ubuntu
version: "24.04"
kernel:
krnl-builder:
type: official
version: 7.0.11
features: nftables,tuntap,kvm,btrfs
binary:
fc-binary:
type: firecracker
version: "1.16.0"
vm:
pkg-builder:
network: "@network:builder"
key: "@key:builder"
image: "@image:ubuntu-builder"
kernel: "@kernel:krnl-builder"
binary: "@binary:fc-binary"
disk_size: 8G
vcpu: 4
mem: 3G
depends_on:
- "@network:builder"
- "@key:builder"
- "@image:ubuntu-builder"
- "@kernel:krnl-builder"
- "@binary:fc-binary"
copy:
copy-scripts:
src: ./scripts
dest: pkg-builder:/mnt/mvmctl/
force: true
depends_on:
- "@vm:pkg-builder"
copy-packaging:
src: ./packaging
dest: pkg-builder:/mnt/mvmctl/
force: true
depends_on:
- "@vm:pkg-builder"
copy-source:
src: ./internal
dest: pkg-builder:/mnt/mvmctl/
force: true
depends_on:
- "@vm:pkg-builder"
copy-pkg:
src: ./pkg
dest: pkg-builder:/mnt/mvmctl/
force: true
depends_on:
- "@vm:pkg-builder"
copy-cmd:
src: ./cmd
dest: pkg-builder:/mnt/mvmctl/
force: true
depends_on:
- "@vm:pkg-builder"
copy-gomod:
src: ./go.mod
dest: pkg-builder:/mnt/mvmctl/go.mod
force: true
depends_on:
- "@vm:pkg-builder"
copy-gosum:
src: ./go.sum
dest: pkg-builder:/mnt/mvmctl/go.sum
force: true
depends_on:
- "@vm:pkg-builder"
exec:
build-all:
target: pkg-builder
user: root
timeout: 30
cmd: |
export DEBIAN_FRONTEND=noninteractive &&
apt-get update -qq &&
apt-get install -y -qq debhelper build-essential rpm &&
cd /mnt/mvmctl &&
./scripts/build-packages.sh --build-binaries --version ${VERSION}
copy:
retrieve-all:
src: pkg-builder:/mnt/mvmctl/dist/packages/
dest: ./dist/packages
force: true
depends_on:
- "@exec:build-all"State File
After mvm env apply, state is persisted to ~/.cache/mvmctl/workflows/<wf-id>/state.yaml.
Terminal
workflow_id: "ec729934a8fb9c67"
spec_path: "./my-env.yaml"
schema_version: "1.0"
resources:
- name: "network:default"
type: "network"
depends_on: []
state:
spec:
subnet: "172.27.0.0/24"
output:
network_id: "net-abc123"
meta:
was_created: true
spec_hash: "a1b2c3..."