Migration from talhelper
This guide walks through migrating an existing talhelper-managed cluster to TOPF.
Overview
The main differences:
| Aspect | talhelper | TOPF |
|---|---|---|
| Config file | talconfig.yaml |
topf.yaml |
| Patches | Inline in config or separate files | Separate files in all/, <role>/, node/<host>/ |
| Patch format | Strategic merge + JSON patches (RFC 6902) | Strategic merge only (with $patch: delete support) |
| Secrets | talenv.sops.yaml + envsubst |
SOPS-encrypted fields directly in topf.yaml |
| Templating | envsubst / talhelper variables | Go templates (.Data, .Node.Data) |
| Workflow | Generate configs, then apply with talosctl |
Direct apply with topf apply |
Step 1: Create topf.yaml
Translate your talconfig.yaml node definitions into topf.yaml:
Before (talconfig.yaml):
clusterName: mycluster
endpoint: https://192.168.1.100:6443
kubernetesVersion: v1.32.8
nodes:
- hostname: node-01
ipAddress: 192.168.1.1
installDisk: /dev/nvme0n1
controlPlane: true
networkInterfaces:
- interface: eno1
dhcp: true
vip:
ip: 192.168.1.100
- hostname: node-02
ipAddress: 192.168.1.2
controlPlane: false
After (topf.yaml):
clusterName: mycluster
clusterEndpoint: https://192.168.1.100:6443
kubernetesVersion: v1.32.8
nodes:
- host: node-01
ip: 192.168.1.1
role: control-plane
- host: node-02
ip: 192.168.1.2
role: worker
Note that node-specific config like installDisk, networkInterfaces, and vip moves into patch files (see below).
Step 2: Extract patches into files
Inline patches from talconfig.yaml become separate files.
Before (inline in talconfig.yaml):
patches:
- |-
cluster:
proxy:
disabled: true
discovery:
enabled: true
controlPlane:
patches:
- |-
...
After (separate files):
all/01-cluster-config.yaml # global patches
control-plane/01-vip.yaml # control-plane patches
worker/10-kubelet.yaml # worker patches
node/node-01/01-specific.yaml # node-specific patches
Each file is a standalone YAML document:
Step 3: Replace JSON patches with strategic merge
talhelper often uses JSON patches (RFC 6902) for removing or adding fields. TOPF uses strategic merge patches instead.
Before (JSON patch to remove a label):
After (strategic merge with $patch: delete):
# control-plane/04-remove-external-lb-exclusion.yaml
machine:
nodeLabels:
node.kubernetes.io/exclude-from-external-load-balancers:
$patch: delete
This is cleaner and avoids the need for JSON pointer escaping (~1 for /).
Step 4: Migrate envs and templating
Before (talhelper uses talenv.sops.yaml + envsubst):
# talenv.sops.yaml
CONTROLPLANE_ENDPOINT: ENC[AES256_GCM,data:...,type:str]
# talconfig.yaml
additionalApiServerCertSans:
- "${CONTROLPLANE_ENDPOINT}"
After (TOPF uses SOPS-encrypted data fields in topf.yaml + Go templates):
# topf.yaml (SOPS-encrypted)
data:
additionalControlPlaneEndpoint: ENC[AES256_GCM,data:...,type:str]
# control-plane/01-extra-SANs.yaml.tpl
cluster:
apiServer:
certSANs:
- { { .Data.additionalControlPlaneEndpoint } }
No separate secrets file needed — sensitive values live directly in topf.yaml under data, encrypted with SOPS.
Step 5: Migrate secrets
Move your existing Talos secrets bundle talsecret.sops.yaml to secrets.yaml. It is compatible with TOPF. Simply keep it in the same directory.
Step 6: Apply
Instead of generating configs and applying with talosctl:
# Before (talhelper)
talhelper genconfig
talosctl apply-config --nodes 192.168.1.1 --file clusterconfig/mycluster-node-01.yaml
# After (TOPF)
topf apply
TOPF handles config generation and apply in a single step.
Real-world Example
See this commit for a complete migration from talhelper to TOPF on a homelab cluster.