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
FieldTypeRequiredDefaultDescription
subnetstringYesCIDR notation, e.g. "172.27.0.0/24".
nat_enabledboolNotrueEnable NAT for internet access.
ipv4_gatewaystringNoauto-computedGateway IP.
nat_gateways[]stringNoauto-detectedHost interfaces for NAT.
defaultboolNofalseSet as default network.

Key

Generate or import an SSH key pair.

Terminal

key:
  main-key:
    algorithm: ed25519
    default: true
FieldTypeRequiredDefaultDescription
algorithmstringNoed25519Key algorithm. Valid: ed25519, rsa, ecdsa.
bitsintNo0 (auto)Key bits (for RSA).
commentstringNo"{name}@{hostname}"Key comment.
forceboolNofalseOverwrite existing key files.
defaultboolNofalseSet as default key.

Image

Download an OS image.

Terminal

image:
  os-image:
    type: alpine
    version: "3.23"
FieldTypeRequiredDefaultDescription
typestringYesImage type/slug, e.g. ubuntu, alpine.
versionstringNolatestVersion tag, e.g. 24.04.
forceboolNofalseForce re-pull even if exists.
defaultboolNofalseSet as default image.
partitionintNo0 (auto)Partition index.
skip_optimizationboolNofalseSkip image optimization.
disabled_detectors[]stringNo[]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"
FieldTypeRequiredDefaultDescription
sourcestringYesFile path, or "@vm:<name>" to import a VM's rootfs.
formatstringNoautoFormat override: raw, qcow2, tar.
versionstringNoVersion tag for the imported image.
forceboolNofalseOverwrite existing image.
defaultboolNofalseSet as default image.
skip_optimizationboolNofalseSkip filesystem optimization.
disabled_detectors[]stringNo[]Disable specific detectors.

Kernel

Download or build a kernel.

Terminal

kernel:
  fc-kernel:
    type: firecracker
FieldTypeRequiredDefaultDescription
typestringYesKernel type: firecracker (pre-built) or official (build from source).
versionstringNolatestVersion tag.
jobsintNoCPU countBuild parallelism (official only).
keep_build_dirboolNofalseKeep build directory.
clean_buildboolNofalseForce clean build.
kernel_configstringNoPath to custom kernel config file.
defaultboolNofalseSet as default kernel.
featuresstringNoComma-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
FieldTypeRequiredDefaultDescription
typestringNofirecrackerBinary type.
versionstringYesVersion tag, e.g. 1.16.0.
git_refstringNoBuild from git ref instead of downloading.
defaultboolNofalseSet as default binary.
forceboolNofalseForce re-download.

VM

Create a virtual machine.

FieldTypeRequiredDefaultDescription
networkstringNoDefault networkNetwork step reference.
keystringNoDefault keySSH key step reference (shorthand for ssh_keys).
ssh_keys[]stringNo[]List of SSH key step references.
imagestringNoDefault imageImage step reference.
kernelstringNoDefault kernelKernel step reference.
binarystringNoDefault binaryBinary step reference.
vcpuintNoconfigvCPU count. Range: 1-32.
memstringNoconfigMemory size (512M, 1G, or bare MiB).
disk_sizestringNoconfigDisk size, e.g. 10G.
userstringNoconfigSSH user.
pci_enabledboolNoconfigEnable PCI.
nested_virtboolNoconfigEnable nested virt (requires PCI).
cpu_templatestringNoPath to CPU template JSON.
console_enableboolNoconfigEnable serial console.
logging_enableboolNoconfigEnable logging.
metrics_enableboolNoconfigEnable metrics.
guest_ipstringNoRequest specific guest IP.
guest_macstringNoRequest specific MAC.
boot_argsstringNoconfigCustom kernel boot args.
volumes[]stringNo[]Volume step references to attach.
countintNo1Batch count.
atomicboolNofalseAtomic batch (all or nothing).
skip_cleanupboolNofalseSkip cleanup on failure.
skip_deblobboolNofalseSkip image deblobbing.
vsock_portintNoconfigVsock port. Default: 1024.
writebackboolNoconfigWriteback 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"
FieldTypeRequiredDefaultDescription
targetstringYesVM step reference (bare name or "@vm:<name>").
cmdstringYesCommand to execute. Wrapped in sh -c.
userstringNoconfigUser to run the command as.
timeoutintNo0Command timeout in seconds. 0 = no timeout.
portintNo0Vsock agent port override. 0 = default.
envmapNo{}Environment variable overrides for the command.
ignore_errorsboolNofalseContinue 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.

FieldTypeRequiredDefaultDescription
targetstringYesVM step reference (bare name or "@vm:<name>").
userstringNoconfigSSH user.
keystringNoconfigKey name or file path.
cmdstringNoCommand to execute.
timeoutintNo0Connection timeout in seconds.
envmapNo{}Environment variable overrides.
ignore_errorsboolNofalseContinue 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.

FieldTypeRequiredDefaultDescription
srcstringYesSource path(s). Single string or array.
deststringYesDestination in vm-name:/remote/path format.
forceboolNofalseForce 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 TypeBehavior on Destroy
networkDeleted — NAT rules, bridge/tap, DB record removed
keyDeleted — key files removed, DB record removed
vmDeleted — Firecracker killed, TAP removed, lease released, volumes detached, DB record deleted
imagePreserved — cached asset, shared across environments
image_importPreserved — image file stays in cache, DB record kept
kernelPreserved — cached asset, shared across environments
binaryPreserved — cached asset, shared across environments
sshNo-op — ephemeral side-effect
execNo-op — ephemeral side-effect
copyNo-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 --default

Package 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..."