[email protected]

How-to · Kubernetes

Automate Talos Linux on Proxmox with OpenTofu

Editorial note: Versions, commands and prices may change. Please verify critical steps independently before production use. This guide does not replace individual professional, legal or tax advice.

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

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.

Sources

Rather have it operated?

You'd rather not run Kubernetes yourself? WZ-IT handles setup, operations and maintenance - privacy-focused from Germany.

Enquiry

Design, take over, or operate Kubernetes

We design and operate Kubernetes on bare metal, Proxmox, and European cloud platforms, including networking, GitOps, observability, and backups.

  • Straight with Timo and Robin - no sales team, no pitch
  • A professional assessment of your situation
  • Concrete next steps for infrastructure, software or AI

The conversation is free and non-binding.

WZ-IT supported us professionally with the provisioning and setup of NetBird. Alongside the technical implementation, we also received well-founded advice on connecting our Kubernetes clusters. The recommendations were concrete and directly actionable. The collaboration was consistently reliable, fast and on equal terms. We can recommend WZ-IT without reservation.
Kai ClaußHead of Development, SweetConnect GmbH
Timo and Robin, founders of WZ-IT

Design, take over, or operate Kubernetes

WZ-IT supported us professionally with the provisioning and setup of NetBird. Alongside the technical implementation, we also received well-founded advice on connecting our Kubernetes clusters. The recommendations were concrete and directly actionable. The collaboration was consistently reliable, fast and on equal terms. We can recommend WZ-IT without reservation.
Kai ClaußHead of Development, SweetConnect GmbH

Frequently Asked Questions

Answers to the most important questions

Companies worldwide trust WZ-IT

  • Stadtwerke Brühl
  • DGHO e.V.
  • ABCO Water Systems
  • Golem.de
  • EVADXB
  • nextGYM
  • AInergy
  • ml&s
  • Odiseo Solutions
  • Annota
  • ARGE
  • SweetConnect GmbH
  • Aphy AG
  • CORGOS
  • Rekorder
  • SolidProof
  • Yonju
  • Keymate
  • Paritel
  • Mr. Clipart
  • Millenium
  • Negosh
  • Führerscheinmacher
  • Boese VA
Read client reviews