Skip to content
Latchkey

Backstage app-config.yaml ${...} env substitution missing in CI

Backstage substitutes ${ENV_VAR} references in app-config.yaml from the process environment. In CI those variables come from secrets or step env, and a missing one leaves the value empty, breaking startup, database, or integrations config.

What this error means

The backend fails to read config, or a downstream feature (database, auth, integration) fails because a "${...}" placeholder resolved to an empty string.

backstage
Backend failed to start up Error: Failed to initialize database connection:
missing password
    at DatabaseManager ...
# app-config.yaml referenced ${POSTGRES_PASSWORD} but the env var was unset

Common causes

The referenced env variable is not set in the job

app-config.yaml uses ${POSTGRES_PASSWORD} or ${BACKEND_SECRET}, but the workflow never exported it, so substitution yields empty.

A secret was not passed to the step

The value lives in a repository secret that was not mapped into the step env, so the process cannot see it.

How to fix it

Export the referenced variables from secrets

  1. List every ${VAR} used in your app-config files.
  2. Map each to a secret or value in the step env.
  3. Re-run so substitution resolves to real values.
.github/workflows/ci.yml
env:
  POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
  BACKEND_SECRET: ${{ secrets.BACKEND_SECRET }}

Provide a CI-specific config with defaults

Use an app-config.local or CI overlay that supplies safe values so substitution never yields empty for non-secret fields.

Terminal
yarn start --config app-config.yaml --config app-config.ci.yaml

How to prevent it

  • Keep an inventory of every ${VAR} used in app-config files.
  • Map required secrets into the step env for jobs that read config.
  • Fail fast with a config lint that flags unset substitutions.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →