From 6cd68d58a8fa7566593581680301d88349876005 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Sat, 10 Jan 2026 14:44:56 +0100 Subject: [PATCH 1/2] fix(install-dynamic-plugins): do not extract catalog entities to `dynamic-plugins-root` by default Extract to temporary directory instead, unless `CATALOG_ENTITIES_EXTRACT_DIR` is set. Otherwise, this causes issues when starting RHDH, which tries to load this folder as a dynamic-plugin: ``` 2026-01-10T13:05:04.084Z backstage error Plugin 'catalog' threw an error during startup, waiting for 15 other plugins to finish before shutting down the process. Failed to instantiate service 'core.httpRouter' for 'catalog' because the factory function threw an error, Error: Failed to instantiate service 'core.auth' for 'catalog' because the factory function threw an error, error: create table "backstage_backend_public_keys__knex_migrations_lock" ("index" serial primary key, "is_locked" integer) - relation "backstage_backend_public_keys__knex_migrations_lock" already exists type="initialization" stack="Error: Failed to instantiate service 'core.httpRouter' for 'catalog' because the factory function threw an error, Error: Failed to instantiate service 'core.auth' for 'catalog' because the factory function threw an error, error: create table \"backstage_backend_public_keys__knex_migrations_lock\" (\"index\" serial primary key, \"is_locked\" integer) - relation \" failed to load dynamic plugin manifest from '/opt/app-root/src/dynamic-plugins-root/extensions' Error: ENOENT: no such file or directory, open '/opt/app-root/src/dynamic-plugins-root/extensions/package.json' at async open (node:internal/fs/promises:641:25) at async Object.readFile (node:internal/fs/promises:1245:14) at async PluginScanner.scanDir (/opt/app-root/src/node_modules/@backstage/backend-dynamic-feature-service/dist/scanner/plugin-scanner.cjs.js:170:21) at async PluginScanner.scanRoot (/opt/app-root/src/node_modules/@backstage/backend-dynamic-feature-service/dist/scanner/plugin-scanner.cjs.js:146:25) at async Object.addDynamicPluginsSchemas (/opt/app-root/src/node_modules/@backstage/backend-dynamic-feature-service/dist/schemas/schemas.cjs.js:63:32) at async Object.init [as func] (/opt/app-root/src/node_modules/@backstage/backend-dynamic-feature-service/dist/schemas/frontend.cjs.js:23:14) at async /opt/app-root/src/node_modules/@backstage/backend-app-api/dist/wiring/BackendInitializer.cjs.js:284:19 at async processNode (/opt/app-root/src/node_modules/@backstage/backend-app-api/dist/lib/DependencyGraph.cjs.js:181:22) at async Promise.all (index 0) at async processMoreNodes (/opt/app-root/src/node_modules/@backstage/backend-app-api/dist/lib/DependencyGraph.cjs.js:176:7) at async DependencyGraph.parallelTopologicalTraversal (/opt/app-root/src/node_modules/@backstage/backend-app-api/dist/lib/DependencyGraph.cjs.js:195:5) at async /opt/app-root/src/node_modules/@backstage/backend-app-api/dist/wiring/BackendInitializer.cjs.js:271:13 at async Promise.allSettled (index 1) at async #doStart (/opt/app-root/src/node_modules/@backstage/backend-app-api/dist/wiring/BackendInitializer.cjs.js:242:21) at async BackendInitializer.start (/opt/app-root/src/node_modules/@backstage/backend-app-api/dist/wiring/BackendInitializer.cjs.js:160:5) at async BackstageBackend.start (/opt/app-root/src/node_modules/@backstage/backend-app-api/dist/wiring/BackstageBackend.cjs.js:19:5) { errno: -2, code: 'ENOENT', syscall: 'open', path: '/opt/app-root/src/dynamic-plugins-root/extensions/package.json' } ``` --- docker/install-dynamic-plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/install-dynamic-plugins.py b/docker/install-dynamic-plugins.py index 17810adc37..f2d9be5d67 100755 --- a/docker/install-dynamic-plugins.py +++ b/docker/install-dynamic-plugins.py @@ -1036,8 +1036,8 @@ def main(): catalog_index_image = os.environ.get("CATALOG_INDEX_IMAGE", "") catalog_index_default_file = None if catalog_index_image: - # default to /extensions if the env var is not set, to make it easier to run locally. - catalog_entities_parent_dir = os.environ.get("CATALOG_ENTITIES_EXTRACT_DIR", os.path.join(dynamic_plugins_root, "extensions")) + # default to a temporary directory if the env var is not set + catalog_entities_parent_dir = os.environ.get("CATALOG_ENTITIES_EXTRACT_DIR", os.path.join(tempfile.gettempdir(), "extensions")) catalog_index_default_file = extract_catalog_index(catalog_index_image, dynamic_plugins_root, catalog_entities_parent_dir) skip_integrity_check = os.environ.get("SKIP_INTEGRITY_CHECK", "").lower() == "true" From 3375186bc3f9fd1a25e16a0f24241b40805f0806 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Sat, 10 Jan 2026 14:56:10 +0100 Subject: [PATCH 2/2] docs: Update docs accordingly --- docs/dynamic-plugins/installing-plugins.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/dynamic-plugins/installing-plugins.md b/docs/dynamic-plugins/installing-plugins.md index d8e8645bc0..5eb36f2259 100644 --- a/docs/dynamic-plugins/installing-plugins.md +++ b/docs/dynamic-plugins/installing-plugins.md @@ -81,9 +81,9 @@ When the `CATALOG_INDEX_IMAGE` is set and the index image contains a `catalog-en The extraction destination is governed by the `CATALOG_ENTITIES_EXTRACT_DIR` environment variable: - If `CATALOG_ENTITIES_EXTRACT_DIR` is set, entities are extracted to `/catalog-entities` -- If not set, it defaults to `/marketplace/catalog-entities` +- If not set, it defaults to `/tmp/extensions/catalog-entities` -**Note:** If the catalog index image does not contain the `catalog-entities/marketplace` directory, a warning will be printed but the extraction of `dynamic-plugins.default.yaml` will still succeed. +**Note:** If the catalog index image does not contain the `catalog-entities/extensions` (or `catalog-entities/marketplace`) directory, a warning will be printed but the extraction of `dynamic-plugins.default.yaml` will still succeed. ## Installing External Dynamic Plugins