Creating VMs
Creating VMs
Section titled “Creating VMs”This guide covers how to create new VM types in the Ghaf Framework, from simple extensions to entirely new system VMs.
VM Architecture Overview
Section titled “VM Architecture Overview”VMs in Ghaf use a three-layer composition model:
- Base Module - Core VM configuration (
modules/microvm/sysvms/*-base.nix) - Profile - Platform-specific VM setup (
modules/profiles/*.nix) - Target - Final customization (
targets/*/flake-module.nix)
Creating a New System VM
Section titled “Creating a New System VM”Step 1: Create the Base Module
Section titled “Step 1: Create the Base Module”Create modules/microvm/sysvms/myvm-base.nix:
# SPDX-FileCopyrightText: 2022-2026 TII (SSRC) and the Ghaf contributors## My VM base configuration#{ config, lib, pkgs, globalConfig, hostConfig, inputs, ... }:{ _file = ./myvm-base.nix;
imports = [ # Common VM modules inputs.self.nixosModules.vm-config inputs.self.nixosModules.givc ];
# VM identification networking.hostName = hostConfig.vmName; system.name = hostConfig.vmName;
# Inherit global settings ghaf = { profiles.debug.enable = globalConfig.debug.enable; development = { debug.tools.enable = globalConfig.development.debug.tools.enable; ssh.daemon.enable = globalConfig.development.ssh.daemon.enable; }; givc = { enable = globalConfig.givc.enable; inherit (globalConfig.givc) debug; }; };
# Time zone from global config time.timeZone = lib.mkDefault globalConfig.platform.timeZone;
# VM-specific configuration microvm = { hypervisor = "qemu"; vcpu = 2; mem = 1024;
# Shared directories shares = [ { tag = "ro-store"; source = "/nix/store"; mountPoint = "/nix/.ro-store"; proto = "virtiofs"; } ];
# Network interface interfaces = [{ type = "tap"; id = "vm-myvm"; mac = hostConfig.networking.thisVm.mac; }]; };
# Networking networking.interfaces.ethint0.ipv4.addresses = [{ address = hostConfig.networking.thisVm.ipv4; prefixLength = 24; }];
# Your VM's services services.myService.enable = true;
# Packages environment.systemPackages = with pkgs; [ htop vim ];}Step 2: Create the VM Module
Section titled “Step 2: Create the VM Module”Create modules/microvm/sysvms/myvm.nix:
# SPDX-FileCopyrightText: 2022-2026 TII (SSRC) and the Ghaf contributors{ config, lib, ... }:let cfg = config.ghaf.virtualization.microvm.myvm;in{ _file = ./myvm.nix;
options.ghaf.virtualization.microvm.myvm = { enable = lib.mkEnableOption "My VM";
evaluatedConfig = lib.mkOption { type = lib.types.nullOr lib.types.unspecified; default = null; description = '' Evaluated NixOS configuration for my VM. Set by the profile using lib.nixosSystem. ''; }; };
config = lib.mkIf cfg.enable { assertions = [{ assertion = cfg.evaluatedConfig != null; message = "ghaf.virtualization.microvm.myvm.evaluatedConfig must be set"; }];
microvm.vms.my-vm = { evaluatedConfig = cfg.evaluatedConfig; }; };}Step 3: Export the Modules
Section titled “Step 3: Export the Modules”Add to modules/microvm/flake-module.nix:
{ flake.nixosModules = { # ... existing modules ... myvm = ./sysvms/myvm.nix; myvm-base = ./sysvms/myvm-base.nix; };}Step 4: Add to Profile
Section titled “Step 4: Add to Profile”Update modules/profiles/laptop-x86.nix (or create a new profile):
{ config, lib, pkgs, inputs, ... }:let globalConfig = config.ghaf.global-config;
# Create the VM base with proper specialArgs myvmBase = lib.nixosSystem { specialArgs = lib.ghaf.vm.mkSpecialArgs { inherit lib inputs; globalConfig = globalConfig; hostConfig = lib.ghaf.vm.mkHostConfig { inherit config; vmName = "my-vm"; }; }; modules = [ inputs.self.nixosModules.myvm-base # Add target-specific modules here ]; };
# Apply vmConfig (mem, vcpu, extraModules from hardware.definition and vmConfig) myvmFinal = myvmBase.extendModules { modules = lib.ghaf.vm.applyVmConfig { inherit config; vmName = "myvm"; }; };in{ config = lib.mkMerge [ # ... existing config ...
# Wire up my VM { ghaf.virtualization.microvm.myvm.evaluatedConfig = myvmFinal; } ];}Step 5: Add Networking Configuration
Section titled “Step 5: Add Networking Configuration”Update host networking to include your VM’s address. In your hardware definition or profile:
ghaf.common.extraNetworking.hosts.my-vm = { name = "my-vm"; ipv4 = "192.168.100.10"; mac = "02:00:00:00:00:10";};Creating an Application VM
Section titled “Creating an Application VM”Application VMs use a template pattern for multiple instances.
Using mkAppVm
Section titled “Using mkAppVm”The mkAppVm function creates application VMs. All values (name, ramMb, borderColor, applications, vtpm) are defined in the mkAppVm call and stored in evaluatedConfig.config.ghaf.appvm.vmDef. Host-level options automatically read from there.
{ config, lib, ... }:let mkAppVm = config.ghaf.profiles.laptop-x86.mkAppVm;in{ ghaf.virtualization.microvm.appvm.vms.my-app = mkAppVm { name = "my-app"; applications = [{ name = "My Application"; description = "Description of my app"; packages = [ pkgs.my-app ]; icon = "my-app-icon"; command = "my-app --flag"; }]; extraModules = [ # Additional configuration { services.foo.enable = true; } ]; };}Extending Existing AppVMs
Section titled “Extending Existing AppVMs”Use the extensions option to add modules to an existing app VM without modifying its base definition. Extensions are applied via NixOS extendModules:
{ pkgs, ... }:{ # Add an app to the chrome VM without modifying its base ghaf.virtualization.microvm.appvm.vms.chrome.extensions = [ ({ pkgs, ... }: { ghaf.appvm.applications = [{ name = "Getting Started"; description = "Introduction guide"; packages = [ pkgs.ghaf-intro ]; command = "ghaf-intro"; }]; }) ];}AppVM Options
Section titled “AppVM Options”| Option | Type | Description |
|---|---|---|
enable | bool | Enable this VM |
evaluatedConfig | NixOS system | Base config from mkAppVm |
extensions | list of modules | Additional modules applied via extendModules |
extraNetworking | attrset | Host-side networking options |
usbPassthrough | list | USB passthrough rules (host-side) |
bootPriority | enum | Boot priority: “low”, “medium”, “high” |
Values like name, ramMb, borderColor, applications, and vtpm are all derived from evaluatedConfig.config.ghaf.appvm.vmDef and should be set in the mkAppVm call, not at the host level.
Application Definition
Section titled “Application Definition”Each application in the applications list:
{ name = "Firefox"; description = "Web browser"; packages = [ pkgs.firefox ]; icon = "firefox"; command = "firefox"; args = [ "--new-window" ];}Extending Existing VMs
Section titled “Extending Existing VMs”Using extendModules
Section titled “Using extendModules”Extend a VM base without modifying it:
{ config, ... }:let baseGuivm = config.ghaf.profiles.laptop-x86.guivmBase;
extendedGuivm = baseGuivm.extendModules { modules = [ # Add your customizations ({ config, ... }: { services.myService.enable = true; environment.systemPackages = [ pkgs.my-tool ]; }) ]; };in{ # Pass the full system result, not .config ghaf.virtualization.microvm.guivm.evaluatedConfig = extendedGuivm;}Adding Feature Modules
Section titled “Adding Feature Modules”For features that might be reused, create a feature module:
{ config, lib, globalConfig, ... }:let enabled = lib.ghaf.features.isEnabledFor globalConfig "myFeature" "gui-vm";in{ _file = ./my-feature.nix;
config = lib.mkIf enabled { services.myFeature.enable = true; };}Then include it in the base or via extendModules.
Hardware Passthrough
Section titled “Hardware Passthrough”PCI Device Passthrough
Section titled “PCI Device Passthrough”For VMs that need direct hardware access:
# In hardware definitionhardware.definition.myvm.pciPassthrough = [ { path = "0000:00:1f.3"; } # Audio device];
# In VM basemicrovm.devices = lib.optionals (hostConfig.pciPassthrough or []) ( map (dev: { bus = "pci"; inherit (dev) path; }) hostConfig.pciPassthrough);USB Device Passthrough
Section titled “USB Device Passthrough”microvm.devices = [ { bus = "usb"; vendorId = "1234"; productId = "5678"; }];VM Resource Configuration
Section titled “VM Resource Configuration”Using vmConfig
Section titled “Using vmConfig”The vmConfig parameter in mkGhafConfiguration allows per-target resource customization:
# In target flake-module.nixmkGhafConfiguration { # ... other params ... vmConfig = { guivm = { mem = 4096; vcpu = 4; }; netvm = { mem = 512; vcpu = 1; }; myvm = { mem = 2048; vcpu = 2; }; };}This maps to ghaf.virtualization.vmConfig and is applied via lib.ghaf.vm.applyVmConfig, which generates a resource module setting microvm.mem and microvm.vcpu.
Debugging VMs
Section titled “Debugging VMs”Build VM Independently
Section titled “Build VM Independently”# Evaluate VM confignix eval .#nixosConfigurations.target-name.config.microvm.vms.my-vm.config.networking.hostName
# Build VM systemnix build .#nixosConfigurations.target-name.config.microvm.vms.my-vm.config.system.build.toplevelCheck VM Configuration
Section titled “Check VM Configuration”# Show all VM optionsnix eval .#nixosConfigurations.target-name.config.ghaf.virtualization.microvm --json | jqSerial Console Access
Section titled “Serial Console Access”VMs can be accessed via serial console when debugging:
microvm.console = "console";boot.kernelParams = [ "console=ttyS0" ];Checklist for New VMs
Section titled “Checklist for New VMs”- Create base module (
*-base.nix) with_filedeclaration - Create VM module (
*.nix) with enable option and evaluatedConfig - Export modules in
flake-module.nix - Add VM base creation to profile
- Configure networking (IP, MAC)
- Add to host’s microvm.vms
- Test build and boot
- Document the VM’s purpose
See Also
Section titled “See Also”- VM Composition - Architecture details
- Config Propagation - globalConfig/hostConfig
- Writing Modules - Module conventions
- Extending Targets - Target customization