WZ-IT designs and implements Kubernetes on Proxmox, including automated cluster creation. The design covers hosts, networking, storage and subsequent operations.
This guide extends the manual Talos installation with OpenTofu and the bpg/proxmox and siderolabs/talos providers. It uses ISO boot and the Talos API. It does not require NoCloud import or SSH access to the guest.
As of October 2026. The examples follow the provider and vendor references linked below. They are a scaffold to adapt to your environment.
Table of contents
- 1. Prerequisites and state
- 2. Providers and inputs
- 3. Declare Proxmox VMs
- 4. Apply Talos configuration
- 5. Bootstrap and access
- 6. Plan, execution and acceptance
- Boundaries and operations
- Sources
1. Prerequisites and state
Proxmox is installed. The execution host can reach the Proxmox API and the node IPs on the Talos API. Configure API tokens and permissions following the bpg provider documentation. Supply PROXMOX_VE_ENDPOINT and PROXMOX_VE_API_TOKEN through the protected runtime environment; the API certificate must be trusted there.
A Talos Image Factory ISO is available on all participating hosts under the same storage file ID. Match the Talos version, schematic and installer image. The manual guide explains image selection and disk inspection. The example starts with the Proxmox guest agent disabled so its readiness is not assumed in maintenance mode.
The Kubernetes endpoint here is a preconfigured TCP load balancer on port 6443 pointing to the control-plane IPs, with a matching DNS name. The code does not create it. Node IPs come from DHCP reservations for the MAC addresses declared in code. For HA, place three control planes on separate hosts and add workers as required. A single control plane is the smaller lab target.
State, plan files and exported credentials contain confidential data. As explained in the OpenTofu state documentation, sensitive only hides display output. Configure an access-controlled backend with locking and backup, plus encrypted storage, before creating secrets. Keep state and credentials out of the Git repository.
2. Providers and inputs
Declare providers explicitly in main.tf. These pins refer to the reviewed documentation versions and do not promise future compatibility. Review lockfile and schema together when changing them.
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "0.117.0"
}
talos = {
source = "siderolabs/talos"
version = "0.12.0"
}
}
}
provider "proxmox" {}
provider "talos" {}
Declare inputs in variables.tf:
variable "nodes" {
type = map(object({
host = string
id = number
ip = string
mac = string
role = string
}))
}
variable "iso_file_id" { type = string }
variable "datastore" { type = string }
variable "cluster_endpoint" { type = string }
variable "talos_version" { type = string }
variable "kubernetes_version" { type = string }
variable "installer_image" { type = string }
variable "bootstrap_node" { type = string }
Set every value in a local terraform.tfvars: each nodes entry contains the Proxmox host, an unused VM ID, reserved IP, unique MAC and role controlplane or worker. bootstrap_node is the map key of a control plane. iso_file_id is the existing ISO file ID, and datastore holds EFI and system disks. cluster_endpoint is the full HTTPS load-balancer URL including port 6443. Select Talos and Kubernetes versions and the complete Image Factory installer image explicitly; avoid latest tags.
3. Declare Proxmox VMs
Add this to main.tf. Match hosts, bridge and resource sizes to your inventory. The system disk is empty here; never assign existing data disks. UEFI, VirtIO SCSI and disabled ballooning follow the Talos Proxmox guide.
resource "proxmox_virtual_environment_vm" "node" {
for_each = var.nodes
name = each.key
node_name = each.value.host
vm_id = each.value.id
bios = "ovmf"
machine = "q35"
scsi_hardware = "virtio-scsi-pci"
boot_order = ["scsi0", "ide2"]
started = true
agent { enabled = false }
cpu {
cores = each.value.role == "controlplane" ? 2 : 4
type = "host"
}
memory {
dedicated = each.value.role == "controlplane" ? 4096 : 8192
floating = 0
}
efi_disk {
datastore_id = var.datastore
type = "4m"
pre_enrolled_keys = false
}
disk {
datastore_id = var.datastore
interface = "scsi0"
size = 100
}
cdrom {
enabled = true
file_id = var.iso_file_id
interface = "ide2"
}
network_device {
bridge = "vmbr0"
model = "virtio"
mac_address = each.value.mac
}
lifecycle { prevent_destroy = true }
}
prevent_destroy blocks planned destruction and replacement while the resource block remains present; it is not a backup and does not prevent manual deletion or removal of the block. The code manages newly created VMs and does not automatically adopt existing ones. See OpenTofu lifecycle.
4. Apply Talos configuration
One shared secrets bundle defines cluster identity. Do not regenerate it on each run. Derive machine configuration by role; apply uses the reserved node IP. Inspect /dev/sda in maintenance mode before first applying and adjust if necessary. depends_on orders resources but does not replace checking Talos API reachability after boot.
resource "talos_machine_secrets" "cluster" {
talos_version = var.talos_version
lifecycle { prevent_destroy = true }
}
data "talos_machine_configuration" "node" {
for_each = var.nodes
cluster_name = "talos-proxmox"
cluster_endpoint = var.cluster_endpoint
machine_type = each.value.role
machine_secrets = talos_machine_secrets.cluster.machine_secrets
talos_version = var.talos_version
kubernetes_version = var.kubernetes_version
config_patches = [yamlencode({
machine = {
install = {
disk = "/dev/sda"
image = var.installer_image
}
}
})]
}
resource "talos_machine_configuration_apply" "node" {
for_each = var.nodes
node = each.value.ip
client_configuration = talos_machine_secrets.cluster.client_configuration
machine_configuration_input = data.talos_machine_configuration.node[each.key].machine_configuration
depends_on = [proxmox_virtual_environment_vm.node]
}
5. Bootstrap and access
Bootstrap runs once on the selected control plane after applying configurations. kubeconfig and talosconfig are exposed as sensitive outputs. Talos endpoints are direct control-plane IPs rather than the Kubernetes load balancer.
resource "talos_machine_bootstrap" "cluster" {
node = var.nodes[var.bootstrap_node].ip
client_configuration = talos_machine_secrets.cluster.client_configuration
depends_on = [talos_machine_configuration_apply.node]
}
resource "talos_cluster_kubeconfig" "cluster" {
node = var.nodes[var.bootstrap_node].ip
client_configuration = talos_machine_secrets.cluster.client_configuration
depends_on = [talos_machine_bootstrap.cluster]
}
data "talos_client_configuration" "cluster" {
cluster_name = "talos-proxmox"
client_configuration = talos_machine_secrets.cluster.client_configuration
endpoints = [for node in values(var.nodes) : node.ip if node.role == "controlplane"]
nodes = [for node in values(var.nodes) : node.ip]
}
output "talosconfig" {
value = data.talos_client_configuration.cluster.talos_config
sensitive = true
}
output "kubeconfig" {
value = talos_cluster_kubeconfig.cluster.kubeconfig_raw
sensitive = true
}
6. Plan, execution and acceptance
Run only after configuring the backend, inputs, certificate trust and credentials. Review the plan for VM IDs, host placement, disk targets and unexpected replacements. If API reachability fails, inspect node state instead of deleting secrets or state. Generate a new plan after an interruption.
For health, replace <CONTROL_PLANE_IP> with the IP of exactly one control plane, for example the one selected by bootstrap_node. The generated talosconfig contains all nodes; Talos 1.14 requires exactly one target node for this command, through which the cluster check runs (as of 19 October 2026).
tofu init
tofu fmt -check
tofu validate
tofu plan -out=cluster.plan
tofu apply cluster.plan
umask 077
tofu output -raw talosconfig > talosconfig
tofu output -raw kubeconfig > kubeconfig
talosctl --talosconfig ./talosconfig --nodes "<CONTROL_PLANE_IP>" health
kubectl --kubeconfig ./kubeconfig get nodes -o wide
Acceptance also covers Kubernetes endpoint reachability, DNS, CNI, a test workload, and storage and restore where configured. Nodes may not be Ready without a working CNI. These resources do not configure storage classes, ingress rules or application backups.
Boundaries and operations
Provider state does not replace etcd or application backups. Validate recovery separately using Kubernetes backup and restore. A version change in HCL is not a complete upgrade runbook; plan Talos and Kubernetes upgrades separately. If state is lost, preserve existing secrets and resources and investigate documented import options; do not bootstrap a running cluster again.
WZ-IT connects automated creation on Proxmox with architecture design. Managed Kubernetes covers ongoing maintenance. The manual guide remains the reference for images, node diagnosis and upgrade paths.










