Opinionated infrastructure repo for bringing up a highly available k3s cluster on top of Proxmox VMs with one workflow.
After you initialize the two local config files, just up provisions the VMs, prepares the operating system, bootstraps HA k3s, and installs the core platform services.
If you want a reproducible HA k3s setup on Proxmox without spending time on glue code, this repo gives you a working path out of the box.
Instead of manually combining VM provisioning, cloud-init, node preparation, k3s bootstrap, ingress, storage, certificates, and upgrades, you get one opinionated workflow with sensible defaults and a small configuration surface.
The main value is not just that it installs Kubernetes. The value is that it removes the repetitive work around Kubernetes on Proxmox:
- no hand-maintained inventory or stage-specific config files
- no manual install order across Terraform, Ansible, and Helm
- no separate setup for ingress, storage, cert-manager, and upgrade management
- no need to re-figure out a HA layout every time you build a new lab
- Bring up a complete HA
k3splatform on Proxmox withjust up - Keep local configuration limited to
cluster.tfvarsandcluster.secrets.tfvars - Scale the cluster shape up or down by changing
cluster_nodesfor control-plane and worker VMs - Reuse generated inventory and derived values instead of editing multiple stage configs by hand
- Get opinionated defaults for kube-vip, Traefik, cert-manager, Longhorn, and upgrade management
- Prepare every node automatically, including unattended APT security updates
- Set up Longhorn disks automatically on worker nodes, including partitioning, formatting, mounting, and persistence
- Re-run the full lifecycle through stable
justcommands instead of ad-hoc shell history
If you do not already have a cloud-init capable Debian template on each Proxmox node, you can create one with the optional 00-create-template stage.
This stage uses Ansible against the Proxmox hosts directly and creates a Debian Trixie template that:
- downloads the official Debian Trixie generic image
- imports it with
qm importdisk - enables Cloud-Init
- enables the Proxmox QEMU guest agent flag
- marks the VM as a reusable Proxmox template
The stage is intentionally fail-fast:
- if the configured VM ID already exists on a node, it aborts
- if the configured template name already exists on a node, it aborts
- if the image is already cached on the Proxmox host, it is reused
- Proxmox VMs for control-plane and worker nodes
- Debian node preparation with required packages, kernel settings, swap disablement, and unattended upgrades
- HA
k3swith embedded etcd and kube-vip for the API VIP and LoadBalancer IPs cert-managerincluding bootstrap issuers and root CA materialssystem-upgrade-controllerfor cluster upgrade plansTraefikas the ingress controller installed via HelmLonghornfor distributed block storageCloudNativePGoperator for PostgreSQL workloadsmetrics-serverfrom the basek3sinstallation- example workloads under
05-examples
- one or more Proxmox nodes with API access
- a Proxmox API token with permission to clone and create VMs
- either an existing cloud-init capable VM template on every Proxmox node you reference in
cluster_nodes, or the optionaljust create-templatesstep - matching
template_idvalues incluster.tfvarsfor those templates on the target nodes VS Codewith the dev container is recommended so the toolchain stays consistentDocker Desktopor an equivalent container runtime for the dev container workflow
Everything else is expected to come from the dev container.
This is the shortest path to a running cluster:
just init-config
just doctorEdit the two generated local files:
cluster.tfvarsfor non-secret cluster configurationcluster.secrets.tfvarsfor Proxmox credentials and the shared bootstrap token
Optional: if you want this repo to create the Proxmox VM templates for you first, copy and edit:
00-create-template/inventory/hosts.yml.example->00-create-template/inventory/hosts.yml00-create-template/vars/templates.yml.example->00-create-template/vars/templates.yml
Then run:
just create-templatesIf the Proxmox hosts require a become password, run:
just create-templates trueThe example template vars default to the official Debian Trixie generic image and one template per Proxmox node. Set the VM IDs, storage names, bridge, and SSH access for your environment before running the playbook. The inventory host names and the keys under proxmox_templates must match the actual Proxmox node names.
Then run the standard end-to-end workflow:
just upjust up runs these tasks in order:
just init-configjust provision-vmsjust configure-vmsjust bootstrap-clusterjust install-core
After bootstrap, k3s-ansible copies the kubeconfig to your control machine and merges it into ~/.kube/config with the proxmox-k3s context.
You can then access the cluster with:
kubectl --context proxmox-k3s get nodesUse this optional task when you want the repo to create Debian Trixie cloud-init templates on your Proxmox nodes before provisioning the cluster VMs.
Before running it, create and edit:
00-create-template/inventory/hosts.yml00-create-template/vars/templates.yml
Then run:
just create-templatesIf Ansible should prompt for the become password, use:
just create-templates trueThis task:
- downloads the official Debian Trixie genericcloud image on each Proxmox host
- downloads the official Debian Trixie generic image on each Proxmox host
- creates the VM shell with
qm create - imports the disk with
qm importdisk - enables Cloud-Init and the Proxmox guest agent flag
- converts the VM to a reusable template
It is intentionally fail-fast and aborts if the configured VM ID or template name already exists on a target Proxmox node.
For most users, this is the main reason to use the repo: you describe the cluster once, run one command, and get a Proxmox-backed HA k3s environment with ingress, storage, certificates, and upgrade plumbing already in place.
The cluster is described declaratively in cluster.tfvars.
cluster_nodes = [
{ name = "k3s-cp-1", role = "control_plane", proxmox_node = "pve-1", template_id = 201, vm_id = 301, ip = "10.30.0.11" },
{ name = "k3s-cp-2", role = "control_plane", proxmox_node = "pve-2", template_id = 202, vm_id = 302, ip = "10.30.0.12" },
{ name = "k3s-cp-3", role = "control_plane", proxmox_node = "pve-1", template_id = 201, vm_id = 303, ip = "10.30.0.13" },
{ name = "k3s-wk-1", role = "worker", proxmox_node = "pve-2", template_id = 202, vm_id = 304, ip = "10.30.0.21" },
{ name = "k3s-wk-2", role = "worker", proxmox_node = "pve-1", template_id = 201, vm_id = 305, ip = "10.30.0.22" }
]
api_endpoint = "10.30.0.10"
kube_vip_service_range = "10.30.0.251-10.30.0.255"
domain_suffix = "k3s.home"From that description, the repo derives:
- the Proxmox VMs to create
- the generated Ansible inventory
- the generated bootstrap values for
k3s-ansible - the generated cluster values consumed by Helmfile
That is the core model of this repo: describe the cluster once, derive everything else from it.
The cluster shape is defined in cluster.tfvars through cluster_nodes.
- You can choose how many control-plane nodes you want
- You can choose how many worker nodes you want
- Each node can be pinned to a specific Proxmox host and template
- CPU, memory, and other VM defaults can stay global, with optional per-node overrides
A typical HA layout is 3 control-plane nodes plus 2 or more worker nodes, but smaller and larger layouts work as long as at least one enabled control-plane node exists.
The user-edited local inputs are intentionally reduced to two gitignored files in the repository root.
This file contains non-secret cluster settings such as:
- Proxmox URL and TLS mode
- explicit node placement and
template_idmapping - VM sizing and datastore defaults
- API VIP and kube-vip service range
- DNS suffix and smoke-test toggle
- SSH user and public key for cloud-init
This file contains:
proxmox_api_token_idproxmox_api_token_secretcluster_bootstrap_token
Terraform consumes those files and generates the downstream artifacts used by Ansible and Helmfile.
The .generated/ directory contains Terraform-derived artifacts and is gitignored on purpose. Do not edit files there manually. Regenerate them with just sync-config or just provision-vms.
These generated files currently include:
ansible/inventory/hosts.yml.generated/bootstrap.vars.yml.generated/core.values.yaml
If you only changed cluster.tfvars or cluster.secrets.tfvars and want to refresh generated artifacts without reprovisioning VMs, use:
just sync-configjust doctor: run local preflight checks for tools, config files, placeholders, and generated artifactsjust create-templates: optionally create Debian Trixie cloud-init templates on the configured Proxmox hosts Usejust create-templates truewhen Ansible should prompt for thebecomepassword.just provision-vms: create or update the Proxmox VMs and refresh generated artifactsjust configure-vms: prepare the nodes with base OS configuration and Longhorn disk setupjust bootstrap-cluster: install HAk3swith kube-vip and merge kubeconfig locallyjust install-core: install the Helmfile-managed platform stack and upgrade plansjust verify-bootstrap: re-run bootstrap validationjust verify-core: re-run core platform validationjust bootstrap-cert-manager: re-run only the cert-manager bootstrap releasejust destroy-cluster: destroy the Terraform-managed VM infrastructure
00-create-template: optional Ansible-based Proxmox template creation01-provision: Terraform for Proxmox VMs and generated downstream config02-configure: Ansible base host preparation03-bootstrap: HAk3sbootstrap withk3s-ansible04-core: Helmfile-managed cluster services05-examples: example workloads for testing the setup