Kubernetes

Lightweight Kubernetes with k3s on DomainIndia VPS (Single & Multi-Node)

By Domain India Team · DomainIndia EngineeringPublished · Updated 6 min read
Knowledge base article
Contents (16 sections)
TL;DR

k3s is a production-grade Kubernetes distribution that runs on a single VPS with 1 GB RAM — perfect for learning K8s or running real workloads without the overhead of full Kubernetes. This guide covers single-node setup, multi-node cluster, Helm, Ingress with SSL, and persistent storage on DomainIndia VPS.

Why k3s instead of full Kubernetes

Standard Kubernetes (kubeadm) needs 2 GB+ RAM per node just for the control plane. k3s strips down to essentials:

  • Single binary (~60 MB)
  • Built-in SQLite (no etcd for single-node)
  • Built-in load balancer, Ingress (Traefik), local storage provisioner
  • Works on 512 MB RAM VPS (though 2 GB is practical minimum)

Same kubectl, same YAML, same Helm charts. Production-ready — k3s powers AWS EKS Anywhere, Rancher, and edge deployments.

When to use k3s

Use caseGood fit?
Learning KubernetesExcellent
Small production app (1-5 services)Great
Edge / IoT deploymentsDesigned for it
Large-scale microservices (100+ pods)Upgrade to full K8s or managed (EKS, GKE)

For most DomainIndia VPS customers: k3s is enough. Only upgrade to full K8s when you outgrow it.

Single-node setup

Order a 4 GB+ DomainIndia VPS (AlmaLinux 9 or Ubuntu 22.04 recommended).

  1. SSH as root
  2. Disable firewalld temporarily (we'll reconfigure after):
    bash
    sudo systemctl disable --now firewalld
  3. Install k3s:
    bash
    curl -sfL https://get.k3s.io | sh -
  4. Verify:
    bash
    sudo kubectl get nodes
    # NAME              STATUS   ROLES                  AGE   VERSION
    # your-vps-name     Ready    control-plane,master   30s   v1.30.0+k3s1
  5. Copy kubeconfig for your laptop:
    bash
    sudo cat /etc/rancher/k3s/k3s.yaml
    # Copy to ~/.kube/config on laptop, replace "server: https://127.0.0.1:6443"
    # with your VPS public IP
Info

By default k3s runs as root. For multi-tenant use, install rootless k3s or use RBAC to restrict namespaces.

Deploy your first app

Create app.yaml:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello
spec:
  replicas: 2
  selector:
    matchLabels: { app: hello }
  template:
    metadata:
      labels: { app: hello }
    spec:
      containers:
      - name: hello
        image: nginxdemos/hello:latest
        ports:
        - containerPort: 80
        resources:
          limits:
            memory: 128Mi
            cpu: 100m
---
apiVersion: v1
kind: Service
metadata:
  name: hello
spec:
  selector: { app: hello }
  ports:
  - port: 80
    targetPort: 80

Apply:

bash
kubectl apply -f app.yaml
kubectl get pods
# NAME                     READY   STATUS    RESTARTS   AGE
# hello-7c5cbb5d4f-abc12   1/1     Running   0          10s
# hello-7c5cbb5d4f-def34   1/1     Running   0          10s

Ingress with SSL (Traefik + Let's Encrypt)

k3s ships with Traefik ingress controller. Configure it to get Let's Encrypt certs automatically.

Edit /var/lib/rancher/k3s/server/manifests/traefik-config.yaml:

yaml
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
spec:
  valuesContent: |-
    ports:
      web:
        redirectTo:
          port: websecure
    additionalArguments:
      - --certificatesresolvers.letsencrypt.acme.email=admin@yourcompany.com
      - --certificatesresolvers.letsencrypt.acme.storage=/data/acme.json
      - --certificatesresolvers.letsencrypt.acme.tlschallenge=true

Wait 30 seconds for Traefik to reload.

Then define your Ingress:

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
spec:
  rules:
  - host: hello.yourcompany.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: hello
            port: { number: 80 }
  tls:
  - hosts: [hello.yourcompany.com]

Point DNS hello.yourcompany.com → your VPS IP. Traefik obtains SSL cert on first visit.

Multi-node cluster

For HA or scaling beyond one VPS:

Control plane (first node)

Install k3s + note the token:

bash
# On VPS 1 (master)
curl -sfL https://get.k3s.io | sh -
sudo cat /var/lib/rancher/k3s/server/node-token
# prints: K10xxxxx::server:abc123...

Worker nodes

bash
# On VPS 2, 3, ... (workers)
curl -sfL https://get.k3s.io | 
    K3S_URL=https://<master-vps-ip>:6443 
    K3S_TOKEN=K10xxxxx::server:abc123... 
    sh -

Verify from master:

bash
sudo kubectl get nodes
# NAME       STATUS   ROLES                  AGE
# master     Ready    control-plane,master   1h
# worker-1   Ready    <none>                 30s
# worker-2   Ready    <none>                 30s

HA control plane (optional)

For >99% uptime, run 3 control-plane nodes with external DB (etcd or PostgreSQL):

bash
curl -sfL https://get.k3s.io | sh -s - server 
    --cluster-init 
    --datastore-endpoint "postgres://k3s:[email protected]:5432/k3s"

Then join other masters with same token.

Persistent storage

k3s ships with local-path-provisioner — provisions HostPath volumes automatically.

yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: local-path
  resources:
    requests: { storage: 5Gi }

For multi-node with shared storage, use Longhorn (k3s-friendly distributed storage):

bash
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.6.0/deploy/longhorn.yaml

Helm charts

Install anything from Helm Hub (Redis, PostgreSQL, Prometheus, Grafana):

bash
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

helm repo add bitnami https://charts.bitnami.com/bitnami
helm install redis bitnami/redis --set auth.password=yourpass

# Use in deployment:
# REDIS_URL=redis://:yourpass@redis-master:6379/0

Monitoring with Prometheus + Grafana

One-liner install:

bash
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack 
    --set grafana.service.type=LoadBalancer 
    --set prometheus.prometheusSpec.resources.requests.memory=512Mi

Expose Grafana via Ingress + get the admin password:

bash
kubectl get secret monitoring-grafana -o jsonpath="{.data.admin-password}" | base64 -d

Backups

Back up etcd + PVs regularly.

For single-node k3s, /var/lib/rancher/k3s/server/db/state.db is your state — cron-rsync to S3:

bash
0 3 * * * tar czf /tmp/k3s-backup.tgz /var/lib/rancher/k3s/server/db /var/lib/rancher/k3s/storage && 
          rclone copy /tmp/k3s-backup.tgz remote:k3s-backups/

See our Automated Backups guide.

Security hardening

  • Change the k3s API port from 6443 if exposed; firewall to trusted IPs only
  • Use Network Policies to restrict pod-to-pod traffic
  • Enable RBAC (default in k3s); don't give users cluster-admin
  • Scan images for CVEs: trivy image your-app:latest
  • Keep k3s updated: curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.30.X+k3s1 sh -

Common pitfalls

"No space left on device"
container images fill /var/lib/rancher/k3s/agent/containerd/. Run k3s crictl rmi --prune regularly.
Traefik dashboard exposed
auto-enabled on :9000; secure with basic auth or disable.
No Ingress IP
k3s with Traefik binds to host ports 80/443. Firewall may block. sudo firewall-cmd --add-port=80/tcp --permanent && --add-port=443/tcp.
etcd write amplification
too many small writes degrade SQLite-backed k3s. Migrate to external etcd for busy clusters.
RAM exhaustion
Prometheus + Grafana + logs + app = fills 4 GB fast. Size resources explicitly, set requests/limits.
Uninstall leaves cruft
always use /usr/local/bin/k3s-uninstall.sh not manual file deletion.

FAQ

Can I use the same kubectl commands I learned for EKS/GKE?

Yes — 95%+ compatible. Some features (CSI drivers, cloud-specific integrations) differ.

Does k3s work with public Docker images?

Yes — pulls from Docker Hub, GHCR, GCR, etc. For private registries, add secrets.

Can I migrate from k3s to full K8s later?

Yes — kubectl manifests transfer. You may need to adjust storage classes and ingress controller choice.

How many nodes can k3s handle?

Up to ~50 nodes tested. For >50, consider RKE2 or upstream Kubernetes.

k3s vs MicroK8s vs minikube?

k3s: production-ready, minimal overhead. MicroK8s: Ubuntu-friendly, snap-based. minikube: dev/test only, don't run in production.

Kubernetes on a DomainIndia VPS — start lean with k3s. View VPS plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app
Lightweight Kubernetes with k3s on DomainIndia VPS