k3s Lightweight Kubernetes for Indonesian Startups: Production Setup on Budget VPS

Photo by Unsplash

Photo by Unsplash
Full Kubernetes (k8s) demands at least 2GB RAM per node just for the control plane components — a significant constraint for Indonesian startups running on Biznet, IDCloudHost, or Niagahoster VPS tiers. k3s, Rancher's lightweight Kubernetes distribution, runs its control plane in under 500MB RAM and ships as a single binary with a built-in load balancer and optional SQLite backing store. This guide walks through a production-grade 3-node k3s cluster that runs real workloads for under Rp 600k/month total.
k3s achieves its small footprint by removing cloud-provider integrations (AWS EBS, GCE PD), alpha features, and legacy APIs that most clusters never use. It replaces etcd with SQLite for single-node setups (or uses embedded etcd for HA clusters) and bundles Flannel CNI, Traefik ingress, CoreDNS, and local-path-provisioner out of the box. The result is a fully conformant Kubernetes distribution that passes the CNCF conformance tests, meaning any standard kubectl commands and Kubernetes manifests work without modification.
A minimal k3s server node requires 512MB RAM and 1 vCPU, compared to a standard kubeadm installation that needs at least 2GB RAM for the control plane alone. k3s agent nodes can run on as little as 256MB RAM. This means a Rp 200k/month VPS (1 vCPU, 1GB RAM) from IDCloudHost can serve as both a k3s server and run lightweight workloads simultaneously — an option that simply does not exist with standard Kubernetes.
k3s is the right choice when your team has the operational capacity to manage a small cluster, your workloads fit on 3–5 nodes, and managed Kubernetes costs (Google GKE Autopilot, DigitalOcean Kubernetes) exceed your budget. For Indonesian startups with 2–5 engineers, a k3s cluster eliminates cloud vendor lock-in and reduces monthly Kubernetes overhead from Rp 400k+ (GKE cluster management fee) to zero — the only cost is the underlying VPS instances.
Disable Traefik during k3s server installation (--disable traefik) if you plan to use a different ingress controller. Replacing Traefik after installation is possible but involves more steps. NGINX Ingress Controller is a common replacement; add '--set controller.service.type=LoadBalancer' and configure MetalLB to assign an IP from your VPS's IP range.
k3s installation is a single curl command that downloads, installs, and starts the k3s service with systemd. The server node (control plane) exposes the Kubernetes API on port 6443 and generates a node token that agent nodes use to join the cluster. For a production 3-node setup, use 1 server node and 2 agent nodes — this provides workload redundancy without the complexity of an HA control plane that requires 3 server nodes and an external etcd.
The installation script accepts environment variables and flags that customize the k3s configuration without requiring a separate config file. Passing --tls-san with your server's public IP or domain ensures the API server certificate includes the correct SAN, which is required for kubectl access from outside the cluster. After installing agent nodes, verify the cluster with kubectl get nodes -o wide — all nodes should show Ready status within 30 seconds.
#!/bin/bash
# 1. Install k3s server node (control plane)
curl -sfL https://get.k3s.io | sh -s - server \
--disable traefik \
--flannel-backend=vxlan \
--tls-san=YOUR_PUBLIC_IP
# 2. Retrieve node token for agents
K3S_TOKEN=$(sudo cat /var/lib/rancher/k3s/server/node-token)
echo "Token: $K3S_TOKEN"
# 3. Join agent nodes (run on each worker VPS)
curl -sfL https://get.k3s.io | K3S_URL=https://SERVER_IP:6443 \
K3S_TOKEN=$K3S_TOKEN sh -
# 4. Verify cluster
kubectl get nodes -o wide
---
# sample-deployment.yaml — runs on k3s unchanged
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
spec:
replicas: 2
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: nginx:1.27-alpine
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: web-app-svc
namespace: production
spec:
selector:
app: web-app
ports:
- port: 80
targetPort: 80
type: ClusterIPk3s writes the kubeconfig file to /etc/rancher/k3s/k3s.yaml with server address set to 127.0.0.1. Copy this file to your local machine and replace 127.0.0.1 with the server's public IP. Set KUBECONFIG=/path/to/k3s.yaml or merge it into ~/.kube/config using kubectl config view --merge. Never expose the Kubernetes API port (6443) publicly without IP allowlisting — use a firewall rule to restrict it to your team's IP addresses only.
k3s's default local-path-provisioner creates PersistentVolumes on the local disk of whichever node a pod is scheduled on — if that node fails, the data is lost. Longhorn provides distributed block storage that replicates volumes across multiple nodes, enabling pod rescheduling without data loss. It integrates as a StorageClass, meaning existing Kubernetes PersistentVolumeClaims work without modification once Longhorn is installed.
Longhorn installs via a single kubectl apply or Helm chart and provides a web UI at /longhorn-ui for monitoring volume health, replica placement, and backup status. Set the default replica count to 2 for a 3-node cluster (replicates across 2 of 3 nodes) — setting it to 3 works but leaves no margin for a node to be down during maintenance. Configure an S3-compatible backup target (Wasabi or Cloudflare R2 are cost-effective choices from Indonesia) for disaster recovery snapshots.
k3s ships with Traefik as the default ingress controller, but it does not automatically provision TLS certificates. Services exposed via Ingress without TLS transmit all data including session cookies in plaintext. Install cert-manager (kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml) and configure a ClusterIssuer with Let's Encrypt before creating any publicly accessible Ingress resources. This takes 10 minutes but protects every service in the cluster.
Deploying to k3s from GitHub Actions requires exposing the API server (port 6443) or using a tunnel solution like Cloudflare Tunnel or ngrok to avoid public exposure. Store the kubeconfig as a GitHub Actions secret, then use the azure/setup-kubectl action to run kubectl apply in your pipeline. A complete deploy workflow takes under 2 minutes: build Docker image, push to registry, update the Deployment image tag with kubectl set image, and verify rollout with kubectl rollout status.
On budget VPS nodes, forgetting resource requests and limits is the fastest path to an unstable cluster. Without limits, a misbehaving pod can consume all available memory and trigger the OOM killer, evicting other pods. Define requests (the amount reserved for scheduling) and limits (the hard cap) for every deployment. A well-tuned web service on a 1GB RAM node typically uses 64Mi requests / 256Mi limits for memory and 50m requests / 500m limits for CPU.
k3s supports in-place upgrades using the system-upgrade-controller — a Kubernetes-native upgrade operator that cordons a node, drains its pods, upgrades k3s, and uncordons it, one node at a time. Define an upgrade Plan object specifying the target k3s version and the controller handles the rolling upgrade automatically. For a 3-node cluster this entire process takes 5–10 minutes with no downtime for applications configured with at least 2 replicas and a PodDisruptionBudget.
Use k3s's built-in kubeconfig embedded in /etc/rancher/k3s/k3s.yaml alongside Lens or OpenLens (free Kubernetes IDE) for a GUI cluster management experience. Lens auto-detects resource limits, shows real-time CPU/memory graphs per pod, and provides a terminal directly into any container — significantly faster for debugging than raw kubectl commands.
A 3-node k3s cluster on IDCloudHost or Biznet runs approximately Rp 600k–900k/month depending on the VPS tier — comparable to a single $10 DigitalOcean droplet but with full Kubernetes orchestration. As your workload grows, add agent nodes incrementally: k3s node addition takes under 5 minutes. When you outgrow 5–6 nodes, migrating to a managed Kubernetes service becomes economical as the operational overhead of managing more nodes exceeds the cost savings.
Deploy the kube-prometheus-stack Helm chart (Prometheus Operator + Grafana + Alertmanager) to get full cluster observability with pre-built dashboards for node CPU, memory, disk, and Kubernetes workload metrics. On budget nodes, reduce Prometheus retention to 7 days and set resource requests to 200m CPU / 512Mi RAM to avoid impacting workload pods. The Grafana k3s dashboard (ID 15282) provides k3s-specific metrics including the embedded SQLite database size and Flannel network statistics.
Key terms in this article include k3s, Longhorn, etcd, and Flannel CNI.