Backstage Dynamic Plugins with Red Hat Developer Hub

This article will teach you how to create Backstage dynamic plugins and install them smoothly in Red Hat Developer Hub. One of the most significant pain points in Backstage is the installation of plugins. If you want to run Backstage on Kubernetes, for example, you have to rebuild the project and create a new image containing the added plugin. Red Hat Developer solves this problem by using dynamic plugins. In an earlier article about Developer Hub, I demonstrated how to activate selected plugins from the list of built-in extensions. These extensions are available inside the image and only require activation. However, creating your plugin and adding it to Developer Hub requires a different approach. This article will focus on just such a case.
For comparison, please refer to the article where I demonstrate how to prepare a Backstage instance for running on Kubernetes step-by-step. Today, for the sake of clarity, we will be working in a Developer Hub instance running on OpenShift using an operator. However, we can also easily install Developer Hub on vanilla Kubernetes using a Helm chart.
Source Code
Feel free to use my source code if you’d like to try it out yourself. To do that, you must clone my sample GitHub repository. We will also use another repository with a sample Backstage plugin. This time, I won’t create a plugin myself, but I will use an existing one. The plugin provides a collection of scaffolder actions for interacting with Kubernetes on Backstage, including apply and delete. Once you clone both of those repositories, you should only follow my instructions.
Prerequisites
You must have an OpenShift cluster with the Red Hat Developer Hub installed and configured with a Kubernetes plugin. I will briefly explain it in the next section without getting more into the details. You can find more information in the already mentioned article about Red Hat Developer Hub.
You must also have Node.js, NPM, and Yarn installed and configured on your laptop. It is used for plugin compilation and building. The npm package @janus-idp/cli
used for developing and exporting Backstage plugins as dynamic plugins, requires Podman when working in image mode.
Motivation
Red Hat Developer Hub comes with a curated set of plugins preinstalled on its container image. It is easy to enable such a plugin just by changing the configuration available in Kubernetes ConfigMap
. The situation becomes complicated when we attempt to install and configure a third-party plugin. In this case, I would like to extend Backstage with a set of actions that enable the creation and management of Kubernetes resources directly, rather than applying them via Argo CD. To do that, we must install the Backstage Scaffolder Actions for Kubernetes plugin. To use this plugin in Red Hat Developer Hub without rebuilding its image, you must export plugins as derived dynamic plugin packages. This is our goal.
Convert to a dynamic Backstage plugin
The backstage-k8s-scaffolder-actions is a backend plugin. It meets all the requirements to be converted to a dynamic plugin form. It has a valid package.json
file in its root directory, containing all required metadata and dependencies. That plugin is compatible with the new Backstage backend system, which means that it was created using createBackendPlugin()
or createBackendModule()
. Let’s clone its repository first:
$ git clone https://github.com/kirederik/backstage-k8s-scaffolder-actions.git
$ cd backstage-k8s-scaffolder-actions
ShellSessionThen we must install the @janus-idp/cli npm package with the following command:
yarn add @janus-idp/cli
ShellSessionAfter that, you should run both those commands inside the plugin directory:
$ yarn install
$ yarn build
ShellSessionIf the commands were successful, you can proceed with the plugin conversion procedure. The plugin defines some shared dependencies that must be explicitly specified with the --shared-package
flag.

Here’s the command used to convert our plugin to a dynamic form supported by Red Hat Developer Hub:
npx @janus-idp/cli@latest package export-dynamic-plugin \
--shared-package '!@backstage/cli-common' \
--shared-package '!@backstage/cli-node' \
--shared-package '!@backstage/config-loader' \
--shared-package '!@backstage/config' \
--shared-package '!@backstage/errors' \
--shared-package '!@backstage/types'
ShellSessionPackage and publish Backstage dynamic plugins
After exporting a third-party plugin, you can package the derived package into one of the following supported formats:
- Open Container Initiative (OCI) image (recommended)
- TGZ file
- JavaScript package
Since the OCI image option is recommended, we will proceed accordingly. However, first you must ensure that podman
is running on your laptop and is logged in to your container registry.
podman login quay.io
ShellSessionThen you can run the following @janus-idp/cli
command with npx
. It must specify the target image repository address, including image name and tag. The target image address is quay.io/pminkows/backstage-k8s-scaffolder-actions:v0.5
. It is tagged with the latest version of the plugin.
npx @janus-idp/cli@latest package package-dynamic-plugins \
--tag quay.io/pminkows/backstage-k8s-scaffolder-actions:v0.5
ShellSessionHere’s the command output. Ultimately, it provides instructions on how to install and enable the plugin within the Red Hat Developer configuration. Copy that statement for future use.

The previous command packages the plugin and builds its image.

Finally, let’s push the image with our plugin to the target registry:
podman push quay.io/pminkows/backstage-k8s-scaffolder-actions:v0.5
ShellSessionInstall and enable the Backstage dynamic plugins in Developer Hub
My instance of Developer Hub is running in the backstage
namespace. The operator manages it.

Here’s the Backstage
CR object responsible for creating a Developer Hub instance. The dynamicPluginsConfigMapName
property specifies the name of the ConfigMap
that stores the plugins’ configuration.
apiVersion: rhdh.redhat.com/v1alpha3
kind: Backstage
metadata:
name: developer-hub
namespace: backstage
spec:
application:
appConfig:
configMaps:
- name: app-config-rhdh
mountPath: /opt/app-root/src
dynamicPluginsConfigMapName: dynamic-plugins-rhdh
extraEnvs:
secrets:
- name: app-secrets-rhdh
extraFiles:
mountPath: /opt/app-root/src
replicas: 1
route:
enabled: true
database:
enableLocalDb: true
YAMLThen, we must modify the dynamic-plugins-rhdh
ConfigMap
to register our plugin in Red Hat Developer Hub. You must paste the previously copied two lines of code generated by the npx @janus-idp/cli@latest package package-dynamic-plugins
command.
kind: ConfigMap
apiVersion: v1
metadata:
name: dynamic-plugins-rhdh
namespace: backstage
data:
dynamic-plugins.yaml: |-
plugins:
# ... other plugins
- package: oci://quay.io/pminkows/backstage-k8s-scaffolder-actions:v0.5!devangelista-backstage-scaffolder-kubernetes
disabled: false
YAMLThat’s all! After the change is applied to ConfigMap
, the operator should restart the pod with Developer Hub. It can take some time, as the pod will be restarted, since all plugins must be enabled during pod startup.
$ oc get pod
NAME READY STATUS RESTARTS AGE
backstage-developer-hub-896c5f9d9-vvddb 1/1 Running 0 4m21s
backstage-psql-developer-hub-0 1/1 Running 0 8d
ShellSessionYou can verify the logs with the oc logs
command. Developer Hub prints a list of available actions provided by the installed plugins. You should see three actions delivered by the Backstage Scaffolder Actions for Kubernetes plugin, starting with the kube:
prefix.

Prepare the Backstage template
Finally, we will test a new plugin by calling the kube:apply
action from our Backstage template. It uses the kube:apply
to create a Secret
in the specified namespace under a given name. This template is available in the backstage-templates
repository.
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
description: Create a Secret in Kubernetes
name: create-secret
title: Create a Secret
spec:
lifecycle: experimental
owner: user
type: example
parameters:
- properties:
name:
description: The namespace name
title: Name
type: string
ui:autofocus: true
required:
- name
title: Namespace Name
- properties:
secretName:
description: The secret name
title: Secret Name
type: string
ui:autofocus: true
required:
- secretName
title: Secret Name
- title: Cluster Name
properties:
cluster:
type: string
enum:
- ocp
ui:autocomplete:
options:
- ocp
steps:
- action: kube:apply
id: k-apply
name: Create a Resouce
input:
namespaced: true
clusterName: ${{ parameters.cluster }}
manifest: |
kind: Secret
apiVersion: v1
metadata:
name: ${{ parameters.secretName }}
namespace: ${{ parameters.name }}
data:
username: YWRtaW4=
https://github.com/piomin/backstage-templates/blob/master/templates.yamlYou should import the repository with templates into your Developer Hub instance. The app-config-rhdh
ConfigMap
should contain the full address templates list file in the repository, and the Kubernetes cluster address and connection credentials.
catalog:
rules:
- allow: [Component, System, API, Resource, Location, Template]
locations:
- type: url
target: https://github.com/piomin/backstage-templates/blob/master/templates.yaml
rules:
- allow: [Template, Location]
kubernetes:
clusterLocatorMethods:
- clusters:
- authProvider: serviceAccount
name: ocp
serviceAccountToken: ${OPENSHIFT_TOKEN}
skipTLSVerify: true
url: https://api.${DOMAIN}:6443
type: config
customResources:
- apiVersion: v1beta1
group: tekton.dev
plural: pipelineruns
- apiVersion: v1beta1
group: tekton.dev
plural: taskruns
- apiVersion: v1
group: route.openshift.io
plural: routes
serviceLocatorMethod:
type: multiTenant
YAMLYou can access the Developer Hub instance through the OpenShift Route
:
$ oc get route
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
backstage-developer-hub backstage-developer-hub-backstage.apps.piomin.ewyw.p1.openshiftapps.com / backstage-developer-hub http-backend edge/Redirect None
ShellSessionThen find and use the following template available in the Developer Hub.

You will have to insert the Secret
name, namespace, and choose a target OpenShift cluster. Then accept the action.

You should see a similar screen. The Secret has been successfully created.

Let’s verify if it exists on our OpenShift cluster:

Final Thoughts
Red Hat is steadily developing its Backstage-based product, adding new functionality in future versions. The ability to easily create and install custom plug-ins in the Developer Hub appears to be a key element in building an attractive platform for developers. This article focuses on demonstrating how to convert a standard Backstage plugin into a dynamic form supported by Red Hat Developer Hub.
Leave a Reply