Introduction
K3s is Kubernetes but:
- Single 90 MB binary (vs 200+ MB for kubeadm)
- SQLite by default (no etcd cluster needed)
- Integrated containerd (no Docker)
- Helm chart, Traefik, MetricsServer, ServiceLB included
- Runs on Raspberry Pi
- CNCF-certified (upstream K8s compliant)
Use cases:
- Edge / IoT
- Small clusters (1-10 nodes)
- Dev / Test
- Single-node prod (homelab, personal sites)
Prerequisites
- Linux VPS Debian / Ubuntu / Rocky
- 1 GB RAM minimum (2 GB recommended)
- Root access
Step 1: Single-node install (server)
curl -sfL https://get.k3s.io | sh -
sudo k3s kubectl get nodes
K3s installs: kubelet, kube-apiserver, etcd/SQLite, kube-scheduler, kube-controller-manager, containerd, Traefik, Local Storage Provisioner.
Step 2: kubectl for your user
mkdir -p ~/.kube
sudo cat /etc/rancher/k3s/k3s.yaml > ~/.kube/config
chmod 600 ~/.kube/config
kubectl get nodes
Step 3: First app
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80 --type=LoadBalancer
kubectl get svc
curl http://VPS_IP:NODE_PORT
K3s has integrated LoadBalancer (klipper-lb) that does host-port.
Step 4: Add worker node (agent)
Get token on server:
sudo cat /var/lib/rancher/k3s/server/node-token
On worker:
curl -sfL https://get.k3s.io | K3S_URL=https://SERVER_IP:6443 K3S_TOKEN=YOUR_TOKEN sh -
kubectl get nodes
Step 5: HA cluster (3+ servers)
First server:
curl -sfL https://get.k3s.io | sh -s - server --cluster-init
Server 2 and 3:
curl -sfL https://get.k3s.io | sh -s - server --server https://SERVER1_IP:6443 --token YOUR_TOKEN
3 control-plane nodes (HA via etcd).
Step 6: Deploy app via YAML
myapp.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: nginx:alpine
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 80
type: ClusterIP
kubectl apply -f myapp.yaml
kubectl get pods
kubectl get svc
Step 7: Ingress with Traefik
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
spec:
rules:
- host: myapp.your-domain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80
kubectl apply -f ingress.yaml
Step 8: HTTPS auto with cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml
ClusterIssuer:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: traefik
Add TLS annotation to ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- myapp.your-domain.com
secretName: myapp-tls
rules:
- host: myapp.your-domain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80
Step 9: Persistent storage
K3s ships with Local Path Provisioner:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myapp-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 10Gi
For replicated storage, check Longhorn.
Step 10: Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install myrelease bitnami/postgresql
Step 11: Monitoring
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install kube-prom prometheus-community/kube-prometheus-stack
kubectl port-forward svc/kube-prom-grafana 3000:80
Default creds: admin / prom-operator.
Step 12: Uninstall
sudo /usr/local/bin/k3s-uninstall.sh # server
sudo /usr/local/bin/k3s-agent-uninstall.sh # agent
Troubleshooting
"Unable to connect to the server"
sudo systemctl status k3s
kubectl config view
Pods CrashLoopBackOff
kubectl describe pod pod-name
kubectl logs pod-name
Often: missing image, wrong env var, insufficient resources.
Disk pressure
df -h
kubectl top nodes
sudo k3s crictl rmi --prune
Networking issues
sudo k3s check-config
Useful commands
sudo systemctl status k3s
sudo systemctl restart k3s
sudo journalctl -u k3s -f
kubectl get nodes -o wide
kubectl describe node name
kubectl get pods -A
kubectl logs pod-name -n namespace
kubectl exec -it pod-name -- sh
kubectl apply -f file.yaml
kubectl delete -f file.yaml
kubectl get svc -A
kubectl get ingress -A
sudo cat /var/lib/rancher/k3s/server/node-token
sudo k3s crictl ps
sudo k3s crictl images
Conclusion
K3s gives you Kubernetes without complexity:
- One-command install
- Production-ready even single-node
- HA possible with embedded etcd
- Full upstream K8s compatibility
Limits:
- SQLite limited to a few hundred pods (use etcd beyond)
- Less integrated tooling than managed K8s
Going further:
- Use Rancher to manage multiple K3s clusters
- Combine with ArgoCD for GitOps
- For edge/IoT, check K3OS (minimalist K3s OS)
Resources
- Official docs: https://docs.k3s.io
- Source: https://github.com/k3s-io/k3s
- Rancher: https://rancher.com
- K3OS: https://k3os.io

















