The Mini Rack Saga - Part 3: RKE2 on Mac Minis
If you’ve been following along, we’ve got two Raspberry Pi 5s running lean and mean — NVMe boot, SSH keys, Tailscale, containers humming. But now it’s time to level up.
In Part 3 we’re bringing three Intel i7 Mac minis into the mix — running Pop!_OS — and turning them into a proper highly available Kubernetes cluster with RKE2. Then we’re going to tie it all together with the Tailscale Kubernetes Operator so you can manage the cluster from anywhere on your tailnet. Yes, we put Linux on Mac minis. Yes, it’s great. Let’s get into it.
Why RKE2? Link to heading
RKE2 is Rancher’s next-generation Kubernetes distribution and it’s a fantastic fit for a homelab that takes security seriously. It’s hardened by default, ships with sensible security policies out of the box, and is specifically designed with air-gapped deployments in mind — which lines up perfectly with where this series is heading in Part 4.
It also has a dead simple install process. You’ll see.
The Hardware Link to heading
Three Intel i7 Mac minis, all running Pop!_OS, all serving as control plane nodes. Three control plane nodes gives us a proper HA setup — if one node goes down, the cluster keeps running. No single point of failure.
Here’s how we’ll refer to them throughout the article:
| Hostname | Role |
|---|---|
mac-mini-01 | Control Plane (Primary) |
mac-mini-02 | Control Plane |
mac-mini-03 | Control Plane |
Why three? RKE2 uses etcd under the hood for its datastore, and etcd requires an odd number of nodes to maintain quorum. Three is the minimum for true HA.
Step 1: Installing Pop!_OS on the Mac Minis Link to heading
The 2018 Intel Mac mini has a T2 security chip, which means a couple of extra steps before Linux will boot. Do this on each machine before anything else.
Prepare a bootable USB Link to heading
Download the latest Pop!_OS LTS ISO from system76.com/pop — grab the Intel/AMD version. Flash it to a USB drive with Balena Etcher.
Disable Secure Boot Link to heading
The T2 chip enforces Secure Boot by default and will refuse to boot unsigned operating systems. You need to turn this off.
On each Mac mini, boot into macOS Recovery:
- Shut down the machine completely.
- Power it on and immediately hold Cmd + R until you see the Apple logo.
- Once in Recovery, open Utilities → Startup Security Utility.
- Set Secure Boot to No Security.
- Set External Boot to Allow booting from external or removable media.
- Restart.
Install Pop!_OS Link to heading
- Plug in the USB drive and power on the Mac mini while holding the Option (⌥) key.
- Select the USB drive from the boot picker — it will appear as EFI Boot.
- The Pop!_OS live installer will launch. Follow the prompts: choose your language and select the internal SSD as the install target.
- Choose Clean Install (this erases macOS — which is exactly what we want).
- On the next screen, enable Encrypt Drive and set a strong passphrase. Pop!_OS uses LUKS full-disk encryption — the drive is unreadable without the passphrase, which is worth having on hardware that lives in a rack.
- Confirm and let the install complete. Reboot and remove the USB when prompted.
You should boot straight into Pop!_OS, prompting for your encryption passphrase. Ethernet works out of the box on the Mac mini — which is all we need for a cluster node. Wi-Fi requires T2 drivers from the t2linux project, but since these machines are rack-mounted with ethernet, skip it.
Repeat on all three nodes before moving on.
Step 2: Fresh Pop!_OS Setup Link to heading
A few things to handle on each node before we touch RKE2. Run these on all three.
Update the system Link to heading
sudo apt update && sudo apt upgrade -y
Set hostnames Link to heading
Give each node a clean hostname:
sudo hostnamectl set-hostname mac-mini-01
Repeat on each node with the appropriate hostname (mac-mini-02, mac-mini-03).
Disable swap Link to heading
Kubernetes requires swap to be off:
sudo swapoff -a
Make it permanent by commenting out any swap entries in /etc/fstab:
sudo nano /etc/fstab
# Comment out any line referencing swap
Check for AppArmor Link to heading
Pop!_OS ships with AppArmor support, so make sure the tools are present:
sudo apt install -y apparmor-parser
Step 3: Install RKE2 on mac-mini-01 Link to heading
Run the installer Link to heading
curl -sfL https://get.rke2.io | sudo sh -
This installs the rke2-server service and the rke2 binary onto your machine.
Create the config file Link to heading
sudo mkdir -p /etc/rancher/rke2
sudo touch /etc/rancher/rke2/config.yaml
sudo nano /etc/rancher/rke2/config.yaml
Add the following:
token: your-shared-secret-token
tls-san:
- mac-mini-01
- <ip-of-mac-mini-01>
The token is a shared secret the other nodes will use to join — pick something strong. The tls-san entries ensure the TLS cert is valid whether you’re connecting by hostname or IP.
Enable and start RKE2 Link to heading
sudo systemctl enable rke2-server.service
sudo systemctl start rke2-server.service
Follow the logs Link to heading
journalctl -u rke2-server -f
Give it a minute to come up, then verify:
sudo /var/lib/rancher/rke2/bin/kubectl \
--kubeconfig /etc/rancher/rke2/rke2.yaml \
get nodes
You should see mac-mini-01 in Ready state.
Step 4: Join mac-mini-02 and mac-mini-03 Link to heading
On both mac-mini-02 and mac-mini-03, run the same installer:
curl -sfL https://get.rke2.io | sudo sh -
Create the config file:
sudo mkdir -p /etc/rancher/rke2
sudo touch /etc/rancher/rke2/config.yaml
sudo nano /etc/rancher/rke2/config.yaml
Add the following, substituting your actual values:
server: https://<ip-of-mac-mini-01>:9345
token: your-shared-secret-token
tls-san:
- mac-mini-02 # mac-mini-03 on the third node
- <ip-of-this-node>
Enable and start:
sudo systemctl enable rke2-server.service
sudo systemctl start rke2-server.service
Repeat on the third node. Note that rke2-server listens on port 9345 for new nodes to register — the Kubernetes API is served on port 6443 as normal. Pop!_OS ships with ufw disabled by default, so if you haven’t configured a firewall these ports are already reachable. If you have ufw enabled, open them explicitly:
sudo ufw allow 9345/tcp
sudo ufw allow 6443/tcp
Once both are up, head back to mac-mini-01 and confirm all three nodes are ready:
sudo /var/lib/rancher/rke2/bin/kubectl \
--kubeconfig /etc/rancher/rke2/rke2.yaml \
get nodes
All three nodes in Ready state. That’s your HA cluster.
Step 5: Tailscale Kubernetes Operator Link to heading
Now for the fun part. Instead of copying kubeconfig files around and relying on LAN access to manage the cluster, we’re going to install the Tailscale Kubernetes Operator. Once this is in place you can run kubectl from anywhere on your tailnet — your laptop, the Pis, anywhere — without being on the same local network.
Prerequisites Link to heading
Before installing the operator you need to set up an OAuth client in the Tailscale admin console and configure your ACL. Let’s do that first.
Configure the ACL Link to heading
In the Tailscale admin console, navigate to Access Controls and update your policy to add the tag owners and grants needed for the operator:
{
"tagOwners": {
"tag:k8s-operator": ["<your-tailscale-account-email>"],
"tag:k8s": ["tag:k8s-operator"]
},
"grants": [
{
"src": ["*"],
"dst": ["*"],
"ip": ["*"],
"app": {
"tailscale.com/cap/kubernetes": [{
"impersonate": {
"groups": ["system:masters"]
}
}]
}
}
]
}
The tag:k8s-operator tag is what the operator will use to identify itself on your tailnet. The grant allows tailnet members to access the Kubernetes API through the operator with system:masters permissions — tune this down for production, but for a homelab this gets you up and running.
Create an OAuth Client Link to heading
In the Tailscale admin console, go to Settings → OAuth clients and create a new client. Give it write scopes for Devices and Auth Keys. Note down the Client ID and Client Secret — you’ll need them in the next step.
Store the OAuth Credentials in Kubernetes Link to heading
Create the namespace first, then store the secret into it:
kubectl create namespace tailscale
kubectl -n tailscale create secret generic operator-oauth \
--from-literal=client_id="<your-client-id>" \
--from-literal=client_secret="<your-client-secret>"
Install the Tailscale Operator via Helm Link to heading
Add the Tailscale Helm repo:
helm repo add tailscale https://pkgs.tailscale.com/helmcharts
helm repo update
Install the operator:
helm install tailscale-operator tailscale/tailscale-operator \
--create-namespace \
--namespace tailscale \
--set oauth.clientIdSecret.name=operator-oauth \
--set oauth.clientIdSecret.key=client_id \
--set oauth.clientSecretSecret.name=operator-oauth \
--set oauth.clientSecretSecret.key=client_secret \
--set operatorConfig.defaultTags=tag:k8s-operator \
--set-string apiServerProxyConfig.mode=true
Give it a moment to come up. You should see the operator appear as a device in your Tailscale admin console tagged with tag:k8s-operator.
Configure kubectl over Tailscale Link to heading
Here’s where it gets cool. Run this on your local machine:
tailscale configure kubeconfig tailscale-operator
That’s it. Your local kubectl is now talking to your RKE2 cluster over Tailscale. Verify it from your local machine:
kubectl get nodes
All three Mac minis, from anywhere on your tailnet, no LAN required.
Exposing Services via Tailscale Link to heading
When you deploy services to the cluster in Part 4, exposing them to your tailnet is as simple as annotating the service:
kubectl annotate svc <SERVICE> \
-n <NAMESPACE> \
tailscale.com/expose=true
The operator takes care of the rest — ingress, DNS, TLS. We’ll put this to use in Part 4.
Wrapping Up Link to heading
Three Intel i7 Mac minis running Pop!_OS, forming a proper HA RKE2 cluster — accessible from anywhere on your tailnet via the Tailscale Kubernetes Operator. That’s a serious homelab upgrade.
Here’s where we stand in the series:
- ✅ Part 1 — The physical rack build
- ✅ Part 2 — Raspberry Pi software setup
- ✅ Part 3 — RKE2 on Mac minis + Tailscale Operator
- Part 4 — Deploying the self-hosted AIML stack
In Part 4 we take this cluster and deploy a fully self-hosted AI stack on top of it — LiteLLM for model routing and per-user usage tracking, Open WebUI as the frontend with Keycloak auth, all exposed securely over Tailscale. The groundwork is laid. See you in Part 4.