Migrate a machine running k3s or kubeadm to Talos Linux over SSH — no physical access or reinstall required.
k2t connects to the remote node, backs up cluster state, generates a Talos config matched to the source cluster, installs Talos in-place via kexec, and bootstraps Kubernetes — preserving workloads when restoring from an etcd snapshot.
WARNING: This process is irreversible. The target machine's OS will be completely erased and replaced with Talos Linux.
k2t migrate [email protected]
- Collect — SSH into the node, detect the cluster type (k3s/kubeadm), gather version, CIDRs, workloads, and hardware info
- Backup — Snapshot etcd (or SQLite), export Kubernetes resources to YAML
- Generate — Run
talosctl gen configwith settings matched to the source cluster - Deploy — Upload the Talos installer agent, write the disk image, reboot via kexec
- Bootstrap — Wait for Talos, apply config, restore etcd, retrieve kubeconfig
git clone https://github.com/rothgar/k2t
cd k2t
make build
# ./k2t is now ready to usego install github.com/rothgar/k2t@latestLocal machine:
talosctlin$PATHkubectlin$PATH- SSH access to the target node
Remote node:
- k3s server or kubeadm control plane running on Linux
sudoaccess (or root)- UEFI boot
k2t migrate [email protected]k2t prompts for confirmation before modifying the remote machine.
Use --migrate-to-etcd to convert SQLite to embedded etcd first — required for snapshot restore on Talos:
k2t migrate [email protected] --migrate-to-etcdCollect info and show what would happen without touching the remote machine:
k2t migrate [email protected] --dry-runProgress is saved to <backup-dir>/migration-state.json. Resume without repeating completed phases:
k2t migrate [email protected] --resumeFor multi-CP clusters, join additional control plane nodes after migrating the first one. The existing cluster is validated as healthy before proceeding:
k2t join-controlplane [email protected] \
--controlplane-config ./k3s-backup/talos-config/controlplane.yaml \
--talosconfig ./k3s-backup/talos-config/talosconfigNo etcd restore or bootstrap is needed — the new node discovers the existing etcd cluster and joins automatically.
After migrating the control plane, convert worker nodes:
k2t join-worker [email protected] \
--worker-config ./k3s-backup/talos-config/worker.yaml \
--talosconfig ./k3s-backup/talos-config/talosconfigFor a 3-CP + 2-worker cluster:
# 1. Migrate the first control plane (etcd restore)
k2t migrate [email protected] --migrate-to-etcd
# 2. Join additional control plane nodes
k2t join-controlplane [email protected] \
--controlplane-config ./k3s-backup/talos-config/controlplane.yaml \
--talosconfig ./k3s-backup/talos-config/talosconfig
k2t join-controlplane [email protected] \
--controlplane-config ./k3s-backup/talos-config/controlplane.yaml \
--talosconfig ./k3s-backup/talos-config/talosconfig
# 3. Join workers
k2t join-worker [email protected] \
--worker-config ./k3s-backup/talos-config/worker.yaml \
--talosconfig ./k3s-backup/talos-config/talosconfig
k2t join-worker [email protected] \
--worker-config ./k3s-backup/talos-config/worker.yaml \
--talosconfig ./k3s-backup/talos-config/talosconfigBack up cluster state without migrating:
k2t collect [email protected]Generate Talos machine configs from an existing backup:
k2t generate --cluster-endpoint 10.1.1.1 --backup-dir ./k3s-backupk2t reads ~/.ssh/config for host aliases, users, ports, and identity files:
k2t migrate myserver # ~/.ssh/config alias
k2t migrate [email protected] # inline user@host
k2t migrate 10.1.1.1 --ssh-key ~/.ssh/mykey # explicit key
k2t migrate 10.1.1.1 --ssh-port 2222 # explicit portSSH agent (SSH_AUTH_SOCK) is used automatically when available. When the SSH user is not root, commands are run with sudo (password is prompted once if needed).
| Flag | Default | Description |
|---|---|---|
--host |
SSH target [user@]host (alternative to positional arg) |
|
--ssh-key |
SSH private key path (overrides ~/.ssh/config) |
|
--ssh-port |
22 |
SSH port (overrides ~/.ssh/config) |
--backup-dir |
./k3s-backup |
Local directory for backups and configs |
-v, --verbose |
Print each remote command and its output |
| Flag | Default | Description |
|---|---|---|
--talos-version |
v1.12.6 |
Talos Linux version to install |
--cluster-name |
(from source) | Talos cluster name |
--migrate-to-etcd |
Convert k3s SQLite to etcd before backup | |
--dry-run |
Show plan without modifying anything | |
--resume |
Resume from last completed phase | |
--yes |
Skip confirmation prompt (CI/automation) |
| Flag | Default | Description |
|---|---|---|
--talos-version |
v1.12.6 |
Talos Linux version to install |
--controlplane-config |
(required) | Path to controlplane.yaml from initial migration |
--talosconfig |
(required) | Path to talosconfig from initial migration |
--skip-health-check |
Skip cluster health validation before joining |
| Flag | Default | Description |
|---|---|---|
--talos-version |
v1.12.6 |
Talos Linux version to install |
--worker-config |
(required) | Path to worker.yaml from control plane migration |
--talosconfig |
(required) | Path to talosconfig from control plane migration |
| Flag | Default | Description |
|---|---|---|
--cluster-endpoint |
Control plane IP or hostname | |
--cluster-name |
talos-cluster |
Talos cluster name |
--talos-version |
v1.12.6 |
Talos version to target |
--kubernetes-version |
(talosctl default) | Kubernetes version for the config |
| Item | Preserved | Notes |
|---|---|---|
| Deployments, StatefulSets, DaemonSets | Yes | Via etcd snapshot restore |
| Services and Ingresses | Yes | Via etcd snapshot restore |
| ConfigMaps and Secrets | Yes | Via etcd snapshot restore |
| RBAC (Roles, ClusterRoles, Bindings) | Yes | Via etcd snapshot restore |
| Pod and Service CIDRs | Yes | Matched in generated Talos config |
| Kubernetes version | Yes | Passed to talosctl gen config |
| PersistentVolume data | No | Disk is erased — back up PV data separately |
k3s-backup/
├── migration-state.json # phase progress (for --resume)
├── k3s.yaml # source cluster kubeconfig
├── database/
│ ├── etcd-snapshot.db # etcd snapshot (restored on Talos)
│ └── backup-info.json
├── talos-config/
│ ├── talosconfig # talosctl client config
│ ├── controlplane.yaml # control plane machine config
│ └── worker.yaml # worker machine config
├── talos-kubeconfig # kubeconfig for the new cluster
└── resources/ # exported Kubernetes resources
├── deployments/
├── services/
├── configmaps/
└── ...
Access the new cluster:
export KUBECONFIG=./k3s-backup/talos-kubeconfig
kubectl get nodesUse talosctl:
export TALOSCONFIG=./k3s-backup/talos-config/talosconfig
talosctl --nodes 10.1.1.1 health
talosctl --nodes 10.1.1.1 dashboard