This repository packages the automation and documentation for a MicroK8s homelab or production edge build. It covers both a simple single-node deployment and a three-node replicated-storage deployment.
This directory contains two supported deployment paths:
- single-node MicroK8s with local
hostpath-storage - three-node MicroK8s with OpenEBS Mayastor replicated storage
Both paths include:
- MicroK8s
- CoreDNS
- RBAC
- ingress
- MetalLB
- cert-manager
- Rancher
Use the single-node path when you want a simple lab or edge setup with local, non-replicated storage. That path now supports either the default node-backed microk8s-hostpath storage or a dedicated physical disk mounted and used as the hostpath backing directory. Use the three-node path when you want shared replicated block storage through Mayastor, optionally with a dedicated second NIC or storage VLAN for node-to-node and Mayastor traffic.
I started this project for two practical reasons.
First, I wanted a simple, repeatable, easy-to-use deployment script for MicroK8s. MicroK8s is a strong fit for a homelab or Datacenter deployment, but the path from a clean Ubuntu install to a working cluster with ingress, load balancing, storage, and Rancher still involves enough moving parts that it is easy to drift, forget a step, or lose time rebuilding the same setup.
Second, I wanted a cleaner foundation for expanding my home lab AI stack. Most of my existing AI workloads were running directly on Docker. That worked, but it made growth, ingress management, storage behavior, and multi-service operations harder to standardize. Moving to MicroK8s provided a better platform for experimenting with self-hosted AI services, management tooling, and future cluster growth while still staying light enough for a home environment.
This repository is the result of turning that work into something reusable, documented, and easier to share, especially as a small personal way to mark 12 years of Kubernetes.
The work in this repository was developed and tested in a home lab environment based on:
- Ubuntu 24.04 LTS guest nodes
- VMware Workstation 7 as the local virtualization platform used for this lab
- a gaming PC host with an AMD Ryzen 5 5500
- 32 GB RAM on the host
- a Docker-based AI stack as the starting point before moving services toward MicroK8s
You do not need identical hardware to use the repository, but the scripts and documentation were shaped around that kind of practical homelab setup.
- Ubuntu 24.04 LTS on the target node or nodes
- latest Ubuntu package updates applied before bootstrap, ideally:
sudo apt update
sudo apt upgrade -y- SSH server enabled on each node
- an SSH user with passwordless
sudo - outbound internet access from the nodes for package and snap downloads
- a working LAN segment for node-to-node communication
- one machine for the single-node path, or three machines/VMs for the three-node path
- enough CPU and RAM to run MicroK8s, ingress, MetalLB, cert-manager, and Rancher
- for the three-node path, one additional unused block device per node for Mayastor when using
disk-onlymode - for the single-node path, enough local disk space for
microk8s-hostpath, or one dedicated local block device if you want the physical-disk-backed option
- static IP addresses or stable DHCP reservations
- a reserved MetalLB IP range on the same LAN as the cluster nodes
- DNS or
/etc/hostscontrol for names such asrancher.example.local - ideally two NICs or two network paths if you want cleaner separation between management traffic and lab/service traffic
Two NICs are not a hard requirement for the scripts to work, but they are a sensible design choice for a more serious home lab, especially if you want to separate administrative access from workload exposure.
Three-node path note:
- If you provide
primary_storage_ip,node2_storage_ip, andnode3_storage_ip, the three-node bootstrap will configure MicroK8s kubelet--node-ipon those addresses. That means Kubernetes node-to-node traffic and Mayastor traffic will prefer the storage network. - This is not a Mayastor-only binding. It is a cluster-internal node IP change for the three-node deployment.
- Single-node deployments can use the default
microk8s-hostpathstorage class or a dedicated physical disk mounted as the backing directory for a custom hostpath storage class. - Three-node deployments support Mayastor
sparsemode anddisk-onlymode. - For
disk-only, the chosen devices must be real block devices, must not be mounted, and must not hold data you care about. - In
disk-only, the final intended state is disk-backed Mayastor pools only, but the bootstrap may briefly create default sparse pools first and then remove them.
- scripts/enable-microk8s-single-node.sh End-to-end single-node bootstrap. It installs MicroK8s on one node, enables the required addons, configures either default hostpath storage or a dedicated physical-disk-backed hostpath storage class, exposes ingress through MetalLB, and installs Rancher.
- examples/single-node-answers.example.yaml Example flat YAML answers file for repeatable single-node runs.
- scripts/enable-microk8s-three-node.sh End-to-end three-node bootstrap. It prepares nodes, installs MicroK8s, joins the cluster, enables addons, deploys Mayastor, and installs Rancher.
- examples/cluster-answers.example.yaml Example flat YAML answers file for repeatable three-node runs.
- scripts/prepare-node-for-mayastor.sh Standalone Mayastor host preparation script for the three-node path.
- scripts/remove-mayastor-sparse-pools.sh
Helper script to detect and optionally remove sparse Mayastor
DiskPoolresources. - scripts/health-status.sh Health summary script for single-node and three-node deployments. It checks SSH access, MicroK8s state, nodes, problem pods, ingress exposure, storage classes, Mayastor state when present, and Rancher pods.
- docs/troubleshooting.md Troubleshooting notes, diagnostic commands, and fixes for issues already encountered.
- CONTRIBUTING.md Contribution guidelines for keeping the automation, documentation, and behavior consistent.
- LICENSE MIT-0 license for unrestricted reuse.
- archive/README.md Notes about the archived legacy scripts.
backups/Local timestamped backups created before edits. This is intended for local safety, not for source control history.
Single node:
chmod +x scripts/enable-microk8s-single-node.sh
cp examples/single-node-answers.example.yaml single-node-answers.yaml
./scripts/enable-microk8s-single-node.sh --answers single-node-answers.yaml
./scripts/health-status.sh --answers single-node-answers.yamlThree nodes:
chmod +x scripts/enable-microk8s-three-node.sh scripts/prepare-node-for-mayastor.sh
cp examples/cluster-answers.example.yaml cluster-answers.yaml
./scripts/enable-microk8s-three-node.sh --answers cluster-answers.yaml
./scripts/health-status.sh --answers cluster-answers.yaml +----------------------+
| Admin Workstation |
| SSH + local kubeconfig|
+----------+-----------+
|
| SSH / kubectl
|
------------------------------------------------
|
Management / LAN (example 192.168.1.0/24)
|
+-------+--------+
| kubenode01 |
| MicroK8s |
| Control/worker |
| CoreDNS |
| RBAC |
| ingress |
| MetalLB |
| cert-manager |
| Rancher |
| hostpath |
+-------+--------+
|
+--------------+--------------+
| |
+---------+---------+ +---------+---------+
| MetalLB ingress IP| | Local storage |
| example: .50 | | hostpath or disk |
+---------+---------+ +-------------------+
|
+---------+---------+
| Host-based ingress|
| rancher.lab.local |
+-------------------+
+----------------------+
| Admin Workstation |
| SSH + local kubeconfig|
+----------+-----------+
|
| SSH / kubectl
|
---------------------------------------------------------
|
Management / Cluster Network (example 192.168.1.0/24)
|
---------------------------------------------------------------------------------
| | |
+-------+--------+ +-------+--------+ +-------+--------+
| kubenode01 | | kubenode02 | | kubenode03 |
| Primary node | | Cluster node | | Cluster node |
| MicroK8s | | MicroK8s | | MicroK8s |
| Control plane | | Control/worker | | Control/worker |
| Mayastor pool | | Mayastor pool | | Mayastor pool |
| /dev/nvme0n1 | | /dev/nvme0n1 | | /dev/nvme0n1 |
+-------+--------+ +-------+--------+ +-------+--------+
| | |
---------------------------------------------------------------------------------
|
+-------------+--------------+
| MicroK8s Cluster |
| dns, ingress, helm3, |
| cert-manager, RBAC, |
| MetalLB, Mayastor |
+-------------+--------------+
|
+-------------------+-------------------+
| |
+---------+---------+ +---------+---------+
| MetalLB IP(s) | | Mayastor CSI |
| example range: | | replicated volumes |
| 192.168.1.240-250 | | StorageClass: |
+---------+---------+ | mayastor-3 |
| +-------------------+
|
+---------+---------+
| Rancher Ingress |
| rancher.lab.local |
+-------------------+
Complete these common prerequisites first.
- Prepare Ubuntu nodes with fixed IP addresses.
- Ensure the nodes can reach each other over the management network.
- Ensure you can SSH from the machine running the scripts to the target node or nodes.
- Ensure the SSH user has passwordless
sudo. - Set Linux hostnames and make sure they match the Kubernetes node names you plan to use.
- Reserve an unused MetalLB IP range on your LAN, for example
192.168.1.240-192.168.1.250. - Decide the DNS name for Rancher, for example
rancher.lab.local. - Ensure your firewall allows SSH and normal cluster traffic.
Three-node path only:
- Identify the block device to use for Mayastor on each node, for example
/dev/nvme0n1. - Make sure the selected Mayastor disks are not mounted, are not used by the OS, and do not contain data you need.
- Make sure each selected Mayastor disk exists on the target node as a real block device.
Both bootstraps use SSH with BatchMode=yes, so key-based login must work before you run either script.
On the machine where you will run the bootstrap:
ls -la ~/.sshIf you already have a key pair such as id_ed25519 and id_ed25519.pub, you can reuse it.
ssh-keygen -t ed25519 -C "microk8s-bootstrap"When prompted:
- Press
Enterto accept the default file path, or choose another path. - Press
Enterfor no passphrase if you want fully non-interactive automation.
Single-node example:
ssh-copy-id kuser@192.168.1.40Three-node example:
ssh-copy-id kuser@192.168.1.40
ssh-copy-id kuser@192.168.1.41
ssh-copy-id kuser@192.168.1.42If your SSH server listens on a non-default port, include -p.
Display your public key:
cat ~/.ssh/id_ed25519.pubThen on each node:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keysPaste the public key into ~/.ssh/authorized_keys.
Single-node example:
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new kuser@192.168.1.40 "echo ok"Three-node example:
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new kuser@192.168.1.40 "echo ok"
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new kuser@192.168.1.41 "echo ok"
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new kuser@192.168.1.42 "echo ok"Check it:
ssh kuser@192.168.1.40 "sudo -n true && echo sudo-ok"If this fails, add a sudoers rule on the node with visudo:
sudo visudoAdd a line like this, replacing kuser with your SSH user:
kuser ALL=(ALL) NOPASSWD:ALL
Three-node path only:
chmod +x scripts/prepare-node-for-mayastor.sh scripts/enable-microk8s-three-node.sh
./scripts/prepare-node-for-mayastor.sh --check-only
./scripts/prepare-node-for-mayastor.sh --device /dev/nvme0n1 --check-onlyIf you want to validate or stage a node ahead of the full bootstrap:
./scripts/prepare-node-for-mayastor.sh --device /dev/nvme0n1chmod +x scripts/enable-microk8s-single-node.sh
./scripts/enable-microk8s-single-node.shTo use a saved YAML answers file:
cp examples/single-node-answers.example.yaml single-node-answers.yaml
./scripts/enable-microk8s-single-node.sh --answers single-node-answers.yamlThe single-node answers file supports:
channelssh_userssh_portnode_namenode_hoststorage_modestorage_class_namestorage_disk_devicestorage_disk_mountpointstorage_disk_allow_formatmetallb_rangerancher_hostnamerancher_bootstrap_passwordingress_loadbalancer_ipingress_address_poolproceed
Single-node storage mode rules:
- if
storage_mode: hostpath, usestorage_class_name: microk8s-hostpathand do not setstorage_disk_device - if
storage_mode: disk, set a dedicatedstorage_class_namesuch asmicrok8s-hostpath-disk, plusstorage_disk_deviceandstorage_disk_mountpoint
chmod +x scripts/enable-microk8s-three-node.sh scripts/prepare-node-for-mayastor.sh
./scripts/enable-microk8s-three-node.shTo use a saved YAML answers file:
cp examples/cluster-answers.example.yaml cluster-answers.yaml
./scripts/enable-microk8s-three-node.sh --answers cluster-answers.yamlThe three-node answers file supports:
channelssh_userssh_portjoin_modeprimary_name,primary_host,primary_diskprimary_storage_ipnode2_name,node2_host,node2_disknode2_storage_ipnode3_name,node3_host,node3_disknode3_storage_ipmetallb_rangerancher_hostnamerancher_bootstrap_passwordmayastor_storageclassmayastor_modemayastor_pool_sizeingress_loadbalancer_ipingress_address_poolproceed
Use the health script after deployment, after upgrades, or during troubleshooting.
Single node:
./scripts/health-status.sh --answers single-node-answers.yamlThree nodes:
./scripts/health-status.sh --answers cluster-answers.yamlAd hoc single-node check without an answers file:
./scripts/health-status.sh --host 192.168.1.40 --user kuserThe script exits with a non-zero status if it detects warnings such as failed SSH access, unhealthy pods, missing ingress exposure, or missing MicroK8s components.
These examples are intentionally simple and are useful as quick smoke tests after the cluster is up.
microk8s helm3 repo add bitnami https://charts.bitnami.com/bitnami
microk8s helm3 repo update
microk8s helm3 upgrade --install nginx bitnami/nginx
microk8s kubectl get pods,svcThis uses the standard nginx image from Docker Hub and creates a Deployment plus a Service:
microk8s kubectl create deployment nginx --image=nginx:stable
microk8s kubectl expose deployment nginx --port=80 --target-port=80 --type=ClusterIP
microk8s kubectl get pods,svcAfter deploying NGINX, create an Ingress resource:
microk8s kubectl apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
spec:
ingressClassName: public
rules:
- host: nginx.example.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80
EOFVerify:
microk8s kubectl get ingress nginx
microk8s kubectl get svc -n ingress ingressThen point the hostname to your MetalLB ingress IP, for example:
192.168.1.50 nginx.example.local
Test it:
curl -I -H 'Host: nginx.example.local' http://192.168.1.50When you run scripts/enable-microk8s-single-node.sh, it will:
- Validate SSH access,
sudo, and MetalLB input. - Install MicroK8s if it is not already present.
- Enable
dns,helm3,ingress,metallb,cert-manager,rbac, andhostpath-storage. - Create a MetalLB-backed
LoadBalancerservice in theingressnamespace when the MicroK8s NGINX ingress controller is present. - In
hostpathmode, makemicrok8s-hostpaththe defaultStorageClass. - In
diskmode, validate the target block device, optionally format it if explicitly allowed, mount it, and create a physical-disk-backed hostpathStorageClass. - Install or upgrade Rancher with Helm.
When you run scripts/enable-microk8s-three-node.sh, it will:
- Validate SSH access,
sudo, node uniqueness, and MetalLB input. - Validate the Mayastor block devices when you use
disk-onlymode. - Validate optional dedicated storage-network IPs when you provide them.
- Apply the Mayastor host prerequisites on all 3 nodes.
- Install MicroK8s on nodes that do not already have it.
- If storage-network IPs are provided, configure kubelet
--node-ipon those addresses and restart MicroK8s. - Join node 2 and node 3 only if they are not already part of the cluster.
- Wait for 3 visible Kubernetes nodes, then wait for the 3-node cluster to become ready.
- Enable
dns,helm3,ingress,metallb,cert-manager, andrbaconly if they are not already enabled. - Create a MetalLB-backed
LoadBalancerservice in theingressnamespace when the MicroK8s NGINX ingress controller is present. - Enable Mayastor.
- In
sparsemode, keep the addon-created default sparse pools. - In
disk-onlymode, Mayastor may briefly create default sparse pools during bootstrap; the script then removes them and replaces them with block-device-backed pools on the 3 nodes. - Create or reconcile the default replicated Mayastor
StorageClass. - Install or upgrade Rancher with Helm.
Both bootstraps are resumable. Rerunning them will skip work that is already complete where possible.
Single-node reruns will:
- skip MicroK8s installation if it is already installed
- skip addons that are already enabled
- preserve the existing ingress
LoadBalancerservice - re-assert the selected single-node default storage class
- run Rancher with
helm upgrade --install
The deployment scripts now write a local timestamped log file by default. This makes it much easier to review failures, long-running steps, and remote command output after a run.
Default behavior:
- scripts/enable-microk8s-single-node.sh writes to
logs/enable-microk8s-single-node-YYYYMMDD-HHMMSS.log - scripts/enable-microk8s-three-node.sh writes to
logs/enable-microk8s-three-node-YYYYMMDD-HHMMSS.log - scripts/prepare-node-for-mayastor.sh writes to
logs/prepare-node-for-mayastor-YYYYMMDD-HHMMSS.log
You can override the path with --log-file:
./scripts/enable-microk8s-three-node.sh --answers cluster-answers.yaml --log-file /tmp/microk8s-three-node.log
./scripts/enable-microk8s-single-node.sh --answers single-node-answers.yaml --log-file /tmp/microk8s-single-node.log
./scripts/prepare-node-for-mayastor.sh --device /dev/nvme0n1 --log-file /tmp/mayastor-prep.logFor the two bootstrap scripts, you can also set log_file: in the answers YAML.
Three-node reruns will:
- skip MicroK8s installation on nodes where it is already installed
- skip cluster join for nodes already present in the cluster
- skip addons that are already enabled
- preserve existing explicit-disk Mayastor pools
- keep existing sparse pools when you use
sparsemode - remove only default sparse Mayastor pools when you use
disk-onlymode, after the initial Mayastor bootstrap if they were created - re-apply the Mayastor
StorageClass - run Rancher with
helm upgrade --install
After either deployment completes:
- Verify Rancher and ingress resources:
kubectl --kubeconfig ~/.kube/config get pods -n cattle-system
kubectl --kubeconfig ~/.kube/config get ingress -A
kubectl --kubeconfig ~/.kube/config get svc -A
kubectl --kubeconfig ~/.kube/config get svc -n ingress ingress- Note the
EXTERNAL-IPofsvc/ingressin theingressnamespace. - Add a DNS entry or a
/etc/hostsentry for your Rancher hostname.
Example:
192.168.1.50 rancher.example.local
- Open Rancher in your browser using the hostname:
https://rancher.example.local
- Log in as
adminusing the bootstrap password you provided. - If
https://<node-ip>returns404, that is expected. Rancher is exposed through a host-based ingress rule and should be accessed by hostname, not raw IP.
If your cluster does not automatically create svc/ingress in the ingress namespace, apply this service manually:
apiVersion: v1
kind: Service
metadata:
name: ingress
namespace: ingress
spec:
selector:
name: nginx-ingress-microk8s
type: LoadBalancer
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
- name: https
protocol: TCP
port: 443
targetPort: 443Apply it with:
ssh kuser@192.168.1.40 "sudo microk8s kubectl apply -f -" <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: ingress
namespace: ingress
spec:
selector:
name: nginx-ingress-microk8s
type: LoadBalancer
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
- name: https
protocol: TCP
port: 443
targetPort: 443
EOFThen verify:
ssh kuser@192.168.1.40 "sudo microk8s kubectl get svc -n ingress ingress"Three-node path only. Use this only if you want to move away from the addon-created sparse Mayastor pools.
Dry-run first:
./scripts/remove-mayastor-sparse-pools.sh --ssh kuser@192.168.1.40Delete the detected sparse pools:
./scripts/remove-mayastor-sparse-pools.sh --ssh kuser@192.168.1.40 --applyIf you already have microk8s kubectl access locally, you can also run:
./scripts/remove-mayastor-sparse-pools.sh
./scripts/remove-mayastor-sparse-pools.sh --apply- The standalone Mayastor prep script is only needed for the three-node path.
- The kubeconfig written by the bootstraps is stored in
~/.kube/configon the machine where you run the script. - The single-node path uses local storage only. It is not replicated and it lives on that one node.
- In
hostpathmode, the single-node path uses the defaultmicrok8s-hostpathstorage class. - In
diskmode, the single-node path mounts a dedicated local disk and creates a hostpath storage class backed by that mountpoint. - In
diskmode, the script only formats the selected disk if you explicitly setstorage_disk_allow_format: true. - The three-node path supports
sparseanddisk-onlyMayastor modes. - In
disk-onlymode, Mayastor may create temporary sparse pools during bootstrap, but the script is designed to remove them and leave disk-backed pools as the final state. - If your Linux hostnames do not match the node names you provide, Mayastor pool creation will fail later in the three-node path.
- If you provide
*_storage_ipvalues in the three-node path, those IPs must already exist on the nodes on a reachable second NIC or VLAN. - The three-node storage-network option changes kubelet node IPs. It is intended to steer cluster-internal and Mayastor traffic to the dedicated network, not just Mayastor alone.
- In the preferred setup, Rancher and other ingress apps should be reached through the MetalLB floating IP assigned to
svc/ingress, together with the configured hostname. - If
svc/ingressis missing, use the ingressLoadBalancerrecovery command above. - For prior failure modes and the exact recovery commands, use docs/troubleshooting.md.