VMs — lifecycle¶
A VirtualBox VM is a record in VBox's registry plus the disk files it references. This page covers the lifecycle: create, configure, run, control, destroy.
Create¶
--platform-architecture is x86 (default) or arm. Set at create time only — can't be changed by modifyvm later. On Apple Silicon, arm is mandatory.
--ostype is a hint VBox uses for hardware defaults (recommended graphics controller, USB controllers, etc.). Pick the closest match:
VBoxManage list ostypes | grep -A1 -i ubuntu
VBoxManage list --platform-arch=arm ostypes | grep -A1 -i ubuntu
Without --register, the VM exists but isn't in the VM list. Almost always you want --register.
The result of createvm is an empty VM: - No memory configured - No CPUs configured - No disks - No storage controllers - Default NAT NIC (usually fine)
You can't startvm until you've at least added some disks and possibly some storage controllers.
Configure¶
modifyvm is the workhorse. Most invocations look like:
VBoxManage modifyvm test \
--memory 8192 \ # MiB of RAM
--cpus 4 \ # number of vCPUs
--vram 32 \ # video RAM in MiB
--firmware efi64 \ # x86 only: bios | efi | efi32 | efi64
--nic1 nat \ # primary NIC
--rtcuseutc on \ # RTC stored as UTC (Linux convention)
--audio-driver none \ # no audio
--boot1 disk --boot2 dvd \ # boot priority
--boot3 none --boot4 none \
--usbohci off --usbehci off --usbxhci off # x86 only
It's idempotent — re-running with the same args is a no-op. Use this property in scripts: "make sure the VM has these settings" rather than "create only if absent".
What each flag does¶
| Flag | What it does | Defaults / notes |
|---|---|---|
--memory N | Sets RAM in MiB | Required before first start. Subtract from host RAM at start. |
--cpus N | vCPU count | More than physical cores → degraded perf, not impossible. |
--vram N | Video RAM in MiB | 32 is plenty for headless. |
--firmware bios/efi/efi32/efi64 | x86 only. EFI required for some modern guest installers. | efi64 is right for Ubuntu 22+. |
--graphicscontroller | none, vboxvga, vmsvga, vmsvga, qemuramfb | ARM must use qemuramfb. x86 default vmsvga. |
--chipset | piix3, ich9, armv8virtual | ARM forced to armv8virtual. Use ich9 for modern x86. |
--nic1 ... --nic8 | Per-NIC type: nat, bridged, hostonly, intnet, natnetwork, null, none | Up to 8 NICs. |
--natpf1 | Add a NAT port-forward to nic1 | "name,proto,host-ip,host-port,guest-ip,guest-port" |
--rtcuseutc on | Stores guest RTC as UTC, not local time | Linux convention; default on Linux ostypes anyway. |
--audio-driver none | Disable audio | Defaults to coreaudio/pulse/alsa; not needed for headless servers. |
--usbohci/--usbehci/--usbxhci on/off | x86 USB controllers | Turn off on headless lab VMs to reduce attack surface and noise. |
--boot1..--boot4 disk/dvd/net/none | Boot priority order | First-found wins. |
--cpuid-set/--cpuid-remove | Hide CPU features from the guest | Rarely needed. |
--paravirt-provider | default, kvm, hyperv, legacy, minimal, none | Auto-picks. |
--hwvirtex on/off | Hardware virt | Default on; off forces software emulation (very slow). |
--vrde on/off | Enable VRDE remote console | See Headless operation. |
For Apple Silicon ARM VMs, several flags don't apply and others change meaning — see Apple Silicon.
Network NIC types¶
--nic1 nat is the default and is what we use in the lab. Other modes:
# Bridged — joins the host's LAN as if it were a physical machine
VBoxManage modifyvm test \
--nic1 bridged \
--bridgeadapter1 "en0: Wi-Fi (AirPort)" # macOS interface name
# Host-only — isolated network on a virtual subnet, host can reach it
VBoxManage hostonlyif create # creates vboxnet0
VBoxManage modifyvm test --nic1 hostonly --hostonlyadapter1 vboxnet0
# Internal — multiple VMs talk to each other on a named virtual network
VBoxManage modifyvm test --nic1 intnet --intnet1 my-private-net
# NAT Network — like NAT but shared across multiple VMs (uncommon)
VBoxManage natnetwork add --netname my-natnet --network 10.10.0.0/24 --enable
VBoxManage modifyvm test --nic1 natnetwork --nat-network1 my-natnet
For the lab: stay with NAT + host port-forwarding. Bridged adds LAN router complications you don't need.
Storage doesn't go through modifyvm¶
Disks and ISOs are attached via storageattach (after a storagectl adds a controller). See Storage.
Start¶
VBoxManage startvm test # open a window (GUI default)
VBoxManage startvm test --type gui # explicit GUI
VBoxManage startvm test --type separate # window + the VM process is detached
VBoxManage startvm test --type headless # no window, runs in background
VBoxManage startvm test --type sdl # SDL frontend (lightweight, no Qt)
headless is what you want for scripts and servers. The VM still has a framebuffer (you can screenshotpng it) but no window opens and no input device is attached.
startvm returns as soon as the VM begins booting — it doesn't wait for the OS to be up. To know when the guest is reachable, poll the SSH port (see src/msai_setup/lab/ssh.py:wait_for_ssh for the pattern).
Control (while running)¶
VBoxManage controlvm test pause # freeze
VBoxManage controlvm test resume
VBoxManage controlvm test acpipowerbutton # graceful shutdown
VBoxManage controlvm test acpisleepbutton # suspend
VBoxManage controlvm test reset # hard reset (Ctrl+Alt+Del)
VBoxManage controlvm test poweroff # pull the plug
VBoxManage controlvm test savestate # suspend to disk
VBoxManage controlvm test screenshotpng /tmp/test.png # capture framebuffer
VBoxManage controlvm test keyboardputstring "ls\n" # type into the guest
VBoxManage controlvm test keyboardputscancode 1c # raw scancode (Enter)
VBoxManage controlvm test mouse moveabs 100 100 0 0 0 # move mouse pointer
acpipowerbutton vs poweroff:
acpipowerbuttonsends the ACPI signal that pressing the power button on a physical PC would send. Modern guests (systemd-logind) handle this and do a clean shutdown.poweroffis "yank the cord". Use only if the guest is unresponsive — risk of filesystem corruption.
savestate is interesting: it suspends the VM state to a file, frees the host RAM, and startvm resumes from exactly where you left off (full state, not a snapshot). Different from snapshots in that it's just a single suspend point with no branching.
Inspect¶
Useful queries:
# What's the VM's state?
VBoxManage showvminfo test --machinereadable | awk -F= '/^VMState=/{print $2}'
# "running", "poweroff", "paused", "saved", etc.
# How many CPUs?
VBoxManage showvminfo test --machinereadable | awk -F= '/^cpus=/{print $2}'
# Which storage controllers?
VBoxManage showvminfo test --machinereadable | grep '^storagecontrollername'
# Forwarding rules?
VBoxManage showvminfo test --machinereadable | grep '^Forwarding'
For programmatic use, parse --machinereadable — it's stable and quoted-where-needed.
Modify a running VM¶
Some properties can change while running, others can't:
| Change | Requires VM stopped? |
|---|---|
Add a NAT port forward (--natpf1) | No |
| Change RAM | Yes |
| Change CPU count | Yes |
Hot-plug a disk (storageattach --hotpluggable on) | No |
| Change firmware | Yes |
| Change graphics controller | Yes |
| Add a snapshot | No |
If you try a stop-required change on a running VM, VBoxManage errors with VBOX_E_INVALID_VM_STATE. Stop the VM first, retry.
Destroy¶
# Stop if running
VBoxManage controlvm test poweroff 2>/dev/null || true
sleep 1
# Unregister + delete files
VBoxManage unregistervm test --delete
# Or unregister without deleting (useful for moving VMs)
VBoxManage unregistervm test
--delete removes the .vbox config, snapshots, and any disks the VM considers owned. Disks created independently and attached via path may not be considered owned — they stay on disk after --delete. To be sure they're gone, closemedium disk /path --delete per disk first.
Lifecycle in scripts¶
Idempotent pattern:
NAME=test
# Stop + delete if exists
if VBoxManage list vms | grep -q "\"$NAME\""; then
VBoxManage controlvm "$NAME" poweroff 2>/dev/null || true
sleep 1
VBoxManage unregistervm "$NAME" --delete
fi
# Recreate
VBoxManage createvm --name "$NAME" --ostype Ubuntu_64 --register
VBoxManage modifyvm "$NAME" --memory 4096 --cpus 2 --firmware efi64 --nic1 nat
VBoxManage createmedium disk --filename "$NAME.vdi" --size 20000
VBoxManage storagectl "$NAME" --name SATA --add sata
VBoxManage storageattach "$NAME" --storagectl SATA --port 0 --device 0 \
--type hdd --medium "$NAME.vdi"
VBoxManage startvm "$NAME" --type headless
For real automation use the Python wrapper in src/msai_setup/lab/vbox.py — same idea, with proper error handling and idempotency at the function level.
Where to go next¶
- Storage — disks, controllers, ISO attachment
- Networking — NAT, bridge, port forwarding in depth
- Snapshots — branching state safely
- Headless — startvm headless + remote console