K0s: Zero-Friction Kubernetes for Homelabs
Single-binary Kubernetes with minimal dependencies. The easiest path to production-grade clusters.
Overview
K0s (pronounced “kay-zero-s”) is a CNCF-certified Kubernetes distribution that delivers the full power of Kubernetes through a single binary with zero dependencies. Developed by Mirantis, k0s eliminates the complexity traditionally associated with Kubernetes deployment while maintaining 100% upstream compatibility and passing all CNCF conformance tests.
Unlike heavyweight Kubernetes distributions that require extensive prerequisites and configuration, k0s bundles everything needed - container runtime (containerd), networking (Kube-router or Calico), storage, and all Kubernetes components - into a single ~250MB binary. For homelabbers, this means going from zero to a production-grade Kubernetes cluster in under 10 minutes.
What you’ll learn:
- Install k0s on single-node and multi-node clusters
- Understand k0s architecture (controller, worker, controller+worker)
- Deploy applications using kubectl and YAML manifests
- Compare k0s with K3s and choose the right distribution
- Configure storage, networking, and ingress
- Manage cluster lifecycle (backup, restore, upgrade)
- Implement best practices for homelab Kubernetes
- Troubleshoot common k0s issues
Why k0s?
Instead of managing separate components (kubelet, kube-proxy, CNI plugins, container runtime), k0s provides a batteries-included distribution that just works. One binary, one config file, one command to start a cluster. Yet unlike simplified alternatives, k0s remains a full, conformant Kubernetes implementation - anything that works on “real” Kubernetes works on k0s.
Prerequisites
Before starting, ensure you have:
- Linux system(s) (Ubuntu 20.04+, Debian 11+, RHEL 8+, or compatible)
- Basic Kubernetes knowledge helpful but not required
- Hardware requirements per node:
- Single-node (controller+worker): 2 CPU cores, 2 GB RAM, 20 GB storage
- Controller node: 1 CPU core, 1 GB RAM, 10 GB storage
- Worker node: 2 CPU cores, 2 GB RAM, 20 GB storage
- Network connectivity between cluster nodes
- Open ports:
- Controller: 6443 (API), 8132 (Konnectivity), 9443 (controller join)
- Worker: 10250 (kubelet)
- Root or sudo access for installation
Minimum viable cluster:
- 1 node: Controller+worker combined (perfect for homelab testing)
- 3 nodes: 1 controller + 2 workers (recommended for production-like setup)
- HA: 3 controllers + N workers (high availability)
Understanding K0s Fundamentals
What is K0s?
K0s is a minimalist Kubernetes distribution focused on:
- Zero dependencies: Single binary, no host OS packages required
- Zero friction: Simple installation, sensible defaults
- Full Kubernetes: 100% upstream, CNCF certified
- Flexible deployment: Single-node to multi-node HA clusters
Architecture Components
Node types in k0s:
┌─────────────────────────────────────────┐
│ Controller Node │
│ ┌───────────────────────────────────┐ │
│ │ kube-apiserver │ │
│ │ kube-controller-manager │ │
│ │ kube-scheduler │ │
│ │ etcd (embedded) │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Worker Node │
│ ┌───────────────────────────────────┐ │
│ │ kubelet │ │
│ │ kube-proxy │ │
│ │ containerd (bundled) │ │
│ │ CNI (kube-router/calico) │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Controller+Worker Node (All-in-One) │
│ ┌───────────────────────────────────┐ │
│ │ All control plane components │ │
│ │ + All worker components │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘ Controller nodes:
- Run Kubernetes control plane (API server, scheduler, controller manager)
- Store cluster state in embedded etcd
- Don’t run workload pods by default (but can if combined with worker)
Worker nodes:
- Run containerized workloads (your applications)
- Communicate with controller via API server
- Execute pod scheduling decisions
Controller+Worker nodes:
- Combined mode (perfect for single-node homelabs)
- Runs both control plane and workloads
- Default mode for k0s installation
K0s vs K3s: Detailed Comparison
Both are lightweight Kubernetes, but different philosophies:
| Aspect | K0s | K3s |
|---|---|---|
| Developer | Mirantis | Rancher (SUSE) |
| Philosophy | Modular, upstream-first | Opinionated, integrated |
| Binary Size | ~250 MB | ~70 MB |
| Dependencies | Zero (fully bundled) | Zero (fully bundled) |
| Container Runtime | containerd (bundled) | containerd (bundled) |
| CNI Default | kube-router | Flannel |
| CNI Options | Kube-router, Calico, custom | Flannel, Canal, custom |
| Storage Default | None (bring your own) | Local-path provisioner |
| Service LB | None (bring your own) | Klipper LB (built-in) |
| Database | etcd (embedded) | SQLite or etcd |
| Control Plane | Traditional Kubernetes | Simplified components |
| Airgap Support | Native | Native |
| ARM Support | Yes (arm64) | Yes (arm64, armhf) |
| Updates | Rolling updates | In-place upgrades |
| Configuration | YAML config file | CLI flags + config |
| Complexity | More modular | More integrated |
| Use Case | Flexible infrastructure | Edge/IoT focused |
When to Choose K0s vs K3s
Choose K0s if:
- You want maximum flexibility in component selection
- You prefer vanilla Kubernetes experience
- You plan to integrate custom CNI/storage solutions
- You’re building infrastructure for learning production Kubernetes
- You want clear separation between controller and worker nodes
- You prefer configuration via YAML over CLI flags
Choose K3s if:
- You want the simplest possible Kubernetes
- You need built-in load balancer (Klipper)
- You prefer opinionated, batteries-included defaults
- You’re deploying to edge devices or IoT
- You want the smallest possible footprint
- You prioritize simplicity over flexibility
Both are excellent for homelabs! K0s feels more like “real Kubernetes,” while K3s feels more like “Kubernetes made easy.”
Installation and Initial Setup
Single-Node Installation (Controller+Worker)
Perfect for learning and small homelabs.
Step 1: Download k0s binary
# Download latest k0s
curl -sSLf https://get.k0s.sh | sudo sh
# Verify installation
k0s version
# Output:
# v1.29.1+k0s.0 Step 2: Install k0s as a systemd service
# Install k0s controller+worker (all-in-one mode)
sudo k0s install controller --enable-worker
# Start k0s service
sudo k0s start
# Check status
sudo k0s status
# Output:
# Version: v1.29.1+k0s.0
# Process ID: 12345
# Role: controller+worker
# Workloads: true Step 3: Access your cluster
# Export kubeconfig
mkdir -p ~/.kube
sudo k0s kubeconfig admin > ~/.kube/config
# Install kubectl (if not already installed)
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
# Verify cluster access
kubectl get nodes
# Output:
# NAME STATUS ROLES AGE VERSION
# homelab-01 Ready <none> 2m v1.29.1+k0s Step 4: Deploy test application
# Create nginx deployment
kubectl create deployment nginx --image=nginx:alpine
# Expose as service
kubectl expose deployment nginx --port=80 --type=NodePort
# Get service port
kubectl get svc nginx
# Access nginx (replace 32000 with actual NodePort)
curl http://localhost:32000 Congratulations! You have a working Kubernetes cluster in under 10 minutes.
Multi-Node Installation (HA-Capable)
For production-like homelab setups with separate controller and worker nodes.
Topology example:
controller-01: 192.168.1.10 (controller)worker-01: 192.168.1.11 (worker)worker-02: 192.168.1.12 (worker)
Step 1: Install k0s on controller node
On controller-01:
# Download k0s
curl -sSLf https://get.k0s.sh | sudo sh
# Install as controller only (no workloads)
sudo k0s install controller
# Start k0s
sudo k0s start
# Verify
sudo k0s status Step 2: Generate worker join token
On controller-01:
# Create worker join token
sudo k0s token create --role=worker > worker-token.txt
# View token (long base64 string)
cat worker-token.txt Step 3: Join worker nodes
On worker-01 and worker-02:
# Download k0s
curl -sSLf https://get.k0s.sh | sudo sh
# Install as worker with token
sudo k0s install worker --token-file /path/to/worker-token.txt
# Start k0s worker
sudo k0s start
# Verify
sudo k0s status Step 4: Verify cluster
On controller-01:
# Export kubeconfig
mkdir -p ~/.kube
sudo k0s kubeconfig admin > ~/.kube/config
# Check nodes
kubectl get nodes
# Output:
# NAME STATUS ROLES AGE VERSION
# worker-01 Ready <none> 2m v1.29.1+k0s
# worker-02 Ready <none> 2m v1.29.1+k0s High-Availability Setup (3 Controllers)
For maximum reliability:
Step 1: Install first controller
On controller-01:
curl -sSLf https://get.k0s.sh | sudo sh
sudo k0s install controller
sudo k0s start Step 2: Generate controller join token
# Create controller join token
sudo k0s token create --role=controller > controller-token.txt Step 3: Join additional controllers
On controller-02 and controller-03:
curl -sSLf https://get.k0s.sh | sudo sh
sudo k0s install controller --token-file /path/to/controller-token.txt
sudo k0s start Step 4: Verify HA cluster
# Check etcd members
kubectl exec -n kube-system etcd-controller-01 -- etcdctl member list
# Output shows 3 etcd members Configuration and Customization
K0s Configuration File
K0s uses a single YAML configuration file:
Generate default config:
# Create default config
sudo k0s config create > k0s.yaml
# View config
cat k0s.yaml Example k0s.yaml:
apiVersion: k0s.k0sproject.io/v1beta1
kind: ClusterConfig
metadata:
name: k0s
spec:
api:
address: 192.168.1.10
port: 6443
storage:
type: etcd
etcd:
peerAddress: 192.168.1.10
network:
provider: kube-router
podCIDR: 10.244.0.0/16
serviceCIDR: 10.96.0.0/12
controllerManager:
extraArgs:
node-monitor-grace-period: 40s
scheduler:
extraArgs:
v: "2"
extensions:
helm:
repositories:
- name: stable
url: https://charts.helm.sh/stable
charts:
- name: metrics-server
chartname: metrics-server/metrics-server
version: "3.11.0"
namespace: kube-system Apply custom config:
# Install with custom config
sudo k0s install controller --config k0s.yaml
# Or update existing installation
sudo k0s stop
sudo k0s start --config /etc/k0s/k0s.yaml Network Provider Options
Kube-router (default):
- Lightweight, all-in-one networking
- BGP-based routing
- Network policy support
Calico:
- Advanced network policies
- Better for large-scale deployments
Enable Calico:
spec:
network:
provider: calico
calico:
mode: vxlan
vxlanPort: 4789
vxlanVNI: 4096 Storage Configuration
K0s doesn’t include default storage provisioner. Install one:
Option 1: OpenEBS (recommended for homelab):
# Install OpenEBS
kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml
# Verify installation
kubectl get pods -n openebs
# Set as default storage class
kubectl patch storageclass openebs-hostpath -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' Option 2: Local path provisioner:
# Install local-path provisioner
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.26/deploy/local-path-storage.yaml
# Set as default
kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' Test storage:
# test-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi kubectl apply -f test-pvc.yaml
kubectl get pvc
# Output:
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
# test-pvc Bound pvc-xxx 1Gi RWO openebs-hostpath Deploying Applications
Example 1: Simple Web Application
# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
type: LoadBalancer kubectl apply -f nginx-deployment.yaml
kubectl get deployments
kubectl get pods
kubectl get svc nginx Example 2: Stateful Application (PostgreSQL)
# postgres.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
data:
POSTGRES_DB: mydb
POSTGRES_USER: myuser
---
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
data:
POSTGRES_PASSWORD: bXlzZWNyZXRwYXNzd29yZA== # base64: mysecretpassword
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
envFrom:
- configMapRef:
name: postgres-config
- secretRef:
name: postgres-secret
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
selector:
app: postgres
ports:
- port: 5432
clusterIP: None kubectl apply -f postgres.yaml
kubectl get statefulsets
kubectl get pvc Example 3: Helm Chart Deployment
# Add Helm repo
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# Install WordPress
helm install my-wordpress bitnami/wordpress --set wordpressUsername=admin --set wordpressPassword=password --set mariadb.auth.rootPassword=secretpassword
# Check status
helm list
kubectl get pods Ingress and Load Balancing
Install Nginx Ingress Controller
# Install via Helm
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace --set controller.service.type=NodePort
# Verify installation
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx Create Ingress Resource
# nginx-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: nginx.homelab.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80 kubectl apply -f nginx-ingress.yaml
# Add to /etc/hosts
echo "192.168.1.10 nginx.homelab.local" | sudo tee -a /etc/hosts
# Access via browser
curl http://nginx.homelab.local Install MetalLB for LoadBalancer Services
# Install MetalLB
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml
# Wait for pods
kubectl wait --namespace metallb-system --for=condition=ready pod --selector=app=metallb --timeout=90s
# Configure IP pool
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: homelab-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.100-192.168.1.110
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: homelab-l2
namespace: metallb-system
spec:
ipAddressPools:
- homelab-pool
EOF Now LoadBalancer services get real IPs!
Cluster Management
Backup and Restore
Backup k0s configuration and etcd:
# Backup k0s data
sudo k0s backup --save-path /backup/k0s-backup.tar.gz
# Backup includes:
# - etcd snapshot
# - k0s configuration
# - certificates Restore from backup:
# Stop k0s
sudo k0s stop
# Restore backup
sudo k0s restore --from /backup/k0s-backup.tar.gz
# Start k0s
sudo k0s start Automated backup script:
#!/bin/bash
# /usr/local/bin/k0s-backup.sh
BACKUP_DIR="/backups/k0s"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
sudo k0s backup --save-path "$BACKUP_DIR/k0s-backup-$DATE.tar.gz"
# Keep only last 7 backups
find "$BACKUP_DIR" -name "k0s-backup-*.tar.gz" -mtime +7 -delete
echo "Backup completed: k0s-backup-$DATE.tar.gz" Schedule with cron:
# Daily backups at 2 AM
0 2 * * * /usr/local/bin/k0s-backup.sh Upgrading K0s
# Download new k0s version
curl -sSLf https://get.k0s.sh | sudo K0S_VERSION=v1.30.0+k0s.0 sh
# Stop current k0s
sudo k0s stop
# Backup before upgrade
sudo k0s backup --save-path /backup/pre-upgrade-backup.tar.gz
# Upgrade (preserves configuration)
sudo k0s install controller --enable-worker --force
# Start upgraded k0s
sudo k0s start
# Verify upgrade
k0s version
kubectl get nodes Monitoring with Prometheus
# Install kube-prometheus-stack via Helm
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install prometheus prometheus-community/kube-prometheus-stack --namespace monitoring --create-namespace --set prometheus.prometheusSpec.retention=30d
# Access Grafana
kubectl port-forward -n monitoring svc/prometheus-grafana 3000:80
# Default credentials: admin / prom-operator Troubleshooting Common Issues
Issue: Nodes Not Ready
# Check node status
kubectl get nodes
# Describe node for details
kubectl describe node <node-name>
# Check k0s logs
sudo journalctl -u k0scontroller -f
sudo journalctl -u k0sworker -f
# Check kubelet logs
kubectl logs -n kube-system -l component=kubelet Issue: Pods Stuck in Pending
# Describe pod
kubectl describe pod <pod-name>
# Common causes:
# 1. Insufficient resources
kubectl describe nodes | grep -A 5 "Allocated resources"
# 2. No storage class
kubectl get storageclass
# 3. Taints on nodes
kubectl describe nodes | grep Taints Issue: Network Problems
# Check CNI pods
kubectl get pods -n kube-system -l app=kube-router
# Test pod-to-pod networking
kubectl run test-1 --image=nicolaka/netshoot --rm -it -- bash
# Inside pod:
curl <another-pod-ip>
# Check DNS
kubectl run test-dns --image=busybox --rm -it -- nslookup kubernetes.default Issue: High Resource Usage
# Check resource usage
kubectl top nodes
kubectl top pods --all-namespaces
# Set resource limits on deployments
kubectl set resources deployment nginx --limits=cpu=200m,memory=512Mi --requests=cpu=100m,memory=256Mi Best Practices for Homelab K0s
1. Use Namespaces
# Create namespace for each application category
kubectl create namespace media
kubectl create namespace monitoring
kubectl create namespace databases
# Deploy to specific namespace
kubectl apply -f app.yaml -n media
# Set default namespace
kubectl config set-context --current --namespace=media 2. Label Everything
metadata:
labels:
app: nginx
environment: production
tier: frontend
managed-by: k0s 3. Use ConfigMaps and Secrets
# Create ConfigMap from file
kubectl create configmap app-config --from-file=config.yml
# Create Secret from literal
kubectl create secret generic db-password --from-literal=password=secret 4. Implement Resource Quotas
apiVersion: v1
kind: ResourceQuota
metadata:
name: media-quota
namespace: media
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
persistentvolumeclaims: "10" 5. Regular Backups
# Automated daily backups
# Backup both k0s and persistent data
# Test restore procedure quarterly Conclusion and Next Steps
K0s delivers production-grade Kubernetes with zero friction - perfect for homelabbers who want to learn Kubernetes without wrestling with installation complexity. By bundling all dependencies into a single binary while maintaining full upstream compatibility, k0s provides the best of both worlds: simplicity and completeness.
Key Takeaways
- Single binary: Everything needed in one ~250MB file
- Zero dependencies: No host OS packages required
- Full Kubernetes: 100% CNCF certified, upstream compatible
- Flexible deployment: Single-node to multi-node HA clusters
- Modular architecture: Choose your own CNI, storage, ingress
- Production-ready: Used in real production environments
When to Use K0s
Perfect for:
- Learning Kubernetes in homelab environment
- Building production-like clusters
- Flexible infrastructure requirements
- Wanting vanilla Kubernetes experience
- Multi-node cluster deployments
- Teams migrating to cloud Kubernetes later
Consider K3s if:
- Maximum simplicity is priority
- Edge/IoT deployment
- Smallest possible footprint
- Built-in defaults preferred
Recommended Workflow
- Start single-node: Deploy controller+worker on one machine
- Deploy applications: Practice with deployments and services
- Add storage: Install OpenEBS or local-path provisioner
- Set up ingress: Install nginx-ingress for HTTP routing
- Implement monitoring: Deploy Prometheus/Grafana stack
- Scale cluster: Add worker nodes as needed
- Enable backups: Automate daily k0s backups
Next Steps
Beginner:
- Install single-node k0s cluster
- Deploy sample applications
- Explore kubectl commands
- Set up storage provisioner
Intermediate:
- Build multi-node cluster
- Configure ingress controller
- Deploy stateful applications
- Implement monitoring stack
Advanced:
- Set up HA control plane (3 controllers)
- Implement GitOps with ArgoCD
- Configure advanced networking
- Build CI/CD pipelines
Related Guides
- K3s Guide - Alternative lightweight Kubernetes
- ArgoCD Guide - GitOps for Kubernetes
- Docker Basics - Container fundamentals
- Portainer Guide - Container management GUI
Resources
- Official Documentation: https://docs.k0sproject.io/
- GitHub Repository: https://github.com/k0sproject/k0s
- Community Slack: https://k8slens.slack.com/
- CNCF Project Page: https://www.cncf.io/projects/k0s/
- Kubernetes Documentation: https://kubernetes.io/docs/
Document Version: 1.0 Last Updated: December 16, 2024 Based on: k0s v1.29+ Estimated Completion Time: 75 minutes (basic setup), 8-10 hours (production cluster)
Happy orchestrating!