home / skills / kadel / claude-plugins / rhdh-context

This skill provides essential context on Red Hat Developer Hub, its relation to Backstage, and how to develop and deploy RHDH plugins.

npx playbooks add skill kadel/claude-plugins --skill rhdh-context

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
10.9 KB
---
name: RHDH Context
description: This skill should be used whenever the user mentions "RHDH", "Red Hat Developer Hub", or "Developer Hub" in any context — whether asking questions, developing plugins, debugging, deploying, or discussing architecture. It provides essential background context about what RHDH is, how it differs from vanilla Backstage, and key nuances that affect all RHDH-related work.
version: 0.2.0
---

## Purpose

Provide context on what Red Hat Developer Hub (RHDH) is, how it relates to Backstage, and the key nuances that affect plugin development and deployment.

## What is RHDH

Red Hat Developer Hub (RHDH) is Red Hat's enterprise distribution of [Backstage](https://backstage.io), the open-source developer portal framework originally created by Spotify. RHDH packages Backstage with enterprise support, curated plugins, and an operator-based deployment model for OpenShift and Kubernetes.

RHDH is **not a fork** of Backstage. It tracks upstream Backstage releases and layers enterprise features on top. The core Backstage APIs and plugin system remain the same, but RHDH adds its own deployment, configuration, and plugin distribution mechanisms.

Key additions RHDH provides over vanilla Backstage:
- **Dynamic plugin system** — install/remove plugins without rebuilding the application
- **Operator-based deployment** — `Backstage` Custom Resource managed by the rhdh-operator
- **Helm chart deployment** — alternative to operator for simpler setups
- **Pre-packaged plugins** — curated set of plugins included in the container image (disabled by default, enabled via configuration)
- **YAML-based frontend wiring** — configure routes, mount points, menus without code changes
- **Enterprise auth defaults** — authentication required by default (no anonymous access)
- **Telemetry** — anonymous usage analytics via `analytics-provider-segment` (enabled by default)
- **Branding and theming** — configurable logos, colors, sidebar, and i18n via app-config

## RHDH vs Vanilla Backstage

### What stays the same

- Backstage plugin APIs (`@backstage/core-plugin-api`, `@backstage/backend-plugin-api`)
- Software catalog model and entity kinds
- TechDocs, scaffolder templates, search
- Authentication providers and permission framework
- React-based frontend architecture

### What RHDH changes

| Area | Vanilla Backstage | RHDH |
|------|-------------------|------|
| **Plugin installation** | Static: plugins compiled into the app at build time | Dynamic: plugins loaded at runtime without rebuilding |
| **Deployment** | DIY: build Docker image, deploy however you want | Operator-managed (`Backstage` CR) or Helm chart on OpenShift/K8s |
| **Plugin distribution** | npm packages as build-time dependencies | OCI images (recommended), tgz archives, npm packages, or local directories |
| **Frontend wiring** | Code in `App.tsx`, `EntityPage.tsx` | YAML-based wiring via `dynamic-plugins.yaml` and `app-config` |
| **Configuration** | `app-config.yaml` baked into image | ConfigMaps/Secrets mounted at runtime, assembled from multiple sources |
| **Versioning** | Pick any Backstage version | Fixed Backstage version per RHDH release |
| **Auth** | Guest access allowed in development | Auth required by default, guest must be explicitly enabled |
| **Support** | Community | Red Hat subscription |

## Dynamic Plugins

The dynamic plugin system is the most significant difference between RHDH and vanilla Backstage.

### How dynamic plugins work

RHDH decouples plugins from the core application. A **backend plugin manager** service scans a configured root directory (`dynamicPlugins.rootDirectory`) for dynamic plugin packages and loads them at startup.

- **Backend plugins** — loaded as Node.js modules from the filesystem at startup
- **Frontend plugins** — loaded as JavaScript bundles via [Scalprum](https://github.com/janus-idp/scalprum) (runtime module federation)

Plugins are installed and removed without rebuilding the application — only a pod restart is required.

### Plugin export process

Standard Backstage plugins are converted to dynamic plugins using the RHDH CLI:

```bash
npx @red-hat-developer-hub/cli@latest plugin export
```

This creates a **derived package** in `dist-dynamic/` with a `-dynamic` suffix. The export process:
1. Builds the plugin
2. Rewrites `@backstage/*` dependencies as peer dependencies (shared with the host)
3. Bundles all non-Backstage dependencies into the package
4. Auto-embeds packages with `-node` or `-common` suffixes
5. Generates Scalprum configuration for frontend plugins

Derived dynamic plugin packages should **not** be pushed to the public npm registry.

### Plugin package sources

Plugins in `dynamic-plugins.yaml` can be loaded from four sources:

- **OCI images** (recommended): `oci://quay.io/org/plugin:v1.0.0!plugin-name-dynamic`
- **tgz archives**: URL with required `integrity` hash (SHA-512)
- **npm packages**: package name with version and required `integrity` hash
- **Local directories**: filesystem path (development only)

OCI images can use digests instead of tags for integrity verification. Private registries require the `REGISTRY_AUTH_FILE` environment variable.

### Pre-packaged plugins

The RHDH container image ships with a curated set of plugins listed in `dynamic-plugins.default.yaml`. Most are **disabled by default** and must be explicitly enabled in the user's `dynamic-plugins.yaml` with `disabled: false`. Default plugin configurations can also be loaded from an external OCI image via the `CATALOG_INDEX_IMAGE` environment variable.

### Frontend wiring via YAML

Frontend plugins are wired through YAML configuration rather than code. Consult `references/frontend-wiring.md` for the full configuration reference. Key wiring capabilities:

- **Dynamic routes** — register new pages with sidebar menu items
- **Mount points** — inject components into existing pages (entity page cards, tabs, context menus, application headers, providers)
- **Menu items** — control sidebar ordering, nesting (up to 3 levels), visibility, and icons
- **App icons** — register custom icons into RHDH's icon catalog
- **Entity tabs** — add or modify catalog entity page tabs
- **Route bindings** — connect plugin cross-references
- **API factories** — register or override Backstage utility APIs
- **Scaffolder field extensions** — add custom form fields to software templates
- **TechDocs addons** — extend TechDocs pages
- **Themes** — provide or override light/dark themes
- **Sign-in page** — custom authentication UI
- **Translation resources** — i18n overrides

Mount points support conditional rendering with `if` conditions: `isKind`, `isType`, `hasAnnotation`, and custom functions with `allOf`/`anyOf`/`oneOf` logical operators.

## Deployment Model

RHDH supports two deployment methods on OpenShift/Kubernetes:

### Operator deployment
The **rhdh-operator** watches `Backstage` Custom Resources and reconciles the full deployment. Key components:
- **Backstage CR** — declares the desired RHDH instance
- **ConfigMaps** — hold `app-config` YAML and `dynamic-plugins.yaml`
- **Secrets** — store credentials (database auth, plugin secrets, auth provider secrets)
- **PostgreSQL** — required database, either operator-managed (local) or external
- **Route/Ingress** — exposes the RHDH UI

### Helm chart deployment
An alternative to the operator. See the [rhdh-chart repository](https://github.com/redhat-developer/rhdh-chart) for values and configuration.

### Local development
Run RHDH locally with `yarn start` for development. Corporate proxy environments are supported via `HTTP_PROXY`/`HTTPS_PROXY` environment variables with the `GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE=''` setting.

## Version Compatibility

RHDH pins a specific Backstage version per release. Plugins **must** be built against a compatible Backstage version or they may fail to load due to API mismatches.

| RHDH Version | Backstage Version | Status |
|--------------|-------------------|--------|
| 1.8 / next | 1.42.5 | Latest |
| 1.7 | 1.39.1 | Current |
| 1.6 | 1.36.1 | Supported |
| 1.5 | 1.35.1 | Supported |

Always confirm the target RHDH version before starting plugin development.

## Key Nuances

### Backend plugins must use the new backend system

RHDH only supports backend plugins written with the **new backend system** (`createBackendPlugin`, `createBackendModule` from `@backstage/backend-plugin-api`). The legacy backend system (`createRouter` with `PluginEnvironment`) is not compatible with dynamic plugin loading.

### Default export is required

Dynamic backend plugins must have a **default export** from their `src/index.ts`:

```typescript
export { default } from './plugin';
```

### Shared vs bundled dependencies

`@backstage/*` packages become peer dependencies shared with the RHDH host. Non-Backstage dependencies are bundled. Mismatched `@backstage/*` versions between plugin and host cause runtime failures. Control this with `--shared-package` (use `!` prefix to bundle a `@backstage` package) and `--embed-package` flags.

### MUI v5 styling issues

Frontend dynamic plugins using Material-UI v5 lack default CSS declarations that static plugins receive. Configure `ClassNameGenerator` to prefix component names with `v5-` before exporting. MUI v5 Grid components do not inherit theme spacing — apply `spacing={2}` manually. Using MUI v4 (`@material-ui/core`) avoids these issues.

### Core service overrides

RHDH allows overriding 15 core backend services (auth, caching, database, discovery, HTTP routing, lifecycle, logging, permissions, health, scheduling, user info, URL reading) via dynamic plugins. Set the corresponding environment variable (e.g., `ENABLE_CORE_ROOTHTTPROUTER_OVERRIDE=true`) to disable the default service and provide a custom `BackendFeature` implementation.

### Auth is required by default

RHDH enables authentication by default with 15+ supported providers. RHDH includes an opinionated auth module that can be overridden with `ENABLE_AUTH_PROVIDER_MODULE_OVERRIDE=true`. For development, enable guest access explicitly:

```yaml
auth:
  environment: development
  providers:
    guest:
      dangerouslyAllowOutsideDevelopment: true
```

### No hot reload for dynamic plugins

Dynamic plugins are loaded once at RHDH startup. Changing a plugin requires restarting the RHDH pod.

### App-config is split across ConfigMaps

RHDH assembles app-config from multiple ConfigMaps referenced in the Backstage CR, allowing different teams to own different configuration segments.

### Lock file issue

If the `install-dynamic-plugins` init container terminates unexpectedly, a lock file may persist and prevent subsequent startups. Remove it manually if pod logs show "Waiting for lock release."

### Logging

RHDH uses the winston logging library. Debug-level logs are disabled by default — enable with `LOG_LEVEL=debug` environment variable.

### Telemetry

Anonymous usage data (page visits, button clicks) is collected by default via the `analytics-provider-segment` plugin. IP addresses are anonymized and user identifiers are hashed.

Overview

This skill provides concise, actionable context for anything mentioning RHDH, Red Hat Developer Hub, or Developer Hub. It explains how RHDH relates to vanilla Backstage, highlights the runtime differences that affect plugin development and deployment, and calls out key operational and compatibility nuances. Use it to avoid common pitfalls and speed up integration with RHDH environments.

How this skill works

The skill summarizes RHDH as an enterprise distribution of Backstage that tracks upstream releases and layers enterprise features like dynamic plugins, operator-based deployment, and YAML-based frontend wiring. It explains the dynamic plugin model: backend modules loaded from the filesystem and frontend bundles loaded at runtime via Scalprum, plus supported package sources (OCI, tgz, npm, local). It also covers deployment models (operator, Helm, local), version pinning, and runtime config assembly from ConfigMaps/Secrets.

When to use it

  • When developing or converting Backstage plugins to work on RHDH (dynamic vs static differences).
  • When debugging plugin loading failures, compatibility errors, or missing exports.
  • When planning deployment on OpenShift/Kubernetes with the rhdh-operator or Helm chart.
  • When configuring runtime wiring, menus, routes, or mount points via YAML.
  • When verifying version compatibility between plugin and RHDH release.

Best practices

  • Build plugins against the exact Backstage version pinned by the target RHDH release.
  • Use the RHDH CLI export to create derived dynamic packages and avoid publishing them publicly.
  • Prefer OCI images for dynamic plugin distribution and use digests for integrity when possible.
  • Ensure backend plugins use the new backend system (createBackendPlugin) and have a default export.
  • Keep app-config fragments in ConfigMaps and manage secrets separately; restart pods to pick up dynamic plugin changes.

Example use cases

  • Converting a static frontend plugin to a dynamic plugin using the RHDH CLI and wiring it via dynamic-plugins.yaml.
  • Troubleshooting a runtime error caused by mismatched @backstage/* versions between plugin and host.
  • Deploying RHDH with the rhdh-operator and providing app-config via multiple ConfigMaps for team-owned configs.
  • Packaging a plugin as an OCI image, referencing it in dynamic-plugins.yaml, and enabling it without rebuilding the host image.
  • Overriding a core backend service with a custom BackendFeature and enabling it through environment variables.

FAQ

Do I need to rebuild RHDH to add or remove a plugin?

No. Dynamic plugins can be installed/removed without rebuilding; a pod restart is required to load changes.

What causes plugins to fail at runtime?

Most failures stem from Backstage version mismatches, missing default exports for backend plugins, or incorrect peer dependency handling of @backstage/* packages.

Can I load dynamic plugins from private registries?

Yes. OCI images from private registries work if REGISTRY_AUTH_FILE is provided; tgz and npm sources require integrity hashes.