Your First Kubernetes Cluster the Hard Way with UTM

UTM gives you the closest thing to production VMs on a Mac. Full QEMU-backed virtual machines with separate kernels, genuine isolation, and clean single-NIC networking. If you want to learn Kubernetes and cloud-init provisioning at the same time — or if production realism matters more than startup speed — this is the right starting point.

This post walks through the UTM Simple setup: 6 VMs running a Kubernetes 1.32.0 cluster with Vault PKI, a bastion server, and full Ansible automation. Deployment takes about 6 minutes from a cold start. Want to understand the architecture and design decisions before diving in? Read the UTM Simple overview first.

For help choosing between UTM, Vagrant, and OrbStack, see the full comparison. For the learning path from Simple to HA, see From Simple to HA.

What You’re Building

6 full VMs on UTM’s shared network (192.168.64.0/24), each with its own kernel, disk, and single network interface — the cleanest networking of all three tools. The architecture includes a HashiCorp Vault server with a 3-tier PKI CA hierarchy, a jump/bastion server, a dedicated etcd node, one control plane master, and two workers. Kubernetes is installed the hard way from individual ARM64 binaries.

Architecture diagram of the UTM Simple 6-VM Kubernetes cluster on Apple Silicon, showing the macOS host, jump bastion, Vault PKI, master, etcd, and two workers with their IP addresses and connection types
6 VMs on UTM’s shared network — macOS host SSHes into the jump bastion, Vault issues TLS certs to all components, and master-1 coordinates etcd and the two workers.
VMRoleIPRAMDisk
vaultPKI & Secrets192.168.64.114 GB20 GB
jumpBastion / Ansible192.168.64.124 GB20 GB
etcd-1Key-value store192.168.64.212 GB20 GB
master-1Control plane192.168.64.314 GB30 GB
worker-1Worker node192.168.64.416 GB40 GB
worker-2Worker node192.168.64.426 GB40 GB

Total footprint: ~26 GB RAM pre-allocated, ~170 GB disk. You’ll need a Mac with 32 GB+ RAM to run this comfortably.

Why UTM

UTM creates full VMs using QEMU with Apple’s Virtualization framework. Each VM boots its own Ubuntu 24.04 kernel (6.8.0-106-generic), gets its own disk image, and has a single network interface with one IP — the most production-realistic setup of the three tools. The VMs are provisioned using cloud-init ISOs, which means you learn the same provisioning mechanism used by AWS, GCP, and Azure. The utmctl CLI handles VM lifecycle from the terminal without touching the GUI.

The tradeoff is resource consumption. Pre-allocated RAM and disk mean your Mac runs warmer and the initial setup (crafting cloud-init ISOs, scripting utmctl commands) is more involved than OrbStack. But the deploy script handles all of this — you run one command.

Prerequisites

Mac with Apple Silicon, UTM installed (free), and at least 32 GB of RAM. The deploy script handles cloud image downloads, SSH key generation, cloud-init ISO creation, and everything else.

Deploy

git clone https://github.com/labitlearnit/k8s-utm-simple-homelab.git
cd k8s-utm-simple-homelab
./scripts/k8s-utm-simple-homelab.sh

The script creates 6 VMs from Ubuntu 24.04 ARM64 cloud images, generates cloud-init ISOs for automated provisioning (hostname, static IP, SSH keys, role-specific packages), boots all VMs via utmctl, configures the jump server as bastion and Ansible controller, then runs the full Ansible deployment: Vault bootstrap, PKI setup, certificate issuance, etcd, control plane, workers, and Calico CNI.

Total time: approximately 5 minutes 57 seconds.

What Makes UTM Different

Cloud-init ISO provisioning. Unlike OrbStack (which passes cloud-init configs directly) and Vagrant (which uses a shell provisioner), UTM uses the NoCloud datasource — a cloud-init ISO attached as a virtual drive. The script generates meta-data, user-data, and network-config files for each VM, packages them into ISOs, and converts them to qcow2 format for UTM. This is the same mechanism that cloud providers use, making it directly transferable knowledge.

Single NIC per VM. Each VM has one network interface with one IP on UTM’s shared network bridge. No dual-NIC confusion (Vagrant) and no dual-IP surprises (OrbStack). When you configure bind addresses in Ansible, there’s only one choice. This is the closest to how production cloud VMs behave.

Fastest file copy. UTM has the fastest file transfer speed between host and VMs of all three tools. For the simple setup with 6 VMs, the difference is minor, but it becomes significant in the HA setup with 11 VMs.

Accessing Your Cluster

ssh jump
kubectl get nodes -o wide

From jump, reach any other VM: ssh vault, ssh etcd-1, ssh master-1, ssh worker-1. The Vault UI is accessible at http://vault:8200 from your Mac’s browser.

UTM-Specific Things to Know

UTM crash on destroy. When running the destroy script to tear down all VMs, UTM occasionally crashes with a “UTM quit unexpectedly” dialog. Clicking Reopen brings it back. This is a known quirk when programmatically deleting multiple VMs through utmctl in rapid succession. Not a dealbreaker, but worth expecting.

UEFI boot time. VMs boot via UEFI, and the first boot includes cloud-init package installation. VMs with package_update: false (etcd, masters) boot in under 30 seconds. The jump server, which installs Ansible, Vault CLI, and other tools, takes the longest.

Background binary downloads. The script starts downloading Kubernetes binaries in the background while VMs are being created and booting. By the time VMs are ready, binaries are usually already cached. This overlapping approach saves several minutes.

What’s Next

Once you’ve explored the simple cluster, the Simple to HA learning path maps the full progression. The UTM HA setup scales to 11 VMs with 38 GB RAM — the full 17-step deployment walkthrough covers every detail.

To understand what the simple cluster is missing, read Why Your Homelab K8s Cluster Isn’t Production-Ready. For the Vault PKI deep dive, see Vault PKI for Kubernetes: 3-Tier CA the Right Way.

The full source code is at github.com/labitlearnit/k8s-utm-simple-homelab.

Big tech, small lab. One reel at a time.

Questions, corrections, or want to share how you’re using these repos?

labitlearnit@gmail.com

Enjoyed this post?

Want homelab configs to your email?

Leave a Reply

Discover more from Lab it, learn it

Subscribe now to keep reading and get access to the full archive.

Continue reading