No alternative text provided

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 for k3s.oursi.net and k3s-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:

  1. Deploy ArgoCD on the new cluster
  2. Sync all apps from Git
  3. 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.