K3s Cluster on Proxmox
A complete, reproducible walkthrough for standing up a 3-node K3s Kubernetes cluster on Proxmox VE. We build a reusable Ubuntu cloud-init template on the Proxmox host, clone it into three VMs, then install K3s (one control plane + two workers).
The Proxmox commands below follow the official Proxmox VE Cloud-Init documentation.
Target Topology
Section titled “Target Topology”| Node | Role | IP Address | Resources |
|---|---|---|---|
k3s-control |
Control plane | 192.168.1.101 |
4 vCPU / 8 GB / 50 GB |
k3s-worker1 |
Worker | 192.168.1.102 |
4 vCPU / 8 GB / 50 GB |
k3s-worker2 |
Worker | 192.168.1.103 |
4 vCPU / 8 GB / 50 GB |
- Proxmox host:
192.168.1.100 - Gateway:
192.168.1.1 - Kubernetes API:
192.168.1.101:6443
Prerequisites
Section titled “Prerequisites”- A running Proxmox VE host (8.x or 9.x) with SSH/root access.
- Outbound internet from the host to download the Ubuntu cloud image.
- An SSH key pair on your workstation (
~/.ssh/id_ed25519.puborid_rsa.pub). - A storage pool that supports disk images (this guide uses
local-lvm).
Step 1 — Build the Ubuntu Cloud-Init Template
Section titled “Step 1 — Build the Ubuntu Cloud-Init Template”Run all commands in this section on the Proxmox host.
1.1 Download the cloud image
Section titled “1.1 Download the cloud image”# Ubuntu 24.04 LTS (Noble) cloud imagecd /var/lib/vz/template/isowget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img1.2 Create the VM and import the disk
Section titled “1.2 Create the VM and import the disk”We use VM ID 9000 as the template. The virtio-scsi-pci controller is
required for Ubuntu cloud images.
# Create a VM with 2 GB RAM and a VirtIO SCSI controllerqm create 9000 --name ubuntu-2404-template --memory 2048 --cores 2 \ --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-pci
# Import the downloaded disk to local-lvm as scsi0qm set 9000 --scsi0 local-lvm:0,import-from=/var/lib/vz/template/iso/noble-server-cloudimg-amd64.img1.3 Add the cloud-init drive and boot settings
Section titled “1.3 Add the cloud-init drive and boot settings”# Cloud-Init needs a CD-ROM drive to pass its configqm set 9000 --ide2 local-lvm:cloudinit
# Boot from the imported disk only (faster boot)qm set 9000 --boot order=scsi0
# Serial console + serial display (expected by most cloud images)qm set 9000 --serial0 socket --vga serial01.4 Set default cloud-init options
Section titled “1.4 Set default cloud-init options”# Default login user created inside cloned VMsqm set 9000 --ciuser devops
# Inject your SSH public key for key-based loginqm set 9000 --sshkey ~/.ssh/id_ed25519.pub
# Get an IP via DHCP by default (overridden per-clone below)qm set 9000 --ipconfig0 ip=dhcp
# Enable the QEMU guest agent (recommended)qm set 9000 --agent enabled=11.5 Convert to a template
Section titled “1.5 Convert to a template”qm template 9000You now have a reusable template. Linked clones from it deploy in seconds.
Step 2 — Clone the Three Cluster VMs
Section titled “Step 2 — Clone the Three Cluster VMs”Still on the Proxmox host. We assign VM IDs 101–103 and static IPs.
# Control planeqm clone 9000 101 --name k3s-controlqm set 101 --ipconfig0 ip=192.168.1.101/24,gw=192.168.1.1qm set 101 --memory 8192 --cores 4qm resize 101 scsi0 50G
# Worker 1qm clone 9000 102 --name k3s-worker1qm set 102 --ipconfig0 ip=192.168.1.102/24,gw=192.168.1.1qm set 102 --memory 8192 --cores 4qm resize 102 scsi0 50G
# Worker 2qm clone 9000 103 --name k3s-worker2qm set 103 --ipconfig0 ip=192.168.1.103/24,gw=192.168.1.1qm set 103 --memory 8192 --cores 4qm resize 103 scsi0 50GStart all three VMs:
qm start 101 && qm start 102 && qm start 103Give cloud-init 1–2 minutes on first boot, then confirm SSH works from your workstation:
ssh devops@192.168.1.101Step 3 — Install the K3s Control Plane
Section titled “Step 3 — Install the K3s Control Plane”SSH into k3s-control (192.168.1.101) and run:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \ --cluster-init \ --write-kubeconfig-mode 644 \ --tls-san 192.168.1.101" \ sh ---cluster-initinitializes an embedded etcd datastore (enables adding more servers later for HA).--write-kubeconfig-mode 644makes the kubeconfig readable so you can copy it out.--tls-sanadds the API IP to the server certificate.
Verify the control plane is up:
sudo k3s kubectl get nodesGrab the node token
Section titled “Grab the node token”You need this token to join the workers:
sudo cat /var/lib/rancher/k3s/server/node-tokenStep 4 — Join the Worker Nodes
Section titled “Step 4 — Join the Worker Nodes”SSH into each worker (192.168.1.102 and 192.168.1.103) and run,
substituting the token from the previous step:
curl -sfL https://get.k3s.io | \ K3S_URL=https://192.168.1.101:6443 \ K3S_TOKEN="<node-token-from-control-plane>" \ sh -The installer sets up the k3s-agent service and registers the node.
Step 5 — Verify the Cluster
Section titled “Step 5 — Verify the Cluster”Back on the control plane:
sudo k3s kubectl get nodes -o wideExpected output:
NAME STATUS ROLES AGE VERSIONk3s-control Ready control-plane,etcd,master 2m v1.31.x+k3s1k3s-worker1 Ready <none> 1m v1.31.x+k3s1k3s-worker2 Ready <none> 1m v1.31.x+k3s1Check the system pods:
sudo k3s kubectl get pods -AStep 6 — Remote kubectl Access
Section titled “Step 6 — Remote kubectl Access”Copy the kubeconfig to your workstation and point it at the control plane IP:
# On your workstationscp devops@192.168.1.101:/etc/rancher/k3s/k3s.yaml ./kubeconfig
# The file points at 127.0.0.1 — replace with the control-plane IPsed -i '' 's/127.0.0.1/192.168.1.101/' ./kubeconfig # macOS# sed -i 's/127.0.0.1/192.168.1.101/' ./kubeconfig # Linux
export KUBECONFIG=$PWD/kubeconfigkubectl get nodesManaging K3s
Section titled “Managing K3s”# Control plane service statussudo systemctl status k3s
# Worker agent status (on workers)sudo systemctl status k3s-agent
# Follow logssudo journalctl -u k3s -fUninstall (if you need to start over)
Section titled “Uninstall (if you need to start over)”# On the control plane/usr/local/bin/k3s-uninstall.sh
# On a worker/usr/local/bin/k3s-agent-uninstall.shTroubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | Fix |
|---|---|---|
| Clone has no IP | cloud-init didn’t apply | Confirm --ipconfig0 is set and the cloud-init CD-ROM (ide2) exists |
| SSH key rejected | Wrong key injected into template | Re-run qm set 9000 --sshkey ... and re-clone |
| Worker won’t join | Wrong token or API unreachable | Recheck the node-token and that :6443 is reachable from the worker |
kubectl connection refused remotely |
kubeconfig points at 127.0.0.1 |
Replace with 192.168.1.101 and add --tls-san on install |
Next Steps
Section titled “Next Steps”With the cluster running you can layer on:
- Traefik / Nginx ingress
- Longhorn for distributed block storage
- Argo CD for GitOps delivery
- Prometheus + Grafana for observability (SLOs, dashboards, alerting)
Once your cluster is exposed to a team, secure access to your tooling with single sign-on — see the SSO with SAML in SonarQube guide. For more about this lab and the rest of the stack, visit the about page.
Sources: Proxmox VE Cloud-Init Support, K3s Quick-Start.