To install a dynamic plugin, you need to add the plugin definition to the dynamic-plugins.yaml file.
The placement of dynamic-plugins.yaml depends on the deployment method.
For more information, see Installing Dynamic Plugins with the Red Hat Developer Hub Operator or Installing Dynamic Plugins Using the Helm Chart.
Plugins are defined in the plugins array in the dynamic-plugins.yaml file. Each plugin is defined as an object with the following properties:
package: The package definition of the plugin. This can be an OCI image,tgzarchive, npm package, or a directory path. For OCI packages ONLY, the tag or digest can be replaced by the{{inherit}}tag to inherit the version from an included configuration. Additionally, when using single-plugin OCI images, the plugin path can also be omitted.disabled: A boolean value that determines whether the plugin is enabled or disabled.integrity: The integrity hash of the package. This is required fortgzarchives and npm packages.pluginConfig: The configuration for the plugin. For backend plugins this is optional and can be used to pass configuration to the plugin. For frontend plugins this is required, see Frontend Plugin Wiring for more information on how to configure bindings and routes. This is a fragment of theapp-config.yamlfile. Anything that is added to this object will be merged into aapp-config.dynamic-plugins.yamlfile whose config can be merged with the mainapp-config.yamlconfig when launching RHDH.
Note: Duplicate plugins found across config files in the includes field will throw an error even if they are disabled. Similarly, duplicate plugin in the dynamic-plugins.yaml file will also throw the same error.
The RHDH container image is preloaded with a variety of dynamic plugin packages, the majority of which are initially disabled, as they must be configued to work. The comprehensive list of these packages is at default.packages.yaml file.
On application start, for each disabled package, the install-dynamic-plugins init container within the redhat-developer-hub pod's will log something like:
======= Skipping disabled dynamic plugin oci://registry.access.redhat.com/rhdh/backstage-community-plugin-analytics-provider-segmentTo activate this plugin, simply add a package with the same name and adjust the disabled field.
plugins:
- disabled: false
package: oci://registry.access.redhat.com/rhdh/backstage-community-plugin-analytics-provider-segment:{{inherit}}While the plugin's default configuration comes from the dynamic-plugins.default.yaml file, you still have the option to override it by incorporating a pluginConfig entry into the plugin configuration.
Note: The plugin's default configuration typically references environment variables, and it is essential to ensure that these variables are set in the Helm chart values or the Operator configuration.
RHDH supports loading default plugin configurations from an OCI container image. This feature allows you to maintain centralized plugin configurations that can be updated independently of the RHDH container image.
When the CATALOG_INDEX_IMAGE environment variable is set, the install-dynamic-plugins init container will:
- Download and extract the specified OCI image
- Look for a
dynamic-plugins.default.yamlfile within the image - Use this file as the primary source for default plugin configurations
- Replace the embedded
dynamic-plugins.default.yamlif it's present in theincludeslist - Extract catalog entities from
catalog-entities/marketplacedirectory (if present in the index image) to a configurable location
The configuration method depends on your deployment approach:
-
Helm Chart: See the Helm Chart Catalog Index Configuration for details.
-
RHDH Operator: See the Operator Catalog Index Configuration for details.
The catalog index OCI image should contain the following at the root level:
- A
dynamic-plugins.default.yamlfile with the same structure as the embedded default configuration file - Optionally, a
catalog-entities/marketplacedirectory containing extension catalog entity definitions
# Contents of dynamic-plugins.default.yaml in the OCI image
plugins:
- package: '@backstage/plugin-catalog'
disabled: true
pluginConfig:
# ... plugin configuration
- package: oci://quay.io/example/plugin:v1.0!my-plugin
disabled: trueWhen the CATALOG_INDEX_IMAGE is set and the index image contains a catalog-entities/marketplace directory, the install-dynamic-plugins.py will automatically extract these catalog entities to a configurable location.
The extraction destination is governed by the CATALOG_ENTITIES_EXTRACT_DIR environment variable:
- If
CATALOG_ENTITIES_EXTRACT_DIRis set, entities are extracted to<CATALOG_ENTITIES_EXTRACT_DIR>/catalog-entities - If not set, it defaults to
/tmp/extensions/catalog-entities
Note: If the catalog index image does not contain the catalog-entities/extensions directory, a warning will be printed but the extraction of dynamic-plugins.default.yaml will still succeed.
RHDH supports external dynamic plugins, which are plugins not included in the core RHDH distribution. These plugins can be installed or uninstalled without rebuilding the RHDH application; only a restart is required to apply the changes.
If your plugin is not already packaged as a dynamic plugin, you must package it into one of the supported formats before installation.
Dynamic plugins can be packaged in three formats:
- OCI image
tgzarchive- npm package
You can also load the dynamic plugin from a plain directory, though this is not recommended for production use (expect for the plugins that are already included in the RHDH container image). But this method can be helpful for development and testing.
More information on packaging dynamic plugins can be found in the Packaging Dynamic Plugins.
When defining the plugin packaged as an OCI image, use the oci:// prefix, followed by the image name, tag OR digest, and plugin name separated by the ! character (oci://<image-name>:<tag>!<plugin-path>).
plugins:
- disabled: false
package: oci://quay.io/example/image:v0.0.1!backstage-plugin-mypluginFor private registries, you can set the REGISTRY_AUTH_FILE environment variable to the path of the configuration file containing the authentication details for the registry. This file is typically located at ~/.config/containers/auth.json or ~/.docker/config.json.
For integrity check one may use image digests, making it possible to refer to the image digest in the dynamic plugin package:
plugins:
- disabled: false
package: oci://quay.io/example/image@sha256:28036abec4dffc714394e4ee433f16a59493db8017795049c831be41c02eb5dc!backstage-plugin-mypluginFor OCI images containing a single plugin, the plugin path (the part after !) can be omitted and will be automatically detected from the image's metadata annotations.
Explicit Path Usage:
plugins:
- disabled: false
package: oci://quay.io/example/image:v1.0.0!backstage-plugin-mypluginAuto-detected Path Usage:
plugins:
- disabled: false
package: oci://quay.io/example/image:v1.0.0When the path is omitted, the installer will inspect the OCI image manifest for the io.backstage.dynamic-packages annotation and automatically extract the plugin path. This ONLY works for images containing a single plugin, please explicitly define the plugin path for multi-plugin images.
Images MUST be packaged with the @red-hat-developer-hub/cli to ensure the proper io.backstage.dynamic-packages annotation is applied.
When working with OCI-packaged dynamic plugins, you may want to avoid specifying the version (tag or digest) in multiple places, especially when including plugins from other configuration files such as dynamic-plugins.default.yaml. Setting the tag of the OCI package to {{inherit}} allows a plugin configuration override to inherit the plugin version from an included configuration.
For example, if we have an included dynamic plugin file (dynamic-plugins.example.yaml) with v0.0.2 of our plugin which might be updated to match the current RHDH version:
# dynamic-plugins.example.yaml
plugins:
- disabled: true
package: oci://quay.io/example/image:v0.0.2!backstage-plugin-mypluginand a dynamic-plugins.yaml file with the {{inherit}} tag using configurations for an older version that are still compatible:
# dynamic-plugins.yaml
includes:
- dynamic-plugins.example.yaml
plugins:
- disabled: false
package: oci://quay.io/example/image:{{inherit}}!backstage-plugin-myplugin
pluginConfig:
exampleName: "test"The resolved version would be v0.0.2, but the overridden pluginConfig and disabled: false would still apply.
General Notes:
- An error will be thrown if you use
{{inherit}}in theincludesplugin configuration(s). - An error will be thrown if
{{inherit}}is used indynamic-plugins.yamlwhen there is no existing matching plugin configuration key in theincludesplugin configuration(s).- Plugin configuration key is a unique key based on the OCI image name + plugin path. Ex:
quay.io/example/image:!backstage-plugin-myplugin
- Plugin configuration key is a unique key based on the OCI image name + plugin path. Ex:
When using {{inherit}} for version inheritance, you can also leverage the plugin path auto-detection feature by omitting the plugin path entirely. This is particularly useful when the base configuration in included files already has an explicit path or uses auto-detection itself.
For example, we can have an example plugin that uses auto-detection that will resolve to oci://quay.io/example/image:v0.0.2!example-path
# dynamic-plugins.example.yaml
plugins:
- disabled: true
package: oci://quay.io/example/image:v0.0.2Then we can just use {{inherit}} without a path, and we will inherit both the version v0.0.2 and the plugin path example-path
# dynamic-plugins.yaml
includes:
- dynamic-plugins.example.yaml
plugins:
- disabled: false
package: oci://quay.io/example/image:{{inherit}}
pluginConfig:
exampleName: "test"This only works when exactly ONE plugin from that OCI image is defined in the included configuration files. If more are found, an error will be thrown. Additionally, an error will be thrown if no matching plugins are found.
When defining the plugin packaged as a tgz archive, use the URL of the archive and the integrity hash of the archive.
plugins:
- disabled: false
package: https://example.com/backstage-plugin-myplugin-1.0.0.tgz
integrity: sha512-9WlbgEdadJNeQxdn1973r5E4kNFvnT9GjLD627GWgrhCaxjCmxqdNW08cj+Bf47mwAtZMt1Ttyo+ZhDRDj9PoA==When defining the plugin packaged as an npm package, use the package name and version, and the integrity hash of the package.
plugins:
- disabled: false
package: @example/backstage-plugin-myplugin@1.0.0
integrity: sha512-9WlbgEdadJNeQxdn1973r5E4kNFvnT9GjLD627GWgrhCaxjCmxqdNW08cj+Bf47mwAtZMt1Ttyo+ZhDRDj9PoA==To get the integrity hash of a JavaScript package from the npm registry, use:
npm view --registry https://example.com:4873/ @backstage-community/plugin-todo-dynamic@0.2.40 dist.integrityTo configure the NPM registry URL and authentication information, you can utilize a .npmrc file. When using OpenShift or Kubernetes, you can add this file by creating a secret with the .npmrc file content and mounting it into install-dynamic-plugins init container.
apiVersion: v1
kind: Secret
metadata:
name: dynamic-plugins-npmrc
type: Opaque
stringData:
.npmrc: |
registry=<registry-url>
//<registry-url>:_authToken=<auth-token>When using RHDH Helm Chart you can just name the Secret using following pattern {{ .Release.Name }}-dynamic-plugins-npmrc, and it will be mounted automatically. (If you installed RHDH using helm install rhdh .... than the secret should be named rhdh-dynamic-plugins-npmrc)
When using the Operator ....
//TODO
The directory where dynamic plugins are located is mounted as a volume to the install-dynamic-plugins init container and the backstage-backend container. The install-dynamic-plugins init container is responsible for downloading and extracting the plugins into this directory. Depending on the deployment method, the directory is mounted as an ephemeral or persistent volume. In the latter case, the volume can be shared between several Pods, and the plugins installation script is also responsible for downloading and extracting the plugins only once, avoiding conflicts.
Important Note: If install-dynamic-plugins init container was killed with SIGKILL signal, which may happen due to the following reasons:
- pod eviction (to free up node resources)
- pod deletion (if not terminated with SIGTERM within graceful period)
- node shutdown
- container runtime issues
- exceeding resource limits (OOM for example)
Then the script will not be able to remove the lock file, so the next time the pod starts, it will be be stuck waiting for the lock to release. You will see the following message in the logs for the init install-dynamic-plugins container:
oc logs -n <namespace-name> -f backstage-<backstage-name>-<pod-suffix> -c install-dynamic-plugins
======= Waiting for lock release (file: /dynamic-plugins-root/install-dynamic-plugins.lock)...In such a case, you can delete the lock file manually from any of the Pods:
oc exec -n <namespace-name> deploy/backstage-<backstage-name> -c install-dynamic-plugins -- rm -f /dynamic-plugins-root/dynamic-plugins.lock