Butler Releases
Release history and changelog for the Butler Kubernetes-as-a-Service platform.
Component Versions
| Component | Latest | Type | Released |
|---|---|---|---|
CRD type definitions (the API contract) | 0.22.0 | module | Jun 12, 2026 |
Core reconciliation controllers | 0.28.3 | container | Aug 31, 2026 |
HTTP/WS API server | 0.23.0 | container | Aug 31, 2026 |
Web console | 0.16.1 | container | Jul 10, 2026 |
CLI tools (butlerctl, butleradm) | 0.18.0 | binary | Jun 12, 2026 |
Management cluster bootstrap controller | 0.5.6 | container | May 20, 2026 |
Harvester HCI provider controller | 0.2.3 | container | Apr 26, 2026 |
Nutanix AHV provider controller | 0.2.3 | container | Apr 26, 2026 |
AWS provider controller | 0.1.2 | container | Apr 26, 2026 |
GCP provider controller | -- | container | -- |
Azure provider controller | -- | container | -- |
OS image building and hosting service | -- | container | -- |
Backstage-based developer portal | 0.7.0 | container | Jul 30, 2026 |
Helm charts for deployment | 0.24.0 | helm | Aug 31, 2026 |
Kubernetes Compatibility
| Butler Version | Kubernetes Versions |
|---|---|
| v0.9.x | 1.28, 1.29, 1.30, 1.31 |
| v0.8.x | 1.28, 1.29, 1.30, 1.31 |
| v0.7.x | 1.28, 1.29, 1.30 |
Release History
- fix(server): accept 1..3 control plane replicas, not only 1 or 3 by @atbagan in https://github.com/butlerdotdev/butler-server/pull/103
- fix(gitops): repository identity and complete repository listing by @atbagan in https://github.com/butlerdotdev/butler-server/pull/104
- feat(controller): map konnectivity resources onto the StewardControlPlane addons by @atbagan in https://github.com/butlerdotdev/butler-controller/pull/114
- feat: local provider CAPD support for the laptop path by @atbagan in https://github.com/butlerdotdev/butler-controller/pull/112
- fix(controller): omit control plane replicas when the tenant omits them by @atbagan in https://github.com/butlerdotdev/butler-controller/pull/113
- docs(#51): end-to-end 'Getting Started with Permissions' walkthrough by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/68
- feat: config-gate the Google sign-in provider; keep Guest default with opt-out by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/69
- feat(permissions): adopt @backstage-community/plugin-rbac-backend [BREAKING] by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/40
- ci: compile-check TypeScript code samples in the permissions docs by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/54
- docs: RBAC permission framework + updated plugin authoring guide by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/41
- fix(#52): adopter-plugin reference installs + compiles; drop continue-on-error by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/57
- docs: adopter-facing note on npm ERESOLVE workaround by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/58
- chart: pluginsWithPermission guard (fail-loud on drift) + warn-loudly on opt-out by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/42
- chart(0.6.1): ship #42's guard + #60's fatigue fix to adopters by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/63
- feat(#59): runtime audit for pluginsWithPermission gaps + addPermissions convention by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/64
- feat(permissions): adopt @backstage-community/plugin-rbac-backend [BREAKING] by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/40
- fix(Dockerfile): bump base to node:22-bookworm-slim for isolated-vm 6.x by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/39
Enforces role-based access on the tenant cluster terminal.
Terminal access
The tenant terminal previously admitted any team member, so a viewer could open an interactive shell. Access is now resolved from Team CRDs and gated by role:
- Write-capable roles (team admin, operator, platform admin) get the interactive shell, unchanged.
- Viewers connect read-only: they stream output but their input is dropped server-side.
- Non-members are refused before the connection upgrades.
Authorization boundary
Write-capable roles run the shell under the tenant cluster-admin credential. A scoped operator identity is possible later and would change only the credential selection seam. Because write-capable roles share that credential, each terminal session now logs the Butler user, role, team, cluster, and duration on open and close for attribution.
Deployment
Helm chart butler-console-v0.14.2 advances the pinned server image to 0.22.0.
New platform capability
Plugins can now gate their own authorization inside butler-portal without editing the central policy. The new AuthAdjudicatorExtensionPoint routes each authorize() call to a plugin-registered adjudicator by permission-name prefix; unclaimed namespaces fall through to ALLOW so core Backstage plugins (catalog, scaffolder, techdocs, search, kubernetes) keep working unchanged.
Authoring guide: https://github.com/butlerdotdev/butler-portal/blob/v0.5.5/docs/plugin-authoring/authorization.md
- feat(permissions): namespace-delegated policy so plugins gate their own authz (#33)
Full Changelog: https://github.com/butlerdotdev/butler-portal/compare/v0.5.4...v0.5.5
Restores terminal and GitOps write parity for the team admin and operator roles.
Fix
In v0.16.0, users whose highest role was a per-team `admin` or `operator` lost write access to two cluster surfaces:
- The cluster terminal was read-only. Operators and team admins could open the terminal and see output but could not type.
- The GitOps Enable and Disable controls were hidden, so operators and team admins could not turn GitOps on or off for a cluster.
Both are now available to team admins and operators, matching the intended role model where `admin` and `operator` are write-capable and `viewer` is read-only. Viewer stays read-only, and platform admin behavior is unchanged.
Deployment
Helm chart `butler-console-v0.14.1` ships this frontend (0.16.1) alongside butler-server 0.21.1. The chart now pins both image tags explicitly.
- fix(Dockerfile): downgrade node base image to node:20-bookworm-slim by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/32
- app: generic sign-in copy for Google card by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/31
- refactor(portal): document upgrade-handler attach window and add guard tests by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/15
- ci(portal): dedupe push+pull_request runs and bound runner wedges by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/16
- feat(portal): portal-JWT signing carrier, Stage 2 of the impersonation fix by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/17
- chore(portal): bump chart 0.2.3 -> 0.3.0 for Stage 2 release by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/18
- fix(portal): make signing-key mount readable by non-root node UID by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/19
- feat(portal): runtime per-plugin gates for Butler-Labs-branded plugins by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/20
- fix(portal): ship packages/app/config.d.ts in backend image (post-PR-#20 boot crashloop hotfix) by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/21
- feat(portal): chart 0.5.0 dynamic plugins (backend + chart + frontend normalization + installer + boot-test green) by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/22
- 0.5.1: real frontend dynamic-plugin loading + chart NODE_PATH + CLI wrapper by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/23
- chart(0.5.3): ConfigMap checksum auto-rollout + postgresql.backup passthrough by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/24
- chart(0.5.4): allowedSources default includes RHDH marketplace + butlerdotdev by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/25
- chart(0.5.5): pin installer 0.1.1 (annotation-less OCI pull fix) by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/26
- chart(0.5.6): pin installer 0.1.2 (auto-detect package.json wrapper dir) by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/27
- chart(0.5.7): pin installer 0.1.3 (flatten scoped npm names) by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/28
- app: render Microsoft sign-in card when configured by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/29
- chart(0.5.8): bump for SignInPage microsoft conditional by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/30
- refactor(portal): document upgrade-handler attach window and add guard tests by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/15
- ci(portal): dedupe push+pull_request runs and bound runner wedges by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/16
- feat(portal): portal-JWT signing carrier, Stage 2 of the impersonation fix by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/17
- chore(portal): bump chart 0.2.3 -> 0.3.0 for Stage 2 release by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/18
- fix(portal): make signing-key mount readable by non-root node UID by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/19
- feat(portal): runtime per-plugin gates for Butler-Labs-branded plugins by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/20
- fix(portal): ship packages/app/config.d.ts in backend image (post-PR-#20 boot crashloop hotfix) by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/21
- feat(portal): chart 0.5.0 dynamic plugins (backend + chart + frontend normalization + installer + boot-test green) by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/22
- 0.5.1: real frontend dynamic-plugin loading + chart NODE_PATH + CLI wrapper by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/23
- refactor(portal): document upgrade-handler attach window and add guard tests by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/15
- ci(portal): dedupe push+pull_request runs and bound runner wedges by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/16
- feat(portal): portal-JWT signing carrier, Stage 2 of the impersonation fix by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/17
- chore(portal): bump chart 0.2.3 -> 0.3.0 for Stage 2 release by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/18
- fix(portal): make signing-key mount readable by non-root node UID by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/19
- feat(portal): runtime per-plugin gates for Butler-Labs-branded plugins by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/20
- fix(portal): ship packages/app/config.d.ts in backend image (post-PR-#20 boot crashloop hotfix) by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/21
- feat(portal): chart 0.5.0 dynamic plugins (backend + chart + frontend normalization + installer + boot-test green) by @atbagan in https://github.com/butlerdotdev/butler-portal/pull/22
Changelog
- feat(cluster): host-usable kubeconfig for local tenants + auto-derive lb-pool (#60)
- fix(bootstrap): sync embedded CRDs from butler-api and guard against drift (#59)
- fix(cluster): cross-platform process detach for the local port-forward (#61)
Changelog
- feat: butleradm bootstrap local + TUI wizard for the laptop path (#58)
Local provider type for the laptop path
- New `local` provider for ClusterBootstrap and ProviderConfig: installs Butler onto a kind-based management cluster and provisions tenant workers as containers via the Cluster API Docker provider (CAPD), for running Butler on a laptop with no hypervisor.
- Adds `ProviderTypeLocal`, an optional `LocalProviderConfig` (`kindNodeImage` override), and `ClusterBootstrap.IsLocal()`.
- `credentialsRef` is now optional on ProviderConfig (the local provider needs no credentials).
Validation
- A CEL rule on ProviderConfig requires a non-empty `credentialsRef.name` for every non-local provider and exempts local, so a credential-less non-local config is rejected at admission.
Known follow-up
`ProviderConfig.CredentialsRef` should become `*SecretReference` (tracked in butlerdotdev/butler-api#47) so credential-less local configs round-trip cleanly. Until then a local ProviderConfig shows a cosmetic not-Ready status; provisioning is unaffected.
Fix: provider get/list read the correct scope and status paths
A display-path fix, found by exercising `provider get` against a real team-scoped provider.
- **Scope showed the wrong value.** `provider get` and `provider list -o wide` read `spec.scope` as a string, but the ProviderConfig stores scope as an object (`spec.scope.type`). The string read was always empty, so the `platform` fallback fired and a team-scoped provider displayed as `platform`. They now read `spec.scope.type`, so team-scoped providers show `team` and unscoped providers still show `platform`.
- **Validation error never displayed.** It was read from a `status.validationError` field that does not exist; a failed validation is recorded as the Ready condition's message, which is now where it is read from.
- **Last-validation timestamp** was read from `status.lastValidatedAt`; the field is `status.lastValidationTime`.
Verified live on butler-beta with a throwaway team-scoped provider before and after the fix.
- feat(auth): log portal proof verification success at Info by @atbagan in https://github.com/butlerdotdev/butler-server/pull/89
Team member-role and group access rules (butleradm)
The `butleradm team` group gains four verbs, completing CLI parity with the console for Team access management. Previously the CLI could add and remove members but could not change a member's role or manage group access rules.
| Verb | What it does |
|---|---|
| `update-member-role` | Change an existing member's role |
| `add-group` | Add a group-to-role access rule |
| `update-group-role` | Change a group rule's role |
| `remove-group` | Remove a group rule |
CRD-direct, status-preserving
All four edit `Team.spec.access` directly, mirroring the existing add-member and remove-member: Get the Team, change the targeted entry in the users or groups list, and Update the same object. Because the Update carries the same fetched object, the controller-managed status and the resourceVersion are preserved without a separate merge step.
The user identifier in `spec.access.users` is the `name` field, which holds the email.
Group rules are identified by name plus optional identity provider
A group rule can be scoped to a single IdP with `--identity-provider`, or left unscoped to match a group name from any IdP. When more than one rule shares a name and no `--identity-provider` is given, `update-group-role` and `remove-group` error and ask which one to act on, rather than editing the wrong rule.
The referenced identity provider is not validated to exist when adding a rule (the apiserver does not require it); a convenience check may be added later.
Validation
The full lifecycle was exercised end to end against butler-beta: update-member-role (status conditions and resourceVersion preserved against the real apiserver), add-group, update-group-role, the multi-rule ambiguity guard (errored without mutating), remove-group, and cleanup to pre-state.
P1 #6: IdentityProvider create/update + discovery test (butleradm)
The `butleradm idp` group gains four verbs, closing audit P1 #6 so an operator can stand up SSO from the CLI (previously list/get/delete only).
| Verb | Mechanism | What it does |
|---|---|---|
| `create` | CRD-direct | Create an IdentityProvider from a manifest |
| `update` | CRD-direct | Update the spec, preserving status conditions and resourceVersion |
| `test` | butler-server | Probe an issuer's OIDC discovery before creating a provider |
| `validate` | butler-server | Probe an existing provider's configured issuer |
create and update are CRD-direct
Both read and write the IdentityProvider directly (it is cluster-scoped, so no namespace flag), like the rest of the idp group. `update` does Get-then-merge-spec-then-Update and applies only the spec, so the controller-managed status conditions and the resourceVersion are preserved. A spec edit never clobbers status.
test and validate are read-only probes
Both go through butler-server, which fetches the issuer's `.well-known/openid-configuration` and reports whether discovery succeeds and which endpoints it found. Neither mutates anything, so neither needs a confirmation guard. `test` takes an issuer URL (pre-create); `validate` uses an existing provider's configured issuer.
Secret handling (v1 choice)
The client secret is supplied by reference: the manifest's `spec.oidc.clientSecretRef` points at a Secret created separately, consistent with `provider create`. The console takes the secret inline and creates it server-side; a `--client-secret-from-file` convenience is queued for later. In exchange, the manifest can set any spec field, including `spec.oidc.insecureSkipVerify` and `spec.oidc.googleWorkspace`, which neither client exposed before.
Also fixed
`idp get` and `idp list` read the pre-nesting field paths (`spec.issuerURL`, `spec.clientID`, `spec.scopes`, `spec.claims`), which are always empty on the current CRD because those fields live under `spec.oidc`. They now read `spec.oidc.*`, so the get detail view and the list ISSUER column render correctly. Found by the live lifecycle test.
Validation
The full lifecycle was exercised end to end against butler-beta: test (valid and clean-failure paths), create, get, update (status conditions and resourceVersion preserved against the real apiserver, no optimistic-concurrency conflict), validate, delete, and cleanup to pre-state.
P1 #5: ProviderConfig get/update + image/network discovery (butleradm)
The `butleradm provider` group gains four verbs, closing audit P1 #5 so a CLI-only operator can run the full cluster-create workflow without falling back to the console.
| Verb | Mechanism | What it does |
|---|---|---|
| `get` | CRD-direct | Show a ProviderConfig as a detail table, or the full object with `-o json/yaml` |
| `update` | CRD-direct | Update the spec from a YAML file, preserving the existing status and resourceVersion |
| `images` | butler-server | List the images a provider can use, for finding the image ID `cluster create` needs |
| `networks` | butler-server | List the networks a provider can use |
get and update are CRD-direct
Both read and write the ProviderConfig directly, like the rest of the provider group. `update` does Get-then-merge-spec-then-Update and applies only the spec, so the existing validation status and resourceVersion are preserved. A spec edit never clobbers status.
images and networks are policy-filtered
Discovery goes through butler-server so the list is filtered by the same image and network policy the console applies. CLI and console show the same allowed options from one source of truth.
Discovery is supported for the on-premises providers (harvester, nutanix). For cloud providers (aws, azure, gcp) and proxmox the image and network are set directly as fields on the ProviderConfig (for example awsVpcId, gcpImage, azureImageUrn), so those return a clear message naming the provider and pointing at the config fields rather than an error dump.
Validation
Both load-bearing paths are covered by tests: the update merge that preserves status and resourceVersion, and the friendly message for the on-premises-only discovery boundary.
Feature 2.3: management cluster GitOps lifecycle (butleradm)
`butleradm` can now manage the management cluster's GitOps lifecycle end to end, closing the management-scope half of the CLI/console capability gap (Feature #4 in the gap audit). This follows Feature 2.2, which did the same for tenant clusters in `butlerctl`.
| Verb | What it does |
|---|---|
| `status` | Whether GitOps is enabled on the management cluster, and the provider, repository, branch, and sync status |
| `discover` | Helm releases and GitOps engine state on the management cluster |
| `preview` | Render the GitOps layout that would be written (non-mutating) |
| `export` | Write the management cluster inventory to a repository (direct push, or `--create-pr`) |
| `enable` | Bootstrap Flux on the management cluster against a repository |
| `disable` | Uninstall Flux from the management cluster |
Full console parity for the management scope. Commands are thin HTTP clients over butler-server's `/api/management/gitops/*` endpoints, consistent with ADR-016.
The disable guard
Management `disable` is the highest-blast-radius operation in the set: it uninstalls the management cluster's own Flux, which manages the platform's components (butler-controller, butler-console) and the image automation that deploys releases. It has no `--yes` shortcut. To proceed, the operator must supply the exact typed phrase `uninstall-management-flux`, by flag or interactive prompt, so a habitual `--yes` cannot fire it. The guard's help and prompt name what it tears down.
Validation
Read verbs (`status`, `discover`, `preview`, `export`) are validated live against the beta management cluster.
`enable` and `disable` are implemented and unit-tested, with live validation deferred by design. Running either against a live management cluster would re-point or tear down the platform's own Flux, so they are covered by unit tests (request shape, error handling, and the confirm guards) rather than a live run. The disable guard's refuse path is mutation-checked.
v0.9.x Highlights
The v0.9.x release cycle introduced cloud provider support and multi-OS workers:
- butler-api v0.9.0/v0.9.1: LoadBalancerRequest CRD, cloud-aware bootstrap types, multi-OS worker fields, Azure vmSize/imageURN additions
- butler-controller v0.9.0: Multi-OS kubeadm bootstrap for non-Talos workers (Rocky, Flatcar, Bottlerocket, Kairos)
- butler-cli v0.5.0: Cloud bootstrap CLI with credential override flags for GCP, AWS, Azure
- butler-bootstrap v0.3.0: Cloud bootstrap support, LoadBalancerRequest HA
- butler-console v0.4.1: Images page and ImageSync management UI
- butler-server v0.4.1: API dependency bump
- butler-crds v0.8.0: 17 CRDs including LoadBalancerRequest
- butler-provider-harvester v0.2.0: butler-api v0.9.0 dependency bump
- butler-provider-nutanix v0.2.0: butler-api v0.9.0 dependency bump
Cloud Provider Initial Releases
- butler-provider-aws v0.1.0: AWS EC2 VM provisioning via MachineRequest
- butler-provider-gcp v0.1.0: GCP Compute Engine VM provisioning via MachineRequest
- butler-provider-azure v0.1.0: Azure VM provisioning via MachineRequest
Upgrade Guide
From v0.8.x to v0.9.x
- Update CRDs first --
helm upgrade butler-crds oci://ghcr.io/butlerdotdev/charts/butler-crds --version 0.8.0 -n butler-system - Upgrade controller --
helm upgrade butler-controller oci://ghcr.io/butlerdotdev/charts/butler-controller --version 0.9.0 -n butler-system - Upgrade console --
helm upgrade butler-console oci://ghcr.io/butlerdotdev/charts/butler-console --version 0.5.0 -n butler-system - Verify -- Check controller logs and TenantCluster status