If infrastructure-as-code is your priority — everything in a Vagrantfile, committable to Git, reproducible with one command — Vagrant is the right choice for your first Kubernetes cluster. The same 6-VM architecture as the UTM and OrbStack versions, but defined declaratively with the QEMU provider and socket_vmnet for Apple Silicon networking.
This post covers the Vagrant Simple setup. Want to understand the architecture and design decisions before diving in? Read the Vagrant Simple overview first. For help choosing between tools, see the UTM vs Vagrant vs OrbStack comparison. For the full learning path from Simple to HA, see From Simple to HA.
What You’re Building
6 full QEMU-backed VMs defined in a single Vagrantfile, running on the socket_vmnet subnet (auto-detected, typically 192.168.105.0/24). Same architecture as UTM and OrbStack: Vault server with 3-tier PKI, jump/bastion server, dedicated etcd node, one master, two workers. Kubernetes installed the hard way from ARM64 binaries.
| VM | Role | IP Suffix | vCPU | RAM |
|---|---|---|---|---|
| vault | PKI & Secrets | .11 | 2 | 4 GB |
| jump | Bastion / Ansible | .12 | 2 | 4 GB |
| etcd-1 | Key-value store | .21 | 2 | 2 GB |
| master-1 | Control plane | .31 | 2 | 4 GB |
| worker-1 | Worker node | .41 | 2 | 6 GB |
| worker-2 | Worker node | .42 | 2 | 6 GB |
Total footprint: 12 vCPUs, ~26 GB RAM. You’ll need a Mac with 32 GB+ total system memory.
Why Vagrant
Vagrant’s value is declarative infrastructure. The entire cluster topology lives in a single Vagrantfile — VM names, IPs, resource allocations, provisioning scripts. Commit it to Git, share it with a teammate, and vagrant up --provider=qemu produces an identical cluster on any Apple Silicon Mac with the same prerequisites installed.
The vagrant-qemu plugin provides a QEMU provider that runs natively on ARM64. socket_vmnet (from the Lima project) gives each VM a routable IP on the host network via macOS’s vmnet.framework. Together, they create full VMs with genuine kernel isolation — the same as UTM under the hood, but managed through Vagrant’s declarative abstraction.
The tradeoff: every VM gets two network interfaces (one NAT for internet, one vmnet for inter-VM communication). This dual-NIC design means the Vagrantfile and Ansible inventory must be explicit about which interface to use. Getting this wrong is the #1 source of mysterious failures — nodes appear to join the cluster but can’t communicate. The automation handles this correctly, but it’s worth understanding.
Prerequisites
Mac with Apple Silicon and these installed via Homebrew:
brew install vagrant qemu socket_vmnet ansible
vagrant plugin install vagrant-qemu
sudo brew services start socket_vmnet
pip3 install hvac
socket_vmnet runs as a LaunchDaemon (requires root) to provide vmnet.framework networking for QEMU. The QEMU process itself stays unprivileged.
Deploy
git clone https://github.com/labitlearnit/k8s-vagrant-simple-homelab.git
cd k8s-vagrant-simple-homelab
./k8s-vagrant-simple-homelab.sh
The script runs vagrant up --provider=qemu to create all 6 VMs, configures the jump server as bastion and Ansible controller, then executes the full Ansible deployment from jump: Vault bootstrap, PKI setup, certificate issuance, etcd, control plane, workers, and Calico CNI.
Total time: approximately 6 minutes 33 seconds. The vagrant up phase alone takes about 2 minutes 25 seconds.
What Makes Vagrant Different
Declarative Vagrantfile. All 6 VMs are defined in one file with their roles, IP suffixes, CPU, and RAM allocations. The network prefix is auto-detected from the socket_vmnet LaunchDaemon plist. vagrant up creates, vagrant destroy -f tears down. The Vagrantfile is version-controllable infrastructure.
Dual network interfaces. Each VM gets eth0 (NAT for internet, managed by the QEMU provider) and eth1 (vmnet for inter-VM communication, provided by socket_vmnet). All cluster communication — etcd, API server, kubelet registration — happens on eth1. The Ansible inventory and all systemd unit files are configured to bind to the vmnet IP, not the NAT IP.
Shell provisioner. Unlike UTM (cloud-init ISOs) and OrbStack (cloud-init passed directly), Vagrant uses its built-in shell provisioner to configure each VM after boot — setting hostnames, injecting SSH keys, writing /etc/hosts, configuring the static IP on the vmnet interface via Netplan, and installing role-specific packages.
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.
Vagrant-Specific Things to Know
Host key cleanup after recreation. When VMs are destroyed and recreated, they generate new SSH host keys. Stale keys cached on the Mac will cause SSH to reject connections. The deploy script handles this automatically, but manual Ansible runs may need cleanup: remove cached host keys for all VM IPs and delete Ansible’s SSH control sockets at ~/.ansible/cp/.
Wrong interface binding. If Kubernetes components bind to eth0 (NAT) instead of eth1 (vmnet), nodes will appear to join the cluster but can’t communicate. Always verify bind addresses in systemd unit files point to the vmnet IPs.
socket_vmnet bridge. If vagrant status shows VMs as running but SSH fails with “No route to host”, the socket_vmnet bridge may need a restart: sudo brew services restart socket_vmnet. Check that bridge100 exists with ifconfig bridge100.
Duplicate SSH config entries. Both the HA and Simple Vagrant projects add a Host jump block to ~/.ssh/config. Running both creates duplicates. SSH uses the first match, so it still works, but clean up with grep -c "Host jump" ~/.ssh/config if needed.
What’s Next
Once you’ve explored the simple cluster, the Simple to HA learning path maps the full progression. The Vagrant HA setup scales to 11 VMs — the Vagrant HA deep dive covers the Vagrantfile, dual-NIC architecture, and deployment timing in 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-vagrant-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
Leave a Reply