Portal Controller
Overview
The Portal Controller syncs Kratix Promises and Resource requests into a developer portal, such as Backstage or Cortex, from a single, unified integration. It replaces the Backstage Controller and SKE Cortex Controller, which are now deprecated in its favour. See Portal Controller for the full documentation.
The release manifest is available at:
http://syntasso-enterprise-releases.s3-website.eu-west-2.amazonaws.com/#ske-portal-controller/
Release Notes
v0.8.0 (2026-10-23)
Released via the SKE Operator Helm Chart version 0.104.0
Features
- Rerun a portal sync on demand. Add the
portal.kratix.io/manual-reconciliation: "true"label to a Promise or Resource request, and the Portal Controller syncs it again. The label removes itself, so you can apply it again later. See Manual reconciliation.
Security Fixes
- Rebuilt with Go 1.27.3 to fix CVE-2026-111943 in the Go standard library (
crypto/x509).
Maintenance
- Base image updates.
v0.7.1 (2026-10-19)
Released via the SKE Operator Helm Chart version 0.101.1
Bug Fixes
- The Portal Controller leaves Kratix's workflow restart label unchanged. This prevents extra portal sync Jobs and stops the controller from consuming a restart intended for Kratix.
Maintenance
- Base image updates.
v0.7.0 (2026-10-09)
Released via the SKE Operator Helm Chart version 0.99.0
Bug Fixes
- Cortex portals now sync Promises that have
int-or-string fields (
x-kubernetes-int-or-string). These fields show as a text input in the generated workflows. Before, the workflows failed to build and nothing was synced. - Deleting a Promise synced to Cortex no longer gets stuck.
Maintenance
- Base image updates.
v0.6.1 (2026-10-06)
Released via the SKE Operator Helm Chart version 0.97.0
Features
-
A Backstage portal can now choose the namespace of a resource request for the developer, using the new
requestNamespacesetting:from: group: the developer picks one of their Backstage Groups, and the request goes to the namespace named after it.from: groupwith anannotation: the namespace is read from that annotation on the Group.from: fixed: every request goes to one namespace. Use this instead of thehideNamespaceportal-patch recipes.
These settings need
@syntasso/plugin-ske-backendv0.24.0 and@syntasso/plugin-ske-frontendv0.22.0 or later. See Choosing the request namespace. -
A resource requested for a Backstage Group is now owned by that Group in the catalogue, so it shows on the Group's page. An explicit
ske.backstage.component/ownerannotation still takes precedence. -
Cortex requests now appear straight away as a
<name> request (pending)entity, owned by the person who asked for it. The end of the workflow links to the entity. Before, nothing appeared until the platform had built the resource. Deleting a request that was never built also deletes its entity.
Maintenance
- Base image updates.
v0.5.0 (2026-09-29)
Released via the SKE Operator Helm Chart version 0.95.0
Generated Backstage Templates now put every spec field on one form page by default. Existing
Backstage connections that don't set --single-spec-page switch to the single-page form when you
upgrade. To keep the multi-step form, add --single-spec-page=false to the connection's
adapterConfig.generate.args before upgrading. If you run ske-portal-adapter directly in your
own Promise pipeline, add --single-spec-page=false as a container argument instead: the
TEMPLATE_SINGLE_SPEC_PAGE environment variable can't turn the single-page form off. See
Portal-wide generate flags.
Features
- Generated Backstage Templates now link to the result when a task completes: View in Catalog first, then Pull Request in pull-request mode. The catalogue link goes to the pending entity until the real one exists.
- Generated Templates now name each step after its outcome and the resource kind, for example "Submitting your Jenkins request". Push-mode Templates now also record whether each run is a create or an update, as pull-request mode already did.
Maintenance
- Base image updates. Bump dependencies.
v0.4.2 (2026-09-22)
SKE v0.59.0 removed the workflow counters from Promise and resource request status, and this
version tracks that change. On earlier Portal Controller versions, resource requests stay
unreconciled once SKE is upgraded.
Maintenance
- Base image updates. Bump dependencies.
v0.4.1 (2026-09-07)
Bug Fixes
- Resource-request pipelines no longer leak ServiceAccounts and RBAC. Each resource request used to get its own ServiceAccount, Role and RoleBinding, and none of them were ever deleted. The resource-request pipelines of a Promise now share a single set, and deleting a Promise cleans up everything it created — including what earlier versions left behind.
v0.4.0 (2026-08-27)
Upgrade the SKE Operator to v0.34.0 first.
Features
-
The Portal Controller no longer needs the
skeintegrationsCRD. It reads its connections from a ConfigMap, so it can be installed on its own, without the SKE Operator. See Installing without the SKE Operator. -
The Jobs the controller creates are now configurable. Labels, annotations and other pod settings come from the
ske-portal-workloads-configConfigMap, so the Jobs can satisfy cluster policies that would otherwise reject them. See Installation.
Bug Fixes
-
Immutable fields are now declared as immutable in generated Templates, so an edit that would change one is rejected up front rather than failing later.
-
The Portal Controller no longer consumes Kratix's manual-reconciliation flag.
v0.3.0 (2026-08-18)
Features
-
Portal Sync condition is now filtered out from the generated Entity.: similar to other Kratix-owned conditions, the Portal Sync conditions are no longer added to the resource Entity when they are generated.
-
Push-mode Templates record the request. A generated
pushTemplate now applies the resource request and then hands it to the Backstage backend, so the developer sees a pending request in the catalogue while the platform catches up rather than nothing at all. Pull-request Templates are unchanged.
This release requires ske-backend v0.23.0 and ske-frontend v0.20.0 or later.
Upgrade the plugins before the Portal Controller. Doing it in that order is safe: the new plugins accept everything the current Templates send, so nothing changes until the controller follows.
In the other order, newly generated push Templates carry a record-request step that an older
backend rejects. Because that step runs after the request has been applied, the developer sees a
failed task for a resource that was in fact created.
v0.2.0 (2026-07-30)
The first release of the Portal Controller: one integration for every developer portal your platform exposes.
Until now, each portal needed its own controller, its own SKEIntegration, and its own customization CRD. Adding a second portal meant running a second controller, and the two
had little in common. The Portal Controller replaces that with a single integration that declares every portal on one resource, generates entities through one pipeline, and gives
Promise authors one way to shape what those entities look like.
Both the Backstage Controller and the SKE Cortex Controller are deprecated as of this release. They continue to work, so you can migrate when it suits you. See Migrating from Backstage and Migrating from Cortex.
What you gain over the standalone controllers
The Portal Controller generates the same entities the standalone controllers did. What changes is everything around them:
| Standalone controllers | Portal Controller | Portals | |
|---|---|---|---|
| Portals per platform | One SKEIntegration per type, so one Backstage and one Cortex | Every portal on one SKEIntegration, including two Backstage instances side by side | Both |
| Opting a Promise in | Per-portal wiring | One label, kratix.io/portal-<type>, and a single Resource can opt back out | Both |
| Customizing what is generated | Backstage had BackstageEntityCustomization, one pipeline shared by the Promise and its Resources. Cortex had a handful of annotations and no way to edit the generated documents | PortalCustomization with independent promise and resource lanes, for every portal type | Both |
| Expressing a customization | Typed entityGenerationOptions fields, or a hand-written container | Declarative Portal Patch recipes, with your own container still available | Both |
| Reporting | Per-controller | One PortalBindingReady condition plus a per-type sync condition and an entity URL annotation | Both |
| Reviewing requests | Not available: submitting a form applied it immediately | pull-request delivery mode raises the request as a pull request, and update and delete go through review too | Backstage |
| Where entities are written | A Destination you create and label environment: backstage beforehand | The portal creates and owns its Destination and state store, or adopts an existing one | Backstage |
For Cortex specifically, the headline change is customization. The standalone controller
generated its entity and workflows the one way it knew how, and the only levers were annotations.
A Cortex portal now runs the same generate → configure → writer pipeline as every other portal, so
you can reshape the generated entity and workflows at both the Promise and the Resource level. See
Cortex examples.
Two things become portal-wide rather than per-Promise: the Cortex fixed namespace, previously the
kratix.io/cortex-fixed-namespace annotation, and the Backstage single-page form. Both are now
adapterConfig.generate.args flags on the portal. Where you need them per Promise, the
hideNamespaceManifest and singleSpecPage Portal Patch recipes do that job.
Features
-
One integration, many portals. Declare every portal your platform exposes on a single
SKEIntegrationoftype: portal-controller. Portals of the same type can coexist, so a staging and a production Backstage are two entries rather than two installations. See Installation. -
Opt in with a label. A Promise or Resource request joins a portal by carrying
kratix.io/portal-<type>, instead of needing a dedicated resource per portal. A single Resource can opt back out without affecting its Promise. See Bind a Promise to a portal. -
Request review with pull-request delivery. Backstage portals can run in
pull-requestmode, where submitting the generated form raises a pull request against a Git repository instead of applying the request directly. Nothing reaches the platform until a human merges it, and updates and deletions go through the same review. Currently GitHub only. See Delivery modes. -
Backstage portals manage their own destination. A portal can create and own the Kratix Destination and state store it writes entities to, rather than requiring you to wire one up beforehand. Platforms that already manage Destinations can adopt an existing one instead. See Backstage.
-
Customizable Cortex entities and workflows. A Cortex portal runs the same
generate → configure → writerpipeline as every other portal type, so the generated entity and workflows can be reshaped at both the Promise and the Resource level. It also runs a status exporter and a setup Job that provisions the shared entity and relationship types the integration depends on. See Cortex. -
Shape entities declaratively. The new
PortalCustomizationresource, which replacesBackstageEntityCustomization, runs containers that edit generated documents before they ship, with separate lanes for the Promise and its Resources. Paired with Portal Patch, most customizations are a few lines of declarative recipe rather than a hand-written script. See Customization.
Known issues
- Portal Patch recipes that target the
ske-configure-resourcestep, includinghideNamespaceManifest, fail underpull-requestdelivery mode, because that step only exists inpushmode. A recipe that matches nothing aborts the sync, so no entity ships.
