Set up a lightweight Kubernetes cluster with k3s on a VPS

Kubernetes is the standard for container orchestration, but a full kubeadm-based cluster with etcd, multiple control-plane components, and a CNI layer can easily pull 2 to 4 GB of RAM before running a single application. On a VPS with limited resources, say 2 GB or 4 GB of RAM, that overhead eats into your budget before you deploy anything useful. K3s solves this by packaging Kubernetes into a single ~100 MB binary, replacing etcd with an embedded SQLite database (or external PostgreSQL), bundling containerd and Flannel, and removing cloud-controller-manager bloat. This post walks through installing k3s on a Debian 12 VPS, configuring a single-node cluster, enabling auto-upgrades, and deploying a test workload. You will have a production-viable cluster in less than 10 minutes.
越南 VPS 上部署 k3s 可以将 Kubernetes 资源开销降至 512 MB 以下,适合资源有限的场景。
Deploying k3s on a Vietnam VPS reduces Kubernetes resource overhead to under 512 MB, making it viable on budget VPS plans.
Prerequisites
- A Linux VPS running Debian 12 (or Ubuntu 24.04, the commands are the same). A 2 GB RAM, 2 vCPU instance is the practical minimum for k3s.
- Root access via SSH. All commands below assume you are logged in as root or can sudo to root.
- A registered domain (optional, for the Traefik ingress example).
- Ports 6443 (kube-apiserver), 10250 (kubelet), 80, and 443 open in the VPS firewall.
Why k3s instead of full Kubernetes
K3s is not a fork or a stripped-down toy, it is a CNCF-certified Kubernetes distribution that passes the same conformance tests as upstream Kubernetes. The differences are pragmatic: the control plane components run in a single binary, the default datastore is SQLite (not etcd), and the container runtime is containerd (not Docker, though Docker can be used too). On a fresh Debian 12 VPS with 2 GB RAM, a k3s server uses roughly 500 to 600 MB of RAM at idle. A kubeadm-based cluster with etcd uses at least double that. For a single-node development environment, a CI/CD runner, or a production cluster that does not need multi-master HA, k3s is the right choice.
The trade-off: you lose the built-in high-availability of etcd in the default mode. If the node goes down, the cluster goes down. For a production cluster that needs HA, you can run k3s with an external PostgreSQL or etcd backend, but then the simplicity argument weakens. K3s fits best where you need a full, compatible Kubernetes API without the infrastructure cost.
Step 1, Install k3s on the VPS
The official install script downloads the k3s binary, sets up systemd units, and starts the server. Run this as root:
curl -sfL https://get.k3s.io | sh -
The script detects the OS, fetches the latest stable k3s binary (~100 MB), writes service files to /etc/systemd/system/k3s.service, and starts the server. By default, k3s uses containerd as the container runtime and Flannel (via wireguard-native or vxlan) for networking. The kubeconfig file is placed at /etc/rancher/k3s/k3s.yaml.
By default, k3s also installs Traefik as the ingress controller and CoreDNS for cluster DNS. If you do not want the bundled ingress, pass --no-deploy traefik to the install script via the INSTALL_K3S_EXEC environment variable.
Verify that the cluster is running:
k3s kubectl get nodes
Expected output:
NAME STATUS ROLES AGE VERSION
vps-01 Ready control-plane,master 1m v1.31.5+k3s1
If the node stays in NotReady for more than 30 seconds, check the k3s service logs:
journalctl -u k3s --no-pager -n 50
Step 2, Configure kubectl for the regular user
By default, kubectl is installed as k3s kubectl. To use a plain kubectl command (compatible with standard tooling), copy the kubeconfig file to your user home and set the KUBECONFIG variable:
mkdir -p $HOME/.kube
sudo cp /etc/rancher/k3s/k3s.yaml $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
export KUBECONFIG=$HOME/.kube/config
Add the export line to $HOME/.bashrc to make it persistent:
echo 'export KUBECONFIG=$HOME/.kube/config' >> $HOME/.bashrc
Now test with a plain kubectl:
kubectl get nodes
If the command does not exist, install the kubectl binary manually or use the alias k3s kubectl. The k3s install does not create a symlink to plain kubectl, you need to add it yourself or use the wrapper.
Step 3, Enable auto-upgrades with systemd
K3s supports an automated upgrade mechanism using a systemd timer that checks for new releases daily. This is especially important for a VPS where you may not log in frequently. Enable it:
systemctl enable --now k3s-upgrade
This timer runs the k3s-upgrade script which pulls the latest release binary and restarts the k3s service. You can check the timer status:
systemctl list-timers --no-pager | grep k3s
Expected output:
Sun 2026-05-10 06:00:00 UTC 4h left 6min left k3s-upgrade.timer
For a single-node cluster, auto-upgrades are safe and recommended. For multi-node clusters, use the system-upgrade-controller approach instead.
Step 4, Deploy a test workload
Create a simple Nginx deployment and expose it via NodePort to confirm the cluster works end-to-end.
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test
spec:
replicas: 2
selector:
matchLabels:
app: nginx-test
template:
metadata:
labels:
app: nginx-test
spec:
containers:
- name: nginx
image: nginx:1.26
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-test
spec:
type: NodePort
selector:
app: nginx-test
ports:
- port: 80
targetPort: 80
nodePort: 30080
EOF
Wait for the pods to become ready:
kubectl get pods -w
Once both pods show Running, test the service using the VPS public IP:
curl http://<VPS_PUBLIC_IP>:30080
You should see the default Nginx welcome page. If curl times out, check that port 30080 is open in the VPS firewall:
iptables -L INPUT -n | grep 30080
# or for nftables-based setups:
nft list ruleset | grep 30080
Step 5, Expose your app with the bundled Traefik ingress
K3s ships with Traefik as the default ingress controller. To expose the Nginx test app using a hostname (e.g., nginx.example.com), create an Ingress resource:
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
spec:
ingressClassName: traefik
rules:
- host: nginx.example.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: nginx-test
port:
number: 80
EOF
Create an A record for nginx.example.com pointing to your VPS public IP. Traefik listens on ports 80 and 443 by default. If you need TLS, you can annotate the Ingress with Cert-Manager or use Traefik's built-in ACME support (configured in the HelmChartConfig).
Test with curl:
curl -H "Host: nginx.example.com" http://<VPS_PUBLIC_IP>
If you see the Nginx page, the ingress is working. Without a proper DNS record, the request will resolve to the VPS IP, but Curl will keep the Host header making this test work.
Step 6, Backup the cluster state
K3s stores its cluster state in the SQLite database at /var/lib/rancher/k3s/server/db/state.db. Backing up this file is sufficient to restore the cluster, including all deployments, ConfigMaps, secrets, and RBAC bindings, on a new node.
sudo cp /var/lib/rancher/k3s/server/db/state.db /root/k3s-backup-$(date +%F).db
To restore: install k3s on a fresh VPS, stop the service, replace the state.db file, and restart:
systemctl stop k3s
cp /root/k3s-backup-2026-05-10.db /var/lib/rancher/k3s/server/db/state.db
systemctl start k3s
Automate daily backups with a cron job or a systemd timer. Store the backup off-node, e.g., on an S3 bucket or a separate backup VPS with rsync. Without a backup, a broken disk means rebuilding the entire cluster from scratch.
Resource usage and scaling to a multi-node cluster
K3s is intentionally lean on a single node. At idle with the default deployments (Traefik, CoreDNS, local-path-provisioner, metrics-server), a 2 GB VPS uses roughly 550 to 650 MB of RAM. After deploying the Nginx test workload plus a couple of small apps, it stays under 1 GB. This leaves room for a medium-sized application like a WordPress site, a GitLab runner, or an n8n automation instance, all within a single 2 GB Linux VPS.
When you outgrow a single node, add more VPS instances as worker nodes. On the new VPS, run:
curl -sfL https://get.k3s.io | K3S_URL=https://<SERVER_IP>:6443 K3S_TOKEN=<NODE_TOKEN> sh -
The node token is stored on the server at /var/lib/rancher/k3s/server/node-token. The worker node automatically registers with the server. Each worker adds its own resources to the cluster, available to schedule pods via the standard Kubernetes scheduler. For a production multi-node setup, switch the datastore from SQLite to an external PostgreSQL or etcd cluster to maintain HA if the server node fails.
For single-node VPS scenarios, consider a VPS with n8n pre-installed if automation is your primary use case, though running n8n inside k3s is equally valid and more portable.
Troubleshooting
k3s service fails with "failed to get node" or "failed to connect to the apiserver"
Check the k3s logs:
journalctl -u k3s --no-pager -n 100
Common causes: the VPS has less than 1.5 GB of RAM and the OOM killer killed k3s, or the data directory is on a filesystem with insufficient space. Ensure at least 10 GB free on the root partition. If the VPS is running on a low- or standard-tier virtualization that lacks native support for Flannel (VXLAN), switch to a different CNI plugin like Calico.
Pods stuck in ContainerCreating or CrashLoopBackOff
Check pod events:
kubectl describe pod <pod-name>
If the containerd image pull fails, verify that the VPS can reach Docker Hub or your private registry. On a VPS behind a restrictive firewall, you may need to pull images from a mirror. K3s uses containerd, not Docker; check containerd's config at /etc/rancher/k3s/registries.yaml if you need to configure a mirror.
FAQ
Can k3s run on a 1 GB RAM VPS?
Yes, but barely. k3s itself will use ~400 to 500 MB at idle, leaving only ~500 MB for workloads. A 1 GB VPS is fine for testing but not for running any real application alongside the cluster. The practical minimum for a usable cluster is 2 GB RAM and 2 vCPUs.
Does k3s support GPU workloads?
K3s itself does not add GPU support, but it uses containerd, which supports nvidia-container-toolkit. Install the NVIDIA drivers and containerd runtime on the host, then use a toleration and resource limit to schedule GPU-accelerated pods. This is the same process as upstream Kubernetes.
How do I switch the datastore from SQLite to PostgreSQL?
Reinstall k3s with the --datastore-endpoint flag pointing to a PostgreSQL connection string. The migration must be done at install time; there is no built-in tool to migrate an existing SQLite state.db to PostgreSQL. Plan ahead: if you expect to need HA in the future, start with PostgreSQL from day one.
Is k3s production-safe for a single-node VPS?
Yes, for workloads that can tolerate the node going down. A single-node cluster has a single point of failure; if the VPS crashes, all services are down until it comes back. For production that requires uptime, add a second worker node or run two server nodes with an external datastore. K3s is used in production at edge locations, IoT gateways, and CI/CD runners every day.
Can I run k3s alongside Docker on the same VPS?
Yes. K3s uses containerd, not Docker. Docker containers and k3s pods are isolated by separate runtimes and namespaces. They share the same kernel and network namespace (unless the pods use hostNetwork). This is common: run your infrastructure as k3s pods and legacy apps as Docker containers on the same host.
Related articles
- Self-hosting n8n for workflow automation on a VPS
- Deploy CI/CD to a VPS with GitHub Actions and SSH
- Run multiple sites on one VPS with Docker and Nginx Proxy
k3s轻量级Kubernetes集群部署指南
k3s是一个CNCF认证的轻量级Kubernetes发行版,将控制平面组件压缩为单个二进制文件,默认使用SQLite替代etcd存储集群状态。在越南VPS上部署k3s可将Kubernetes基线资源消耗降至约500 to 600 MB,适合2 GB内存的VPS。本文完整演示了安装、配置kubectl、启用自动升级、部署Nginx工作负载以及使用Traefik Ingress暴露服务的全过程。单节点集群适合开发、CI/CD或边缘场景,如需高可用可在安装初期就选择外部PostgreSQL作为数据存储。


