Configuration Files
Overlay Documentation
This page covers writing tests within rhdh-plugin-export-overlays. For using @red-hat-developer-hub/e2e-test-utils in external projects, see the Guide.
This page explains the YAML configuration files used in overlay E2E tests.
If you're looking for the general file formats and examples, see Configuration Files (Guide). This page focuses on overlay-specific behavior such as metadata auto-generation and OCI URL replacement.
All Configuration Files Are Optional
Key Concept
All configuration files in tests/config/ are optional. The package provides sensible defaults. Only create a configuration file when you need to override or extend the defaults (i.e., when the default config does not already cover your use case).
If your plugin works with the package defaults and metadata-based configuration, you may not need any configuration files at all.
Configuration File Location
Configuration files are placed in tests/config/:
tests/config/
├── app-config-rhdh.yaml # RHDH application configuration (optional)
├── rhdh-secrets.yaml # Kubernetes secrets (optional)
├── dynamic-plugins.yaml # Dynamic plugins (optional - usually not needed)
├── value_file.yaml # Helm values override (optional, Helm only)
└── subscription.yaml # Operator subscription (optional, Operator only)2
3
4
5
6
All of these files are optional. Only create them when you need to override or extend defaults.
app-config-rhdh.yaml (Optional)
The main RHDH configuration file. This file is merged with default configurations from @red-hat-developer-hub/e2e-test-utils.
Only create this file when you need to:
- Override a default value in the RHDH app config
- Add config keys that are not provided by the defaults
Purpose
- Set plugin-specific configuration values
- Configure backend settings
- Customize the RHDH instance title
- Allow external hosts for reading
Structure
# RHDH app config file
# This file merges with the default values from @red-hat-developer-hub/e2e-test-utils
app:
title: RHDH <Plugin Name> Test Instance
backend:
reading:
allow:
- host: ${EXTERNAL_HOST}
# Plugin-specific configuration
<pluginName>:
setting1: value1
setting2: value22
3
4
5
6
7
8
9
10
11
12
13
14
15
Referencing Secrets
Use ${VAR_NAME} syntax to reference values from rhdh-secrets.yaml:
backend:
reading:
allow:
- host: ${TECH_RADAR_DATA_URL}
techRadar:
url: "http://${TECH_RADAR_DATA_URL}/tech-radar"2
3
4
5
6
These reference the Kubernetes Secret created by rhdh-secrets.yaml. The secret must define TECH_RADAR_DATA_URL for this to work.
Real Example: Tech Radar
# rhdh app config file
# this file is used to merge with the default values of the rhdh app config
app:
title: RHDH Tech Radar Test Instance
backend:
reading:
allow:
- host: ${TECH_RADAR_DATA_URL}
techRadar:
url: "http://${TECH_RADAR_DATA_URL}/tech-radar"2
3
4
5
6
7
8
9
10
11
rhdh-secrets.yaml (Optional)
A Kubernetes Secret manifest that bridges environment variables to the RHDH deployment.
Only create this file when you need to pass environment variables into RHDH configuration files.
Environment Variable Substitution
Important
Environment variable substitution is performed ONLY on rhdh-secrets.yaml.
When this file is processed, any $VAR_NAME references are replaced with actual values from the environment. Other config files (app-config-rhdh.yaml, dynamic-plugins.yaml) do not get direct substitution - they reference the secrets created by this file.
When Is This File Needed?
| Where you need the variable | rhdh-secrets.yaml required? |
|---|---|
Test code (*.spec.ts) | No - use process.env directly |
RHDH configs (app-config-rhdh.yaml, etc.) | Yes |
How It Works
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ Vault / .env │────▶│ rhdh-secrets.yaml│────▶│ app-config-rhdh.yaml│
│ MY_SECRET=value │ │ MY_SECRET: $VAR │ │ ${MY_SECRET} │
│ │ │ (substituted) │ │ (references secret) │
└─────────────────┘ └──────────────────┘ └─────────────────────┘2
3
4
5
- Environment variable exists (from Vault in CI, or
.envlocally) rhdh-secrets.yamlreferences it with$VAR_NAME- substituted with actual value- RHDH configs reference the secret with
${VAR_NAME}
Structure
apiVersion: v1
kind: Secret
metadata:
name: rhdh-secrets
type: Opaque
stringData:
# $VAR_NAME is replaced with the actual value from environment
TECH_RADAR_DATA_URL: $TECH_RADAR_DATA_URL
API_KEY: $VAULT_MY_API_KEY2
3
4
5
6
7
8
9
Real Example: Tech Radar
apiVersion: v1
kind: Secret
metadata:
name: rhdh-secrets
type: Opaque
stringData:
TECH_RADAR_DATA_URL: $TECH_RADAR_DATA_URL2
3
4
5
6
7
Then in app-config-rhdh.yaml, you can use ${TECH_RADAR_DATA_URL}:
techRadar:
url: "http://${TECH_RADAR_DATA_URL}/tech-radar"2
dynamic-plugins.yaml (Optional)
Configuration for dynamic plugins. This file is optional and in most cases should not be provided.
Best Practice
Do NOT create dynamic-plugins.yaml unless you have a specific reason. When the file doesn't exist, all plugins in the workspace are automatically enabled with their default configurations from metadata. Only create this file if you need to override specific plugin settings.
How Plugin Configuration is Generated
The package uses plugin metadata files (in metadata/*.yaml at the workspace level) to automatically configure plugins:
workspaces/<plugin-name>/
├── metadata/ # Plugin metadata (Package CRD format)
│ ├── plugin-frontend.yaml # Frontend plugin metadata
│ └── plugin-backend.yaml # Backend plugin metadata
├── e2e-tests/ # Your test project
│ └── tests/config/
│ └── dynamic-plugins.yaml # Optional - usually not needed
└── plugins/ # Plugin source code2
3
4
5
6
7
8
The metadata files contain spec.appConfigExamples with the plugin's default configuration. These are automatically read and used during deployment.
When File Doesn't Exist (Recommended)
When tests/config/dynamic-plugins.yaml is missing, the package auto-generates a complete configuration:
- Scans for plugin metadata in
../metadata/(relative to e2e-tests) - Reads each
*.yamlfile as Package CRD format - Extracts
spec.dynamicArtifactandspec.appConfigExamples[0].content - Generates plugin entries with:
package: The dynamicArtifact pathdisabled: false(enabled by default)pluginConfig: From appConfigExamples
- Merges with package defaults and auth config
Result: All plugins from metadata are enabled with their default configurations. This is the recommended approach for most plugins.
When File Exists
When tests/config/dynamic-plugins.yaml exists:
- Package defaults are loaded
- Auth-specific plugins (keycloak/guest) are merged
- Your custom config is merged on top (overrides earlier values)
- Plugin metadata is injected only for plugins listed in your file
Important: If you provide this file, you take control of which plugins are enabled. Plugins not listed in your file won't get metadata injected automatically.
When to Create This File
Only create this file when:
- You need to disable specific plugins
- You need to override default plugin settings from metadata
- The plugin has no metadata files
- You need different configuration than the metadata defaults
Structure (If Needed)
plugins:
- package: "@company/backstage-plugin-example"
disabled: false
pluginConfig:
example:
setting: customValue # Overrides metadata default2
3
4
5
6
See Also
For detailed information about plugin metadata handling, see the package documentation:
value_file.yaml (Optional, Helm Only)
Override Helm chart values for RHDH deployment.
Only create this file when you need to:
- Override default Helm values
- Configure cluster-specific settings
- Customize resource limits or replicas
Structure
# Override Helm values
upstream:
backstage:
resources:
limits:
memory: 4Gi2
3
4
5
6
This file is merged with the package's default Helm values.
subscription.yaml (Optional, Operator Only)
Override Operator subscription settings.
Only create this file when you need to:
- Use a specific operator channel
- Configure operator-specific settings
- Override default subscription configuration
Structure
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: rhdh
spec:
channel: fast2
3
4
5
6
This file is merged with the package's default subscription configuration.
Plugin Package References
When providing plugin packages (in dynamic-plugins.yaml if you create one), you can use either:
- Local paths:
./dynamic-plugins/dist/my-plugin - OCI references:
oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/my-plugin:1.0.0
Both formats work for local development.
PR Builds and OCI Images
When GIT_PR_NUMBER is set (either in CI or locally), the package automatically replaces plugin paths with OCI image references built from that PR.
How It Works
- The
/publishcommand (as a PR comment) triggers a workflow that builds OCI images from the PR - Images are pushed to
ghcr.io/redhat-developer/rhdh-plugin-export-overlays - When tests run with
GIT_PR_NUMBERset (CI or local), the package:- Reads
source.jsonandplugins-list.yaml - Fetches plugin versions from the source repo's
package.jsonfiles - Replaces plugin paths with OCI URLs pointing to the PR-built images
- Reads
OCI URL Format
oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/{plugin-name}:pr_{PR_NUMBER}__{version}Automatic Replacement
In CI (PR executions): The replacement happens automatically. It doesn't matter what format you use in your configuration - it will be replaced with the OCI reference for that PR's built images.
Locally with GIT_PR_NUMBER: If you set GIT_PR_NUMBER locally, it will fetch and use the published OCI images from that PR. This is useful for testing PR builds locally.
# Test locally using PR 1845's published OCI images
export GIT_PR_NUMBER=1845
yarn test2
3
For a full local workflow, see Local OCI Testing.
What You Don't Need to Do
The OCI URL replacement is automatic. You don't need to:
- Create or modify any configuration files for PR builds
- Specify OCI URLs manually
- Handle different configurations for local vs CI
The package handles this transparently based on the GIT_PR_NUMBER environment variable.
Required Files for OCI URL Generation
When GIT_PR_NUMBER is set, these files must exist in the workspace root:
| File | Purpose |
|---|---|
source.json | Contains repo (GitHub URL) and repo-ref (commit SHA) |
plugins-list.yaml | Lists plugin paths (e.g., plugins/tech-radar:) |
In CI, these are generated automatically. For local testing with GIT_PR_NUMBER, you may need to create them or copy them from a CI run.
Strict Validation
OCI URL generation is strict - deployment will fail if required files are missing or version fetching fails. This ensures builds don't silently fall back to local paths.
Nightly Builds and OCI Resolution
In nightly mode (E2E_NIGHTLY_MODE=true), plugin packages are resolved to released OCI refs from workspace metadata (spec.dynamicArtifact) instead of PR-built images. Metadata config injection is skipped — only package resolution happens.
How Each Plugin Type Is Resolved
| Plugin Type | Example | Resolution |
|---|---|---|
| Workspace plugin with metadata | ./dynamic-plugins/dist/plugin-tech-radar | Metadata OCI ref (e.g., oci://ghcr.io/.../plugin-tech-radar:bs_1.45.3__1.13.0) |
| Cross-workspace plugin (no metadata) | ./dynamic-plugins/dist/backstage-plugin-kubernetes-backend-dynamic | Kept as-is |
| npm package | @red-hat-developer-hub/backstage-plugin-global-header-test@0.0.2 | Kept as-is (with integrity hash preserved) |
| Existing OCI reference | oci://quay.io/.../plugin-name:tag | Kept as-is |
Multiple Registries
Plugin OCI refs use the actual registry from each plugin's metadata — plugins may come from ghcr.io, quay.io/rhdh, registry.access.redhat.com/rhdh, or other registries. The system does not assume a single registry.
See Plugin Metadata - Mode Comparison for the full comparison of PR check, nightly, and local dev modes.
Configuration Merging
@red-hat-developer-hub/e2e-test-utils merges your configuration with defaults in this order:
- Base defaults from
@red-hat-developer-hub/e2e-test-utils/config/common/ - Auth-specific from
@red-hat-developer-hub/e2e-test-utils/config/auth/{guest,keycloak}/ - Deployment method from
@red-hat-developer-hub/e2e-test-utils/config/{helm,operator}/ - Your custom config from
tests/config/
Later files override earlier ones using deep merge.
Merge Strategies
Arrays use the "replace" strategy by default:
# Base config
backend:
reading:
allow:
- host: localhost
# Your config (replaces the array)
backend:
reading:
allow:
- host: ${EXTERNAL_HOST}
# Result
backend:
reading:
allow:
- host: ${EXTERNAL_HOST} # localhost is replaced2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Setting Environment Variables
Environment variables can be set in multiple ways:
1. In Test Code
Set variables in beforeAll:
test.beforeAll(async ({ rhdh }) => {
// Deploy external service first
await $`bash ${setupScript} ${project}`;
// Get the service URL and set as env var
process.env.TECH_RADAR_DATA_URL = await getServiceUrl();
// Now deploy RHDH (will use the env var)
await rhdh.deploy();
});2
3
4
5
6
7
8
9
10
2. In .env File
For local development:
# .env
TECH_RADAR_DATA_URL=my-service.example.com2
3. In Vault (CI)
Add secrets to the Vault with VAULT_ prefix. They are automatically exported during OpenShift CI execution:
VAULT_TECH_RADAR_DATA_URL: my-service.apps.cluster.example.comThen reference in your config:
techRadar:
url: "http://${VAULT_TECH_RADAR_DATA_URL}/tech-radar"2
Secret Naming
All secrets in Vault must start with VAULT_ prefix for automatic export.
Common Configuration Patterns
Allow External Hosts
backend:
reading:
allow:
- host: ${EXTERNAL_HOST}2
3
4
Set Plugin URL
<pluginName>:
url: "http://${SERVICE_URL}/<endpoint>"2
Custom App Title
app:
title: RHDH <Plugin> Test Instance2
Real-World Workspace Patterns
These examples show how different workspaces use dynamic-plugins.yaml and how the package resolves their plugins.
Auto-Generated (No dynamic-plugins.yaml)
Workspaces: tech-radar, quickstart, acr
When no dynamic-plugins.yaml exists, the package auto-generates entries from all metadata/*.yaml files:
# Auto-generated at deploy time:
plugins:
- package: oci://ghcr.io/.../backstage-community-plugin-tech-radar:bs_1.45.3__1.13.0
disabled: false
# pluginConfig injected from metadata appConfigExamples (PR/local mode)2
3
4
5
No configuration files needed — metadata provides everything.
Cross-Workspace Plugins
Workspace: argocd
When your workspace needs plugins from another workspace (e.g., Kubernetes backend for ArgoCD):
plugins:
# Workspace plugin — resolved to metadata OCI ref (or PR tag)
- package: oci://ghcr.io/.../backstage-community-plugin-argocd:bs_1.45.3__2.4.3!backstage-community-plugin-argocd
pluginConfig:
dynamicPlugins:
frontend: { ... }
# Cross-workspace plugin — no metadata match, kept as-is
- package: ./dynamic-plugins/dist/backstage-plugin-kubernetes-backend-dynamic2
3
4
5
6
7
8
9
Cross-workspace plugins have no metadata in the current workspace, so they pass through unchanged in all modes.
OCI Aliases
Workspace: redhat-resource-optimization
Some plugins share a single OCI image with multiple plugins distinguished by aliases (the !alias suffix):
plugins:
- package: oci://quay.io/redhat-resource-optimization/dynamic-plugins:1.3.2!red-hat-developer-hub-plugin-redhat-resource-optimization2
The alias after ! tells RHDH which plugin to extract from the shared image.
Disabled Wrapper Plugins
Workspace: scorecard
When your workspace uses an OCI image for a plugin that also has a local wrapper enabled by default:
plugins:
# Workspace plugin — resolved to metadata ref
- package: oci://ghcr.io/.../red-hat-developer-hub-backstage-plugin-scorecard:tag!alias
# Cross-workspace OCI — kept as-is
- package: oci://ghcr.io/.../red-hat-developer-hub-backstage-plugin-dynamic-home-page:tag!alias
# Disable the local wrapper to avoid conflicts
- package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-dynamic-home-page
disabled: true2
3
4
5
6
7
8
9
10
Different OCI Registries
Plugins can come from different registries. The package preserves the original registry from each plugin's metadata:
# ghcr.io (community plugins)
- package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-community-plugin-tech-radar:bs_1.45.3__1.13.0
# quay.io (Red Hat plugins)
- package: oci://quay.io/rhdh/red-hat-developer-hub-backstage-plugin-scaffolder-relation-processor@sha256:abc123
# registry.access.redhat.com (certified plugins)
- package: oci://registry.access.redhat.com/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator@sha256:f40d39fb2
3
4
5
6
7
8
npm Packages
Workspace: global-header
For plugins published to npm instead of OCI:
plugins:
- package: "@red-hat-developer-hub/backstage-plugin-global-header-test@0.0.2"
integrity: "sha512-ABC123..."2
3
npm packages with integrity hashes pass through unchanged in all modes — no metadata resolution.
All Local Paths
Workspace: topology
Some workspaces use only local paths (no OCI references in their config):
plugins:
- package: ./dynamic-plugins/dist/backstage-community-plugin-topology
- package: ./dynamic-plugins/dist/backstage-plugin-kubernetes-backend-dynamic2
3
In PR mode, pluginConfig from metadata is injected. In nightly mode, local paths are resolved to metadata OCI refs if metadata exists. Local paths with no metadata match stay unchanged.
Related Pages
- Directory Layout - Where config files go
- Spec Files - Using config in tests
- Environment Variables - All supported variables
- Plugin Metadata - How metadata resolution works