cli: enable embedded-postgres via config instead of env var

Rather than requiring the `EXPERIMENTAL_DEV_DB` environment variable,
the embedded postgres server is now started automatically when
`backend.database.client` is set to `embedded-postgres` in the app
config. The CLI reads the config before spawning the backend and
injects the actual pg connection details via env override.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-04-01 13:03:11 +02:00
parent f14df56222
commit 538d0a1488
4 changed files with 42 additions and 5 deletions
+3 -2
View File
@@ -1,5 +1,6 @@
---
'@backstage/cli': patch
'@backstage/cli-module-build': patch
'@backstage/backend-defaults': patch
---
The `package start` command now supports an experimental `EXPERIMENTAL_DEV_DB` env flag that can be set to enable the use of `embedded-postgres` as the database for local development, rather than SQLite. For this to work the desired version of the `embedded-postgres` package must be installed in your project, typically as a `devDependency`.
Added experimental support for using `embedded-postgres` as the database for local development. Set `backend.database.client` to `embedded-postgres` in your app config to enable this. The `embedded-postgres` package must be installed as an explicit dependency in your project.
+1 -1
View File
@@ -585,7 +585,7 @@ export interface Config {
/** Database connection configuration, select base database type using the `client` field */
database: {
/** Default database client to use */
client: 'better-sqlite3' | 'sqlite3' | 'pg';
client: 'better-sqlite3' | 'sqlite3' | 'pg' | 'embedded-postgres';
/**
* Base database connection string, or object with individual connection properties
* @visibility secret
@@ -23,6 +23,7 @@ import { runBackend } from '../../../lib/runner';
interface StartBackendOptions {
targetDir: string;
checksEnabled: boolean;
configPaths?: string[];
inspectEnabled?: boolean | string;
inspectBrkEnabled?: boolean | string;
linkedWorkspace?: string;
@@ -33,6 +34,7 @@ export async function startBackend(options: StartBackendOptions) {
const waitForExit = await runBackend({
targetDir: options.targetDir,
entry: 'src/index',
configPaths: options.configPaths,
inspectEnabled: options.inspectEnabled,
inspectBrkEnabled: options.inspectBrkEnabled,
linkedWorkspace: options.linkedWorkspace,
@@ -56,6 +58,7 @@ export async function startBackendPlugin(options: StartBackendOptions) {
const waitForExit = await runBackend({
targetDir: options.targetDir,
entry: 'dev/index',
configPaths: options.configPaths,
inspectEnabled: options.inspectEnabled,
inspectBrkEnabled: options.inspectBrkEnabled,
require: options.require,
@@ -20,8 +20,13 @@ import { ctrlc } from 'ctrlc-windows';
import { IpcServer, ServerDataStore } from '../ipc';
import debounce from 'lodash/debounce';
import { fileURLToPath } from 'node:url';
import { isAbsolute as isAbsolutePath } from 'node:path';
import {
isAbsolute as isAbsolutePath,
resolve as resolvePath,
} from 'node:path';
import { targetPaths } from '@backstage/cli-common';
import { ConfigSources } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import spawn from 'cross-spawn';
import { startEmbeddedDb } from './startEmbeddedDb';
@@ -46,6 +51,8 @@ export type RunBackendOptions = {
require?: string | string[];
/** An external linked workspace to override module resolution towards */
linkedWorkspace?: string;
/** Config file paths from --config flags */
configPaths?: string[];
};
export async function runBackend(options: RunBackendOptions) {
@@ -60,7 +67,8 @@ export async function runBackend(options: RunBackendOptions) {
const extraEnv: Record<string, string> = {};
if (process.env.EXPERIMENTAL_DEV_DB) {
const dbClient = await readDatabaseClient(options.configPaths);
if (dbClient === 'embedded-postgres') {
const db = await startEmbeddedDb();
extraEnv.APP_CONFIG_backend_database = JSON.stringify({
client: 'pg',
@@ -207,3 +215,28 @@ export async function runBackend(options: RunBackendOptions) {
return () => exitPromise;
}
async function readDatabaseClient(
configPaths?: string[],
): Promise<string | undefined> {
const rootDir = targetPaths.rootDir;
const source = ConfigSources.default({
rootDir,
allowMissingDefaultConfig: true,
argv: (configPaths ?? []).flatMap(p => [
'--config',
resolvePath(rootDir, p),
]),
});
const abortController = new AbortController();
for await (const { configs } of source.readConfigData({
signal: abortController.signal,
})) {
abortController.abort();
return ConfigReader.fromConfigs(configs).getOptionalString(
'backend.database.client',
);
}
return undefined;
}