Installation
Requirements
| Component | Minimum version | Needed for |
|---|---|---|
| SKE Operator | v0.32.1 | Installing the portal-controller integration type |
| Portal Controller | v0.2.0 | This integration |
| Portal Patch | 0.7.0 | Declarative customization, optional |
@syntasso/plugin-ske-backend | v0.21.0 | Backstage portals |
@syntasso/plugin-ske-frontend | v0.18.0 | Backstage portals |
Install the Portal Controller
The Portal Controller is installed and upgraded by the
SKE Operator from a single
SKEIntegration resource of
spec.type: portal-controller. Only one SKEIntegration of this type may exist on a platform at
a time.
apiVersion: platform.syntasso.io/v1alpha1
kind: SKEIntegration
metadata:
name: portal-controller
spec:
type: portal-controller
version: v0.2.0
portals: []
Applying the resource installs the Portal Controller Deployment, the PortalCustomization Custom
Resource Definition, and the RBAC and Service Account it runs as, into the
kratix-platform-system namespace.
Connect a portal
Each entry under spec.portals describes one portal:
| Field | Required | Meaning |
|---|---|---|
name | yes | Identifies the portal. Must be unique within type, so you can have two Backstage portals, but not two both named prod. |
type | yes | The portal type this entry targets, for example backstage or cortex. |
url | depends on type | The portal's base URL. Required for Cortex, unused by Backstage. |
secretRef | depends on type | Names the portal's credential Secret. Required for Cortex, unused by Backstage. |
config | depends on type | Settings for this portal only. The keys differ per type. |
adapterConfig | no | Adapter image, pull secrets and generate flags. |
You can declare more than one portal, of the same or different types, on the same
SKEIntegration. What each field needs varies by type:
Credential Secret handling
The Portal Controller does not create or manage a portal's credential Secret, and not every portal needs one. Backstage does not, since it never calls a live API.
Where a portal's secretRef is set, create the Secret yourself in kratix-platform-system before
installing or upgrading, with the credential under a token key:
apiVersion: v1
kind: Secret
metadata:
name: cortex-token
namespace: kratix-platform-system
type: Opaque
stringData:
token: <cortex-api-token>
The operator validates only that a referenced Secret exists, not that it contains a usable
credential. If it is missing, the SKEIntegration reports IntegrationDeploymentReady: False
until you create it.
Once installed, the credential is injected directly into the sync pipeline's writer container, and for Cortex the status exporter, using a secret key reference. It is never written to a shared volume and never reaches the generate or configure stages.
Bind a Promise to a portal
A Promise, or a Resource request, opts into a portal with a label:
metadata:
labels:
kratix.io/portal-backstage: "true" # opt in to the default portal of type "backstage"
If you have more than one portal of the same type, name the one you want with an instance suffix:
metadata:
labels:
kratix.io/portal-backstage.prod: "true" # opt in to the portal named "prod"
On a Promise, only the presence of the label matters. Its value is not read, so
kratix.io/portal-backstage: "false" still opts the Promise in. To opt a Promise out, remove the
label.
Opting a single Resource out
Resource requests inherit their Promise's labels. To keep one request out of the portal, set the
same label to the string "false" on that request:
metadata:
labels:
kratix.io/portal-backstage: "false" # this request only, on an opted-in Promise
The request is skipped silently, with no entity generated and no condition reported. Use the same key the Promise resolved to, including the instance suffix if it used one.
Binding status
A Promise reports how its binding resolved on the PortalBindingReady condition:
| Reason | Meaning |
|---|---|
BindingResolved | The label resolved to exactly one portal |
AmbiguousPortalInstance | More than one portal of the type exists and no instance was named |
PortalConnectionNotFound | The named instance does not match a configured portal |
PortalIntegrationNotFound | No portal-controller SKEIntegration is installed |
CustomizationNotFound | The named PortalCustomization does not exist |
AmbiguousCustomization | More than one PortalCustomization label is present |
Once bound, each sync reports on a per-type condition, BackstagePortalSynced or
CortexPortalSynced:
| Reason | Meaning |
|---|---|
EntitySynced | The entity shipped successfully |
SyncInProgress | The sync Job is running |
GenerateOrWriteFailed | The Job failed before shipping the entity |
PromiseHasNoAPI | The Promise has no spec.api to render into an entity |
On success the Promise is annotated with kratix.io/portal-<type>-entity-url, pointing at the
entity in the portal.
Adapter image resolution
By default, a backstage or cortex portal uses the adapter bundled inside the Portal
Controller's own image, at the version set on spec.version. Most platforms never change this.
To pin a different adapter image for one portal, for example to roll a fix out to a single portal
before the rest, set adapterConfig.image to a single image reference (repo:tag or
repo@sha256, there is no separate version field):
portals:
- name: prod
type: backstage
adapterConfig:
image: ghcr.io/syntasso/ske-portal-controller:v0.2.1
Other adapterConfig fields
| Field | Applies to | Meaning |
|---|---|---|
image | all portals | Adapter image override |
imagePullSecrets | all portals | Extra pull secret names, always merged with syntasso-registry |
generate.args | all portals | Flags passed verbatim to the generate stage |
exporter.deploymentSpec | Cortex portals | Overlay merged into the status exporter's Deployment |
Portal-wide generate flags
adapterConfig.generate.args is a list of flags passed verbatim to the adapter's generate stage
for that portal. Every Promise and Resource routed through the portal gets the same behaviour, so
this is a portal-wide setting, not a per-Promise one:
| Flag | Portal type | Effect |
|---|---|---|
--single-spec-page | backstage | Merges the generated Template's spec form pages into one, instead of one page per top-level spec property |
--fixed-namespace=<value> | cortex | Hard-codes the namespace on the generated create workflow and hides the namespace input. Must be a valid DNS-1123 label |
portals:
- name: prod
type: cortex
# ... url, secretRef, config as below
adapterConfig:
generate:
args:
- "--fixed-namespace=team-a"
Portal Patch offers per-Promise equivalents of both, through the
singleSpecPage and hideNamespaceManifest recipes. Use the flags when you want the behaviour
everywhere on a portal, and a recipe when you want it for particular Promises.
Do not put credentials in generate.args. Unlike a secretRef credential, which is injected into
the writer container as a secret key reference, these flags are passed as container arguments and
are visible to anyone with read access to the Job in kratix-platform-system.
Air-gapped environments
Mirror the following images into your own registry:
| Image | Needed for |
|---|---|
ghcr.io/syntasso/ske-portal-controller:<version> | The controller and its bundled adapter, one image |
ghcr.io/syntasso/portal-patch:<version> | Only if you use Portal Patch recipes |
Any adapterConfig.image | Only if you override the adapter image |
Any PortalCustomization container image | Your own configure containers |
The Kratix pipeline adapter image is also required, but it is part of the SKE platform install rather than this integration.
Point the operator at your registry with its image registry configuration, the same way as other
SKE components. Pull secrets come from adapterConfig.imagePullSecrets, which is always merged
with syntasso-registry.
