
How I Cut My Kubernetes Hosting Costs by Over 50% (and Got More Power)
Oursi.net is hosted on Kubernetes, and when I first deployed it, I used DigitalOcean’s managed Kubernetes service. My setup was minimal: one VM with 2 vCPUs and 4GB of RAM ($24), plus a load balancer ($12). Even with that basic configuration, my bill was roughly $50/month (taxes included) — pretty steep considering the very limited traffic the site gets.
So I looked for alternatives.
Exploring Cheaper Options
I considered running Kubernetes at home on a small PC. But I’m in France, and Orange (my ISP) charges nearly $20/month just for a fixed IP. That’s too much, and I didn’t want to deal with dynamic IP issues affecting DNS reliability.
Instead, I found Contabo, a VPS provider offering a beefy 8vCPU + 24GB RAM machine for $15/month — more than triple the resources I had on DigitalOcean for less than half the price. Add a couple more bucks for private networking between nodes, and it was still a steal.
Since I want to scale later with more nodes, having a private internal network made sense. I went ahead with that option.
Other providers have similar prices though, so pick whichever you like of course!
Why k3s?
I chose k3s because:
- It’s lightweight
- It's easy to install
- It has a built-in local path provisioner
- It plays nicely with GitOps (hello, ArgoCD!)
Setting Up the Control Plane Node
After spinning up an Ubuntu 24.04 VPS, here’s what I did:
Prepare the node:
1cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf 2overlay 3br_netfilter 4EOF 5 6modprobe overlay 7modprobe br_netfilter 8 9cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf 10net.bridge.bridge-nf-call-iptables = 1 11net.bridge.bridge-nf-call-ip6tables = 1 12net.ipv4.ip_forward = 1 13net.ipv6.conf.all.forwarding = 1 14EOF 15 16sysctl --system 17swapoff -a 18mkdir /local-path-provisioner 19hostnamectl set-hostname k3s-master
This sets up network modules, disables swap (required by kubelet), and prepares a directory for local persistent volumes.
Install k3s:
1export K3S_VERSION=v1.33.1+k3s1 2curl -sfL https://get.k3s.io | \ 3 INSTALL_K3S_VERSION=$K3S_VERSION sh -s - server \ 4--cluster-init \ 5--default-local-storage-path /local-path-provisioner \ 6--tls-san 194.163.138.155 \ 7--tls-san k3s.oursi.net \ 8--tls-san k3s-master.oursi.net \ 9--node-ip 10.0.0.1 \ 10--node-external-ip 194.163.138.155 \ 11--flannel-iface eth1 \ 12--disable traefik,metrics-server
Replace IPs and SANs with your actual values.
10.0.0.1
is my private network IP,194.163.138.155
is my public IP.
I created DNS records fork3s.oursi.net
andk3s-master.oursi.net
pointing to the public IP.
Don’t forget to run everything as root (sudo -i
).
Getting kubectl Access
Once the cluster is up, grab the kubeconfig file:
1scp root@k3s-master:/etc/rancher/k3s/k3s.yaml ~/.kube/config
And update:
1server: https://<your-public-ip-or-domain>:6443
GitOps FTW
I disabled the default installation of Traefik and metrics-server because I deploy all infrastructure using ArgoCD. This made the migration from DO to my new k3s cluster super smooth:
- Deploy ArgoCD on the new cluster
- Sync all apps from Git
- Done ✅
The Bottom Line
With this setup, I now pay less than $20/month, down from $50. And I get:
- More CPU and RAM
- Private networking for future worker nodes
- Full control over my infrastructure
- A much cheaper bill 💸
If you're running a low-traffic site or side project on Kubernetes, this kind of migration is definitely worth it.
At some point, I’ll look into setting up a more reliable storage provider than the local path provisioner. For now, it’s not a priority since my PostgreSQL database (deployed with the Zalando operator — see my other article) backs up to S3, and that’s the only truly critical data I need to preserve.