Self-host Kubernetes: escape the cloud tax
🤖 Researched and drafted automatically from the official docs, and reviewed before publishing. Commands are taken from the source projects — but always sanity-check before running anything on your own hardware.
Kubernetes lets you orchestrate containerized applications across multiple machines—the same system Google built to run their production workloads at scale. Instead of renting capacity from AWS EKS, Azure AKS, or GCP GKE, you can run Kubernetes on your own hardware and keep all that money in your pocket.
This guide walks you through building a minimal Kubernetes cluster on bare metal, suitable for a homelab or small office. You’ll have full control over your infrastructure, no vendor lock-in, and no surprise bills.
Prerequisites
You need:
- At least two physical machines (three or more for a production-grade cluster)
- A Go environment or Docker installed on your build machine
- Network connectivity between all nodes
- Basic Linux administration experience
- A private network or VPN—never expose Kubernetes to the public internet
Step 1: Prepare your hardware
Each machine should run a modern Linux distribution (Ubuntu 20.04 LTS or later is solid). Assign static IP addresses to all nodes. Ensure they can ping each other over your LAN.
For a homelab cluster, even modest hardware works:
- Control plane node: 2+ CPU cores, 4+ GB RAM
- Worker nodes: 2+ CPU cores, 4+ GB RAM each
Step 2: Clone and build Kubernetes
On a build machine with Go installed:
git clone https://github.com/kubernetes/kubernetes
cd kubernetes
make
If you prefer to use Docker instead of a Go environment:
git clone https://github.com/kubernetes/kubernetes
cd kubernetes
make quick-release
The build process compiles the Kubernetes binaries. This takes time; go grab coffee.
Step 3: Set up container runtime
Kubernetes needs a container runtime. Install containerd or Docker on every node:
# Example for Ubuntu with containerd
sudo apt-get update
sudo apt-get install -y containerd.io
sudo systemctl enable containerd
sudo systemctl start containerd
Verify the runtime is running:
sudo systemctl status containerd
Step 4: Initialize the control plane
On your designated control plane node, initialize the cluster. Copy the Kubernetes binaries you built to a location in your PATH (e.g., /usr/local/bin), then run:
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
This command bootstraps the control plane. At the end, it prints a kubeadm join command—save this; you’ll use it on worker nodes.
Set up your local kubeconfig:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Verify the control plane is running:
kubectl get nodes
You should see your control plane node in a NotReady state until you install a network plugin.
Step 5: Install a network plugin
Kubernetes needs a network overlay to connect pods across nodes. Flannel is simple and works well on bare metal:
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
Wait a minute, then check node status:
kubectl get nodes
Your control plane should now show Ready.
Step 6: Join worker nodes
On each worker node, copy the Kubernetes binaries to /usr/local/bin, ensure containerd is running, then run the kubeadm join command from Step 4:
sudo kubeadm join <control-plane-ip>:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>
Replace the placeholders with the actual values from your control plane’s init output. If you lost the command, regenerate it on the control plane:
kubeadm token create --print-join-command
Step 7: Verify cluster health
From the control plane, check all nodes:
kubectl get nodes
All nodes should show Ready. Check pod status:
kubectl get pods --all-namespaces
You should see system pods running in kube-system, kube-public, and kube-node-lease namespaces.
Step 8: Deploy a test workload
Create a simple deployment to verify everything works:
kubectl create deployment nginx --image=nginx:latest
kubectl expose deployment nginx --port=80 --type=NodePort
Check the service:
kubectl get svc nginx
Note the NodePort value (typically in the 30000–32767 range). Visit http://<worker-node-ip>:<NodePort> from another machine on your LAN. You should see the Nginx welcome page.
Step 9: Secure your cluster
Kubernetes API server is accessible on port 6443 by default. Restrict access to your LAN or VPN only. Use firewall rules:
# Example: allow only from your LAN subnet
sudo ufw allow from 192.168.1.0/24 to any port 6443
Never expose the API server to the public internet. Always use a VPN or private network for management traffic.
Step 10: Set up persistent storage (optional)
For stateful workloads, configure persistent volumes. A simple approach is to use local storage on each node:
kubectl apply -f - <<EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
EOF
For production, consider a distributed storage system like Ceph or Longhorn.
Ongoing maintenance
- Updates: Kubernetes releases frequently. Plan updates by draining nodes one at a time, upgrading binaries, and rebooting.
- Monitoring: Install Prometheus and Grafana to watch cluster health.
- Backups: Regularly backup etcd (the control plane’s database) to recover from failures.
- Logging: Deploy a log aggregator like Loki or the ELK stack to centralize logs from all pods.
Is it worth it?
Yes, if you’re running persistent workloads. The upfront effort pays off fast. You avoid vendor lock-in, control your own data, and save money—especially if you’re already running a homelab. The learning curve is real, but Kubernetes is the industry standard; skills transfer anywhere.
If you’re just testing or running short-lived projects, managed Kubernetes might still make sense. But for a home or small-office deployment running 24/7, self-hosted Kubernetes is absolutely worth the effort. You own the infrastructure, you own the bill.
Gear used in this build
* Affiliate links — I earn a small commission at no cost to you. It's gear I use and would genuinely recommend. See the full disclosure.
Related video
New self-hosted AI & homelab shorts, daily.
Subscribe on YouTubeRelated guides
Control a Dumb AC Unit with ESP32 and ESPHome
Wire an ESP32 to your window AC's remote control pins, then command it over your LAN using ESPHome. No Wi-Fi remote needed, total cost under $35.
Run rust-analyzer on your homelab for 100x less RAM
Deploy a self-hosted Rust LSP server on cheap hardware. Get IDE features—completion, refactoring, diagnostics—without melting your machine. Keep it on your LAN.
Run Claude Code and Codex Locally with OtoDock
Deploy OtoDock on your server to get local code generation without cloud API costs. Self-hosted alternative to Claude Code and GitHub Copilot.