Kubernetes on a VPS: A Minimal Cluster with k3s
k3s is a lightweight Kubernetes distribution from Rancher Labs that requires only 512 MB of RAM. It is ideal for learning K8s and running small production clusters on a VPS with 4 GB of RAM or more.
Installing k3s (Master Node)
curl -sfL https://get.k3s.io | sh -
systemctl status k3s
# Check the cluster
kubectl get nodes
kubectl get pods -AAdding a Worker Node
# Get the token on the Master
cat /var/lib/rancher/k3s/server/node-token
# On the Worker node
curl -sfL https://get.k3s.io | K3S_URL=https://MASTER_IP:6443 K3S_TOKEN=TOKEN sh -Deploying an Application
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
replicas: 3
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: nginx:alpine
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: webapp-service
spec:
selector:
app: webapp
ports:
- port: 80
targetPort: 80
type: LoadBalancerkubectl apply -f deployment.yaml
kubectl get pods
kubectl get servicesHelm: The K8s Package Manager
# Install Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Install an application from a Helm Chart (e.g., cert-manager)
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=trueIngress + Auto-SSL
# Traefik (included with k3s by default)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: webapp-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- app.yourdomain.com
secretName: webapp-tls
rules:
- host: app.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp-service
port:
number: 80Real Errors and Pod Statuses Explained
Three pod statuses cover almost every question a Kubernetes newcomer has. Let's look at what each one means and how to find the cause with kubectl describe.
The Pod Is Stuck in Pending
kubectl describe pod webapp-7d9f8c-abcde
Events:
Warning FailedScheduling 12s default-scheduler 0/1 nodes are available: 1 Insufficient memory.The scheduler cannot find a node with enough of the requested resources from resources.requests. The fix: lower requests.memory in the manifest, add a worker node, or check whether resources are already taken by other pods with kubectl describe node.
CrashLoopBackOff
kubectl get pods
NAME READY STATUS RESTARTS AGE
webapp-7d9f8c-abcde 0/1 CrashLoopBackOff 6 4mThe container starts and immediately dies, and Kubernetes restarts it with an exponential backoff. The cause is almost always the application itself: check kubectl logs webapp-7d9f8c-abcde --previous — the --previous flag is essential, otherwise you see the logs of the new crashed process instead of the one before it.
ImagePullBackOff
Events:
Warning Failed 8s kubelet Failed to pull image "myrepo/app:v2": rpc error: code = Unknown desc = Error response from daemon: pull access denied for myrepo/app, repository does not exist or may require 'docker login'Either there's a typo in the image name or tag, or the image is private and the node has no configured imagePullSecrets. Create a secret with kubectl create secret docker-registry regcred --docker-server=... --docker-username=... --docker-password=... and reference it in spec.imagePullSecrets.
k3s vs. Other Lightweight Distributions
| Criterion | k3s | MicroK8s | Full kubeadm |
|---|---|---|---|
| Minimum RAM | 512 MB | 540 MB | 2 GB on the control plane |
| State database | SQLite (or etcd for HA) | dqlite | etcd |
| One-command install | Yes | Yes (snap) | No, several steps |
When k3s Is Not a Good Fit
- The project fits inside Docker Compose or Docker Swarm — the Kubernetes overhead does not pay off at a scale of 1-2 services.
- The team is unfamiliar with Kubernetes concepts and has no time to learn them — it is better to start with Docker Swarm.
- You need a strictly certified upstream Kubernetes build for compliance — k3s replaces some components (Traefik, servicelb, local-path-provisioner) with its own lightweight versions, and some audits will require disabling that.
- A VPS with less than 1 GB of RAM — even minimal k3s eats a significant share of memory; a regular stack without orchestration is enough for a website.
- You need GPU task scheduling with vendor-specific device plugins — you will have to configure those manually, since k3s has no ready-made integration.
Verifying the Result
# All nodes are in the Ready state
kubectl get nodes -o wide
# All system pods are running
kubectl get pods -A | grep -v Running
# An HTTP response through the Ingress
curl -I https://app.yourdomain.com
# The cert-manager certificate issuance status
kubectl get certificate -A
kubectl describe certificate webapp-tlsIf the grep -v Running command returns even one line (other than the header), some system pod did not come up — investigate with kubectl describe pod in the relevant namespace. Before using the cluster in production, set up external monitoring — it will show a service outage even if Kubernetes itself still considers the pod alive.