K3SD is a modern, config-driven tool for creating, managing, and uninstalling K3s Kubernetes clusters across multiple machines. It supports fully modular, extensible cluster and addon configuration, and includes a TUI (text user interface) for easy config generation.
- Features
- Prerequisites
- Installation
- Configuration
- TUI Config Generator
- Usage
- Command-line Options
- Addon System
- Addon Migration and Idempotency
- Linkerd Multicluster Linking
- Database and Versioning
- Architecture
- Project Roadmap
- Contributing
- Extending the TUI: Adding New Forms and Inputs
- Deploy K3s clusters with multiple worker nodes via SSH
- Cross-platform: Linux, macOS, Windows
- Fully config-driven: all cluster and addon options are set in a JSON config file
- Built-in addons: cert-manager, Traefik, Prometheus, Gitea, Linkerd, ClusterIssuer, and more
- Custom addon support: install any Helm chart or manifest via config
- TUI (text UI) for interactive config generation
- Clean uninstall of clusters
- Per-node kubeconfig management
- Verbose logging and atomic Helm operations
kubectl- Kubernetes CLIlinkerd- Linkerd CLI (for Linkerd addon)step- step CLI (for Linkerd certs)ssh- SSH client for remote server access
Download the latest binary for your platform from the Releases page.
# Example for Linux x86_64
curl -LO https://github.com/argon-chat/k3sd/releases/latest/download/k3sd-linux-amd64.tar.gz
tar -xzf k3sd-linux-amd64.tar.gz
chmod +x k3sd
sudo mv k3sd /usr/local/bin/K3SD uses a single JSON file to describe all clusters and addons. Example:
[
{
"address": "10.144.103.55",
"user": "ubuntu",
"password": "password123",
"nodeName": "master",
"labels": {
"label1": "value1"
},
"domain": "example.com",
"privateNet": false,
"workers": [
{
"address": "10.144.103.64",
"user": "ubuntu",
"password": "password123",
"nodeName": "worker1",
"labels": {},
"done": false
}
],
"addons": {
"gitea": {
"enabled": true,
"subs": {
"${POSTGRES_USER}": "gitea",
"${POSTGRES_PASSWORD}": "changeme",
"${POSTGRES_DB}": "giteadb"
}
},
"gitea-ingress": {
"enabled": true,
"subs": { "${DOMAIN}": "example.com" }
},
"cert-manager": { "enabled": true },
"traefik": { "enabled": false },
"prometheus": { "enabled": false },
"cluster-issuer": { "enabled": false },
"linkerd": { "enabled": false },
"linkerd-mc": { "enabled": false }
},
"customAddons": {
"somePod": {
"enabled": false,
"helm": {
"chart": "mychart",
"repo": {
"name": "myrepo",
"url": "https://charts.example.com"
},
"version": "1.2.3",
"valuesFile": "./yamls/somepod-values.yaml",
"namespace": "default"
},
"manifest": {
"path": "./yamls/somepod.yaml",
"subs": { "KEY": "value" }
}
}
}
}
]K3SD includes a built-in TUI for interactively generating cluster configs. Run:
k3sd -generateThis will launch a form-based UI to enter master node info, select addons, and (if needed) configure addon variables. The resulting config is saved as a JSON file.
k3sd --versionk3sd --config-path=/path/to/clusters.jsonk3sd --config-path=/path/to/clusters.json --uninstall| Option | Description |
|---|---|
--config-path |
Path to clusters.json (required) |
--yamls-path |
Path prefix for YAMLs (default: ./yamls or ~/.k3sd/yamls) |
--uninstall |
Uninstall the cluster |
--version |
Print the version and exit |
-v |
Enable verbose logging |
--helm-atomic |
Enable atomic Helm operations (rollback on failure) |
-generate |
Launch the TUI config generator |
All addon/component selection is now done via the config file, not CLI flags.
K3SD supports two types of addons:
- Built-in addons: Managed by the migration registry with dedicated Up/Down logic. Examples:
cert-manager,traefik,prometheus,gitea,cluster-issuer,linkerd,linkerd-mc. - Custom addons: User-defined Helm charts or manifests, managed via the
customAddonsmap in your config. These use the same migration logic as built-ins.
Note:
gitea-ingressis not a standalone built-in addon. It is managed as part of the Gitea addon logic and is typically configured as a manifest/ingress resource.
All addons are enabled/disabled via your config file. The migration logic ensures only necessary actions are taken when the cluster config changes, and all install/uninstall flows are robust and idempotent.
K3SD uses an enum-based migration status:
AddonApply: Install or upgrade the addon.AddonDelete: Uninstall the addon.AddonNoop: No action needed (state unchanged).
The function ComputeAddonMigrationStatus determines the correct action for each addon based on the current and previous cluster state. This ensures safe, repeatable operations and supports upgrades, rollbacks, and config diffs.
- Implement
UpandDownfunctions inpkg/addons/youraddon.go. - Register your addon in
pkg/addons/addonRegistry.go. - Add config keys and substitutions as needed.
Add a new entry to the customAddons map in your config file, specifying either a Helm chart or manifest (or both). No code changes are required for most custom addons. Both Helm and manifest custom addons are supported and can be enabled/disabled independently.
K3SD now supports robust, idempotent Linkerd multicluster linking. If you enable both linkerd and linkerd-mc addons and specify a linksTo array in your cluster config, K3SD will automatically link your clusters using the correct kubeconfigs and Linkerd CLI commands.
- The system checks for existing links and unlinks as needed.
- Linking/unlinking is idempotent and robust against config changes.
- Handles error cases and uninstall order correctly.
Example cluster config:
{
"context": "cluster-1",
"nodeName": "cluster-1-master",
...
"addons": {
"linkerd": { "enabled": true },
"linkerd-mc": { "enabled": true }
},
"linksTo": ["cluster-2", "cluster-3"]
}Note: The
linksTofield in your config should contain the context names of the clusters you wish to link, not their IP addresses.
K3SD stores cluster state and version history in a local SQLite database (via GORM). This enables:
- Safe upgrades and rollbacks
- Accurate migration logic for addons
- Tracking of cluster changes over time
K3SD is organized into several key packages:
- pkg/cluster: Handles cluster creation, worker join, uninstall, and main orchestration logic.
- pkg/addons: Built-in and custom addon management, including migration logic (Up/Down), registry, and linking (Linkerd).
- pkg/clusterutils: Utilities for YAML/Helm apply/delete, SSH, manifest handling, and migration status computation.
- pkg/types: All config and runtime types (Cluster, AddonConfig, CustomAddonConfig, etc).
- pkg/db: Cluster state/versioning with SQLite (via GORM).
- pkg/utils: Logging, CLI flags, version, and helpers.
- pkg/k8s: Kubeconfig and Kubernetes-specific helpers.
| Feature / Milestone | Status |
|---|---|
| Deploy K3s clusters with multiple worker nodes via SSH | ✅ |
| Cross-platform support (Linux, macOS, Windows) | ✅ |
| Built-in addon system (config-driven) | ✅ |
| Custom addon support (Helm/manifest) | ✅ |
| TUI config generator | ✅ |
| Clean uninstall of clusters | ✅ |
| Per-node kubeconfig management | ✅ |
| Verbose logging and atomic Helm operations | ✅ |
| Support for choosing CNI of choice | 🚧 |
| Add support for more service meshes (e.g., Istio) | 🚧 |
| Remember/apply config JSON diffs for future changes | 🚧 |
Legend: 🚧 = in progress or planned, ✅ = implemented
Contributions are welcome! To get started:
- Fork the repo and create a feature branch.
- Make your changes (see below for addon guidelines).
- Run
go build,go vet, andgolangci-lint runto ensure code quality. - Submit a pull request with a clear description.
- Create your addon logic in
pkg/addons/youraddon.goas a function:func ApplyYourAddon(cluster *types.Cluster, logger *utils.Logger) { /* ... */ }
- Register it in
pkg/addons/addonRegistry.go. - Add config keys and substitutions as needed (see other addons for examples).
Add a new entry to the customAddons map in your config file, specifying either a Helm chart or manifest (or both). See the config example above.
- Use helpers in
pkg/clusterutilsfor manifest/Helm operations. - Addons should be idempotent and log all actions.
- Document any new config keys in the README.
The TUI is designed to be modular and easily extensible. To add a new input field or a new form (e.g., for a new addon), follow these steps:
- Edit the
clusterFieldsarray incli/tui/generate.go:var clusterFields = []FieldDef{ {"Master node IP", "", false}, {"Master SSH user", "", false}, // ... {"My New Field", "default-value", false}, // <-- Add your field here }
- No further code changes are needed. The field will automatically appear in the TUI and be included in the generated config.
- Define your addon fields as a
[]FieldDef:var myAddonFields = []FieldDef{ {"MY_OPTION", "default", false}, {"MY_SECRET", "", true}, }
- Create a form function using the generic builder:
func buildMyAddonForm(app *tview.Application, onBack func(), onDone func(subs map[string]string)) *tview.Form { return buildAddonSubsForm(app, "MyAddon Configuration", myAddonFields, onBack, onDone) }
- Add your addon to the
addonListarray:var addonList = []string{ "gitea", "myaddon", // ... }
- Update the logic in
buildClusterFormto call your new form when your addon is selected (see how Gitea is handled for an example).
- All field definitions are arrays at the top of
generate.go. - Use the
FieldDefstruct for each field:{Label, Default, IsPassword}. - Use the
buildAddonSubsFormhelper for any new addon form. - No need to modify core logic—just add to arrays and call the generic builder.
This approach keeps the code DRY, modular, and easy to maintain. For more advanced flows (multi-step forms, validation, etc.), follow the same pattern: define your fields, use the generic builder, and handle the result in the main flow.