Configure Backstage
Install the SKE plugins and configure Backstage to discover the files produced by the publication guide. This guide covers GitHub and AWS S3 discovery.
Prerequisites
-
Generated
catalog-info.yamlfiles in a Git repository or bucket, and read credentials for Backstage. -
Access to edit your Backstage app and its configuration, or a hosted service that supports both SKE plugins and the required discovery provider.
-
Backstage app running v1.40 or above: The current SKE plugin release documents compatibility with v1.55.0. See the plugin releases for the compatibility statement for your version. If you do not have a Backstage app, refer to the Backstage official documentation for instructions on how to create a Backstage app.
-
Node.js 22+: Starting from Backstage v1.50, Node.js 22 or higher is required due to native module dependencies. Ensure your build environment and Docker images use Node 22+.
-
NODE_OPTIONSenvironment variable: Set the following when starting your Backstage app:export NODE_OPTIONS=--no-node-snapshot
Accessing the private npm registry
The SKE plugins are distributed through a private npm registry. Please follow the steps below to enable access to it.
- For local development
- For Docker
To access the private npm registry on your local machine, you will need npm config in a local ~/.npmrc file in your home directory. Create this file if it doesn't already exist,
and add the following content:
# replace <YOUR_TOKEN_HERE> with the provided Syntasso registry token
@syntasso:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken="<YOUR_TOKEN_HERE>"
always-auth=true
For Yarn 3+, use the following command to set a local .yarnrc.yml file:
yarn config set npmScopes --json '{"syntasso": {"npmAlwaysAuth": true, "npmAuthToken": "<YOUR TOKEN HERE>", "npmRegistryServer": "https://npm.pkg.github.com"}}'
For Yarn 3+, keep the registry scope in your app's .yarnrc.yml:
npmScopes:
syntasso:
npmAlwaysAuth: true
npmRegistryServer: https://npm.pkg.github.com
npmAuthToken: ${SYN_REGISTRY_TOKEN}
Provide the token to the dependency-install layer as a BuildKit secret. Adapt the existing
RUN yarn install in your Dockerfile, preserving the app's cache mounts and install arguments:
RUN \
yarn install --immutable
Build with the token exported in your shell:
export SYN_REGISTRY_TOKEN=<YOUR_TOKEN>
docker build --secret id=syntasso_token,env=SYN_REGISTRY_TOKEN -f packages/backend/Dockerfile .
For Yarn 1, mount your authenticated .npmrc at the install layer instead:
RUN \
yarn install --frozen-lockfile --production --network-timeout 300000
Pass --secret id=npmrc,src=$HOME/.npmrc to your existing Docker build command.
Keep registry tokens out of Docker build arguments and image layers.
Install the plugins
Two packages, one for each half of Backstage:
yarn add @syntasso/plugin-ske-backend@0.24.0 --cwd packages/backend
yarn add @syntasso/plugin-ske-frontend@0.22.0 @backstage/plugin-permission-react --cwd packages/app
The frontend package requires the host app to provide @backstage/plugin-catalog,
@backstage/plugin-catalog-graph, @backstage/plugin-catalog-react,
@backstage/plugin-scaffolder, @backstage/plugin-scaffolder-react and
@backstage/plugin-permission-react. Check peer dependency warnings during installation.
These instructions use @syntasso/plugin-ske-backend v0.24.0 and
@syntasso/plugin-ske-frontend v0.22.0, matching the current
Portal Controller requirements. Install or upgrade
the plugins before the controller so generated Templates do not call missing actions.
If you are using Spotify Portal for Backstage, publish the plugins to your cloud instance before completing the install. See hosted and restricted installations.
Backend
Add the plugin in packages/backend/src/index.ts:
// SKE plugin
backend.add(import('@syntasso/plugin-ske-backend'));
That single line registers the scaffolder actions the generated Templates use, the routes the frontend calls, and the catalog provider that surfaces pending requests.
The plugin creates one database table and runs its migrations at startup. If you run more than one Backstage replica, use PostgreSQL rather than SQLite.
Frontend
Choose the instructions for your app's frontend system. Do not apply the legacy
EntityPage.tsx changes to a new-frontend app.
New frontend system
If your Backstage app uses the new frontend system (identified by createApp from @backstage/frontend-defaults), the plugin is automatically discovered; no
changes to EntityPage.tsx are needed. This is also the case if you are using Spotify Portal for Backstage.
The plugin registers the following extensions via its alpha entry point:
- Promise Status: the Status tab for Promise entities.
- Resource Status and Manage: the Status and Manage tabs for Resource entities.
- Pending Request Status: approval and provisioning state before a resource exists.
To verify the plugin is loaded, check your Backstage app configuration. The plugin should appear under @syntasso/plugin-ske-frontend/alpha in the resolved plugin list.
Then add the plugin's scaffolderModuleResourceRequestCopy module to your app's features in
packages/app/src/App.tsx:
import { scaffolderModuleResourceRequestCopy } from '@syntasso/plugin-ske-frontend/alpha';
const app = createApp({
features: [scaffolderModuleResourceRequestCopy /* , ... */],
});
This makes the scaffolder task page describe a request as creating, updating or deleting the
resource. Without it the page shows the Template's own step names, which are the same for a create
and an update. Backstage cannot register the module for you because it replaces the scaffolder
plugin's API. See scaffolderModuleResourceRequestCopy.
On a host where you cannot edit the app, such as Roadie or Spotify Portal for Backstage, the module cannot be added. The task page then shows the Template's own step names, "Submitting your Postgresql request" and "Registering your Postgresql request in the catalog", which do not say whether the run creates or updates the resource.
Legacy frontend system
If App.tsx imports createApp from @backstage/app-defaults, your app uses the legacy system. In
packages/app/src/components/catalog/EntityPage.tsx, import the entity pages:
import {
KratixPendingRequestEntityPage,
KratixPromiseEntityPage,
KratixResourceEntityPage,
isKratixType,
} from '@syntasso/plugin-ske-frontend';
Then add three cases to the existing componentPage switch, before the default case:
<EntitySwitch.Case if={isKratixType('promise')}>
<KratixPromiseEntityPage>{entityWarningContent}</KratixPromiseEntityPage>
</EntitySwitch.Case>
<EntitySwitch.Case if={isKratixType('pending-request')}>
<KratixPendingRequestEntityPage>{entityWarningContent}</KratixPendingRequestEntityPage>
</EntitySwitch.Case>
<EntitySwitch.Case if={isKratixType('resource')}>
<KratixResourceEntityPage>
{entityWarningContent}
</KratixResourceEntityPage>
</EntitySwitch.Case>
The matcher selects Promise and Resource Components using either the kratix.io/type
annotation or spec.type. For example, a Resource can use kratix.io/type: resource or
spec.type: kratix-resource.
Add kratix.io/type: promise to every Promise Component. Although the route matcher
recognises spec.type: kratix-promise, the page also needs the annotation when it loads
Promise data. Without it, the Promise Overview does not load. It shows a
missing-annotation message for Resource fields instead of the Promise details.
This gives Promise, Resource and pending request entities their Status pages, and Resources a Manage tab for updating and deleting the resource. For the full list of entity pages, components, annotations, and behaviours, see the SKE Frontend Plugin reference.
Hosted and restricted installations
For a hosted app, use its plugin installer and confirm that it supports both the frontend and backend packages, plus your discovery provider. App-level API overrides require access to app source; hosted services may not expose it.
For an air-gapped app, download the pinned packages on a connected machine with registry access:
npm pack @syntasso/plugin-ske-backend@0.24.0
npm pack @syntasso/plugin-ske-frontend@0.22.0
Transfer the tarballs and their required dependencies to your internal registry, then install from that registry. Keep the package names and versions unchanged. The app build also needs access to its Backstage dependencies. For Spotify Portal, see the plugin publishing steps.
Give Backstage access to GitHub
integrations:
github:
- host: github.com
token: ${GITHUB_TOKEN}
The credential needs:
- Contents: read on the catalog repository the Portal Controller writes to.
- Pull requests: write on the repositories developers raise requests against, for pull-request mode.
- Contents: write on the requests repository, if you use
ske.mode: gitopsorhybrid.
Backstage never writes the catalog repository, Kratix does, using the state store's own credential. But Backstage does read it. A token scoped only to the request repositories gives you working submissions and a silently empty catalog.
Discover the generated entities
Choose the provider matching your catalog storage. The SKE backend's pending request provider is registered by the SKE plugin; it does not discover files from your state store.
GitHub discovery
Install and register the GitHub discovery provider:
yarn add @backstage/plugin-catalog-backend-module-github --cwd packages/backend
// Keep the existing catalog backend registration.
backend.add(import('@backstage/plugin-catalog-backend-module-github'));
The Portal Controller writes Promise entities and Resource entities into two separate trees, so configure one catalog provider for each:
catalog:
providers:
github:
platformPromiseCatalog:
organization: acme
catalogPath: /gitops/backstage/prod/dependencies/**/catalog-info.yaml
filters:
branch: main
repository: '^platform-catalog$'
schedule:
frequency: { minutes: 1 }
timeout: { minutes: 3 }
platformResourceCatalog:
organization: acme
catalogPath: /gitops/backstage/prod/resources/**/catalog-info.yaml
filters:
branch: main
repository: '^platform-catalog$'
schedule:
frequency: { minutes: 1 }
timeout: { minutes: 3 }
Fill the paths in from your portal's configuration:
| Placeholder | Comes from |
|---|---|
gitops/backstage/prod | The state store's path, then the Destination's path |
repository | The repository in the state store's url |
branch | The state store's branch |
organization | Your GitHub organisation |
Two narrow providers rather than one glob over the whole prefix, because catalogPath takes a
single glob and a wider one would ingest anything else that lands under it.
Ensure your catalog rules permit Component and Template; both are allowed by the default rules.
AWS S3 discovery
Install and register the AWS S3 discovery provider:
yarn add @backstage/plugin-catalog-backend-module-aws --cwd packages/backend
// Keep the existing catalog backend registration.
backend.add(import('@backstage/plugin-catalog-backend-module-aws'));
Configure the S3 reader and provider
in app-config.yaml. This example uses environment credentials:
integrations:
awsS3:
- accessKeyId: ${AWS_ACCESS_KEY_ID}
secretAccessKey: ${AWS_SECRET_ACCESS_KEY}
catalog:
providers:
awsS3:
platformPromiseCatalog:
bucketName: platform-catalog
prefix: gitops/backstage/prod/dependencies/
region: eu-west-1
schedule:
frequency: { minutes: 1 }
timeout: { minutes: 3 }
platformResourceCatalog:
bucketName: platform-catalog
prefix: gitops/backstage/prod/resources/
region: eu-west-1
schedule:
frequency: { minutes: 1 }
timeout: { minutes: 3 }
Set the bucket name and region from your BucketStateStore. Each prefix combines the state
store's path with the Destination's path, then dependencies/ or resources/. Permit Backstage
to list those prefixes and read their objects. For workload or assumed-role credentials, use
the reader's documented credential configuration.
A Kratix BucketStateStore supporting an S3-compatible endpoint does not guarantee that
Backstage's AWS discovery provider supports that endpoint. Confirm both the reader and
discovery provider work with your storage, or install a compatible provider.
Verify
Once Backstage has run a catalog refresh you should see:
- A
Componentfor each Promise bound to the portal, with a matching scaffolder Template on the Create page. - A
Componentfor each Resource request, with Manage available on it.
If entities do not appear, check the repository branch or bucket prefixes contain the generated files, the provider module is registered, and Backstage can list and read them. A bucket provider scans its prefix, so keep unrelated documents outside those prefixes.
Next, send resource requests to Kratix.
Task log detail
Backstage builds each task's logger at LOG_LEVEL, defaulting to info. The SKE actions log their
mechanics at debug, so set LOG_LEVEL=debug on the backend to see them.
