Kubernetes turns 12 this year. In that time, it’s gone from a Google side project to the operating system of modern infrastructure  running everywhere from mainframes to GPUs, across multi-cloud, hybrid, on-prem, and edge environments. The CNCF landscape has grown alongside it, filling in the gaps that Kubernetes left open.

This blog isn’t about all of those gaps. It’s about one specific intersection: lightweight Kubernetes with K3s on an on-premise infrastructure (in this case Proxmox) and declarative multi-cluster management with k0rdent.

If you’ve run Kubernetes on on-prem infrastructure, you know the pain:

We wanted something declarative, repeatable, and clean, but still friendly to an on-prem setup. That’s where k0rdent, Proxmox, and K3s came together.

In this blog, we’ll walk through how we curated a use case for provisioning and used k0rdent to provision a K3s cluster on an On-premise environment  by writing our own Helm charts and using k0rdent’s Bring Your Own Template (BYOT) approach. This isn’t a theoretical post, this is exactly how our cluster gets created.

The Big Picture

Here’s what we set out to build:

At a high level, the flow looks like this:

User → k0rdent

Proxmox Infrastructure (BYOT VMs)

Control Plane Provider

Bootstrap Provider (K3s)

Running Kubernetes Cluster

Each layer does one job, and does it well.

Why On-Prem + k0rdent Makes Sense

Proxmox is one of the examples of a self-hosted environment, but Kubernetes on-prem often ends up being hand-crafted, hard to scale, and harder to reproduce.

k0rdent changes that mindset. Instead of scripting how things should happen, you describe what you want and let reconciliation do the work.

k0rdent offers a clear separation of concerns:

  1. Infrastructure — provision the VMs
  2. Control Plane — configure the cluster topology
  3. Bootstrap — install and initialise Kubernetes

That separation made Proxmox integration surprisingly clean.

Step 1: Infrastructure Provider (BYOT)

k0rdent doesn’t ship with a native Proxmox provider, so we built one.

We created a custom Helm chart that acts as an Infrastructure Provider for Proxmox.

Why Bring Your Own Template?

Instead of dynamically building VM images on every provision, I used existing Proxmox VM templates. My templates already had cloud-init enabled, SSH access configured, and base OS packages installed.

This gave me:

What the Helm Chart Does

The Proxmox infrastructure chart is intentionally scoped — it only handles infrastructure:

No Kubernetes logic here. Just clean VM provisioning.

Step 2: Control Plane Provider

Helm charts for the Proxmox provider are available here: https://github.com/Improwised/charts/tree/main/charts/cluster-api-provider-proxmox

Once the VMs exist, the Control Plane Provider takes over. Its job is to:

In our setup, control plane nodes run on Proxmox VMs, with VM details flowing directly from the infrastructure provider.

 Roles are assigned declaratively, the cluster feels intentional, not accidental.

 Roles are assigned declaratively, the cluster feels intentional, not accidental.
Control Plane Provider: Nodes run on Proxmox VMs, with VM details flowing directly from the infrastructure provider.

Step 3: Bootstrapping Kubernetes with K3s

For bootstrapping, I chose K3s — and it fits perfectly here.

Why K3s? It’s lightweight, fast to install, has minimal dependencies, and is purpose-built for on-prem and edge environments.

Here’s what the provider definitions look like:

apiVersion: operator.cluster.x-k8s.io/v1alpha2

kind: BootstrapProvider

metadata:

  name: k3s

spec:

  version: v0.3.0

  fetchConfig:

    url: https://github.com/k3s-io/cluster-api-k3s/releases/v0.3.0/bootstrap-components.yaml

  {{- if .Values.configSecret.name }}

  configSecret:

    name: {{ .Values.configSecret.name }}

    namespace: {{ .Values.configSecret.namespace | default .Release.Namespace | trunc 63 }}

  {{- end }}

apiVersion: operator.cluster.x-k8s.io/v1alpha2

kind: ControlPlaneProvider

metadata:

  name: k3s

spec:

  version: v0.3.0

  fetchConfig:

    url: https://github.com/k3s-io/cluster-api-k3s/releases/v0.3.0/control-plane-components.yaml

  {{- if .Values.configSecret.name }}

  configSecret:

    name: {{ .Values.configSecret.name }}

    namespace: {{ .Values.configSecret.namespace | default .Release.Namespace | trunc 63 }}

  {{- end }}
Bootstrapping Kubernetes with K3s

What the Bootstrap Provider Does

The Bootstrap Provider Helm chart handles the K3s lifecycle:

  1. Installs K3s on the first control plane node
  2. Extracts the cluster token
  3. Joins additional control plane and worker nodes
  4. Generates kubeconfig access

Once this step completes, Kubernetes is up and running.

How k0rdent Ties It All Together

By using this method,  the system continuously reconciles the desired state:

  1. Provisions Proxmox VMs
  2. Waits for them to become reachable
  3. Passes VM data to the control plane provider
  4. Triggers the K3s bootstrap process
  5. Keeps watching — and corrects drift

The result: a fully declarative and managed K3s cluster on your on-premise environment.

What You End Up With

After everything settles, you have:

Scaling the cluster is no longer a weekend project. Rebuilding it is no longer terrifying. It’s just configuration.

Running Kubernetes on-prem doesn’t have to mean duct tape and bash scripts. With k0rdent’s BYOT approach, your On-prem infrastructure becomes a first-class citizen; declarative, reproducible, and treated as a first-class citizen.. If your infrastructure isn’t supported out of the box, build the template. That’s the whole point.