Build a Lightweight Ubuntu VM on macOS with Virtualization Kit and vftool
This guide walks through using macOS Big Sur's Virtualization Kit together with the open‑source vftool to compile, sign, and run a minimal Ubuntu virtual machine, covering image preparation, network configuration, launchctl auto‑start, and essential post‑creation tweaks.
This article explains how to use macOS Big Sur's Virtualization Kit and the open‑source vftool to create and manage a lightweight Ubuntu virtual machine.
1. Compile vftool
vftool is written in Swift, so you need the Xcode command‑line tools. Install them with: $ xcode-select --install Or download Xcode from the Apple developer site.
After installing the tools, clone and compile the repository:
$ git clone https://github.com/evansm7/vftool.git
$ clang -framework Foundation -framework Virtualization vftool/vftool/main.m -o /usr/local/bin/vftoolIf you encounter the error "Virtualization requires the \"com.apple.security.virtualization\" entitlement", you must create a self‑signed code‑signing certificate in Keychain Access → Certificate Assistant → Create a Certificate, choose "Code Signing" as the type, and give it any name.
Sign the compiled binary with the new certificate:
$ codesign --entitlements vftool/vftool/vftool.entitlements -s "<NAME ON CERTIFICATE>" /usr/local/bin/vftoolAlternatively, you can build with Xcode directly:
$ xcodebuild
$ cp build/Release/vftool /usr/local/bin/vftool2. Prepare image files
Download the kernel, initrd and disk image for Ubuntu 20.04:
kernel: https://cloud-images.ubuntu.com/releases/focal/release/unpacked/ubuntu-20.04-server-cloudimg-amd64-vmlinuz-generic
initrd: https://cloud-images.ubuntu.com/releases/focal/release/unpacked/ubuntu-20.04-server-cloudimg-amd64-initrd-generic
disk image: https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-amd64.tar.gz
Download and rename them:
$ mkdir -p ~/bin/vm
$ cd ~/bin/vm
$ wget https://cloud-images.ubuntu.com/releases/focal/release/unpacked/ubuntu-20.04-server-cloudimg-amd64-vmlinuz-generic
$ wget https://cloud-images.ubuntu.com/releases/focal/release/unpacked/ubuntu-20.04-server-cloudimg-amd64-initrd-generic
$ wget https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-amd64.tar.gz
$ mv ubuntu-20.04-server-cloudimg-amd64-vmlinuz-generic vmlinux
$ mv ubuntu-20.04-server-cloudimg-amd64-initrd-generic initrd
$ tar xvfz ubuntu-20.04-server-cloudimg-amd64.tar.gzCreate a data disk:
$ dd if=/dev/zero of=data.img bs=1m count=512003. Adjust VM network (optional)
To change the VM's network subnet, edit
/Library/Preferences/SystemConfiguration/com.apple.vmnet.plistand modify the Shared_Net_Address and Shared_Net_Mask keys, e.g.:
<key>Shared_Net_Address</key>
<string>192.168.64.1</string>
<key>Shared_Net_Mask</key>
<string>255.255.255.0</string>4. Create the virtual machine
Run vftool with the prepared files:
$ vftool \
-k vmlinux \
-i initrd \
-c focal-server-cloudimg-amd64.img \
-d data.img \
-m 2048 \
-a "console=hvc0"The tool will start the VM and attach a TTY (e.g., /dev/ttys000). Open another terminal and connect to the TTY: $ screen /dev/ttys000 Inside the VM, perform initial configuration:
# touch /etc/cloud/cloud-init.disabled
# echo 'root:root' | chpasswd
# echo "podman" > /etc/hostname
# ssh-keygen -f /etc/ssh/ssh_host_rsa_key -N '' -t rsa
# ssh-keygen -f /etc/ssh/ssh_host_dsa_key -N '' -t dsa
# ssh-keygen -f /etc/ssh/ssh_host_ed25519_key -N '' -t ed25519
# cat <<EOF > /etc/netplan/01-dhcp.yaml
network:
renderer: networkd
ethernets:
enp0s1:
dhcp4: no
addresses: [192.168.64.2/24]
gateway4: 192.168.64.1
nameservers:
addresses: [114.114.114.114]
version: 2
EOF
# echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
# sed -i "/^PasswordAuthentication/ c PasswordAuthentication yes" /etc/ssh/sshd_config
# exitAfter exiting, the VM can be stopped with CTRL‑C. Restart it with the same command, adding the root device if needed:
$ vftool \
-k vmlinux \
-i initrd \
-d data.img \
-m 2048 \
-a "console=hvc0 root=/dev/vda" \
-t 05. Set up automatic start with launchctl
Create a launchd plist in ~/Library/LaunchAgents (e.g., vftool.ubuntu.plist) that runs a start script:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ubuntu</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>$HOME/bin/vm/start.sh</string>
</array>
<key>StandardOutPath</key>
<string>/var/log/vftool.ubuntu.stdout.log</string>
<key>StandardErrorPath</key>
<string>/var/log/vftool.ubuntu.stderr.log</string>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>Create the start script ( start.sh) that invokes vftool:
#!/bin/bash
$HOME/bin/vftool \
-k $HOME/bin/vm/vmlinux \
-i $HOME/bin/vm/initrd \
-d $HOME/bin/vm/data.img \
-m 2048 \
-a "console=hvc0 root=/dev/vda" \
-t 0Make the script executable and create log files with write permission:
$ chmod +x start.sh
$ touch /var/log/vftool.ubuntu.stdout.log
$ touch /var/log/vftool.ubuntu.stderr.log
$ sudo chmod a+rw /var/log/vftool.ubuntu.stdout.log
$ sudo chmod a+rw /var/log/vftool.ubuntu.stderr.logLoad and start the service:
$ launchctl load -w ~/Library/LaunchAgents/vftool.ubuntu.plist
$ launchctl start ubuntuVerify the service: $ launchctl list ubuntu Now the Ubuntu VM starts automatically on macOS boot and can be accessed via SSH: $ ssh [email protected] References:
Apple Virtualization Kit documentation
vftool GitHub repository
Ubuntu cloud image URLs
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
