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.4 | container | Oct 27, 2026 |
HTTP/WS API server | 0.24.0 | container | Oct 27, 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
- chore: genericize environment-specific examples and fixtures by @atbagan in https://github.com/butlerdotdev/butler-server/pull/105
- fix(auth): stop treating any-team admins as platform admins by @atbagan in https://github.com/butlerdotdev/butler-server/pull/97
- fix(teams): gate team create, update and delete server-side by @atbagan in https://github.com/butlerdotdev/butler-server/pull/92
- fix(auth): require platform admin for platform-level mutations by @atbagan in https://github.com/butlerdotdev/butler-server/pull/93
- fix(auth): scope management and Steward reads by @atbagan in https://github.com/butlerdotdev/butler-server/pull/96
- fix(clusters): require operate role for kubeconfig download and audit it by @atbagan in https://github.com/butlerdotdev/butler-server/pull/95
- fix: gate team provider reads on team visibility by @atbagan in https://github.com/butlerdotdev/butler-server/pull/100
- fix(audit): redact credential fields the scrubber missed and never store a raw body by @atbagan in https://github.com/butlerdotdev/butler-server/pull/102
- fix(auth): team admin is never platform admin; gate env mutations; role-matrix tests by @atbagan in https://github.com/butlerdotdev/butler-server/pull/106
- fix(team): least-privilege Kubernetes RBAC for team roles (viewer=view, operator=edit, admin=admin) by @atbagan in https://github.com/butlerdotdev/butler-controller/pull/115
- 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.
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