Writing Backstage Configuration Files
File Format
Configuration is stored in YAML format in app-config.yaml files. This
configuration is shared between the frontend and backend and it looks something
like this:
app:
title: Backstage Example App
baseUrl: http://localhost:3000
backend:
listen: 0.0.0.0:7007
baseUrl: http://localhost:7007
organization:
name: CNCF
proxy:
/my/api:
target: https://example.com/api/
changeOrigin: true
pathRewrite:
^/proxy/my/api/: /
Configuration files are typically checked in and stored in the repo that houses the rest of the Backstage application.
The particular configuration that is available to each Backstage app depends on what plugins and packages are installed. To view the configuration reference for your own project, including what configuration keys available and whether they are needed by the frontend, use the following command:
yarn backstage-cli config:docs
Restricting public configuration
This feature is supported only with the default Rspack build. Legacy webpack
builds, enabled with LEGACY_WEBPACK_BUILD, embed frontend configuration in the
JavaScript bundle, which is not restricted by these allowlists.
When serving a protected app with a
public entry point, the app backend injects
all frontend-visible configuration into both entry points by default. To restrict
what is initially exposed to users without an authenticated session cookie,
configure app.publicConfig:
app:
publicConfig:
allowedConfig:
- app.title
- backend.baseUrl
- auth.environment
- auth.providers
allowedExtensions:
- sign-in-page:app
The presence of app.publicConfig enables filtering. Missing or empty lists allow
no config keys or extensions. An existing extension list is replaced with an empty
list when no extension IDs are allowed.
Allowed key paths include their descendants, except that app.extensions is
always controlled exclusively by allowedExtensions, including when app or
app.extensions is listed in allowedConfig. Extension IDs are matched exactly
for both string entries and object entries. These IDs select extension configuration
to expose; they do not control which extensions the public app loads. Normal
frontend visibility rules still apply. Authenticated
users continue to receive the full frontend-visible configuration.
Filtering applies to the injected configuration payload. HTML templates still
receive the full frontend-visible config, so titles and asset paths work even
when their config keys are omitted from allowedConfig. Values explicitly
rendered by a template remain visible in the HTML regardless of these allowlists.
Do not combine app.publicConfig with app.disableConfigInjection: true.
Filtering requires runtime configuration injection; disabling injection serves
the configuration embedded at build time without applying these allowlists.
This setting only applies when serving the enabled public entry point to unauthenticated users. It has no effect when that entry point is absent or disabled. The main entry point always receives the full frontend-visible config.
Environment Variable Overrides
Individual configuration values can be overridden using environment variables
prefixed with APP_CONFIG_. Everything following that prefix in the environment
variable name will be used as the config key, with _ replaced by .. For
example, to override the app.baseUrl value, set the APP_CONFIG_app_baseUrl
environment variable to the desired value.
The value of the environment variable is parsed as JSON, but it will fall back
to being interpreted as a string if it fails to parse. Note that if you for
example want to pass on the string "false", you need to wrap it in double
quotes, e.g. export APP_CONFIG_example='"false"'.
While it may be tempting to use environment variable overrides for supplying a lot of configuration values, we recommend using them sparingly. Try to stick to using configuration files, and only use environment variables for things like reusing deployment artifacts across staging and production environments.
Note that environment variables work for frontend configuration too. They are
picked up by the serve tasks of @backstage/cli for local development, and are
injected by the entrypoint of the nginx container serving the frontend in a
production build.
Configuration Files
It is possible to have multiple configuration files (bundled and/or remote*),
both to support different environments, but also to define configuration that is
local to specific packages. The configuration files to load are selected using a
--config <local-path|url> flag, and it is possible to load any number of
files. Paths are relative to the working directory of the executed process, for
example package/backend. This means that to select a config file in the repo
root when running the backend, you would use --config ../../my-config.yaml,
and for config file on a config server you would use
--config https://some.domain.io/app-config.yaml
Note: In case URLs are passed, it is also needed to set the remote option in the loadBackendConfig call.
If no config flags are specified, the default behavior is to load
app-config.yaml and, if it exists, app-config.local.yaml from the repo root.
In the provided project setup, app-config.local.yaml is .gitignore'd, making
it a good place to add config overrides and secrets for local development.
Note that if any config flags are provided, the default app-config.yaml files
are NOT loaded. To include them you need to explicitly include them with a flag,
for example:
yarn start --config ../../app-config.yaml --config ../../app-config.staging.yaml --config https://some.domain.io/app-config.yaml
All loaded configuration files are merged together using the following rules:
- Configurations have different priority, higher priority means you replace values from configurations with lower priority.
- Primitive values are completely replaced, as are arrays and all of their contents.
- Objects are merged together deeply, meaning that if any of the included configs contain a value for a given path, it will be found.
- A
nullvalue in a config file will be treated as an explicit absence of configuration. This means that the reading will not fall back to a lower priority config, but it will still be treated as if the configuration was not present.
The priority of the configurations is determined by the following rules, in order:
- Configuration from the
APP_CONFIG_environment variables has the highest priority, followed by files. - Files loaded with config flags are ordered by priority, where the last flag has the highest priority.
- If no config flags are provided,
app-config.local.yamlhas higher priority thanapp-config.yaml.
Includes and Dynamic Data
Includes are supported via special data loading keys that are prefixed with $,
which in turn provide a number of different ways to read in data. To load in an
external configuration value, supply an object with one of the special include
keys, for example $env or $file. A full list of supported include keys can
be found below. For example, the following will read the config key
backend.mySecretKey from the environment variable MY_SECRET_KEY:
backend:
mySecretKey:
$env: MY_SECRET_KEY
With the above configuration, calling config.getString('backend.mySecretKey')
will return the value of the environment variable MY_SECRET_KEY when the
backend started up. All includes are loaded at startup, so changing the contents
of files or environment variables will not be reflected at runtime.
Below is a list of the currently supported methods for loading includes.
Env Includes
This reads a string value from an environment variable. For example, the
following configuration loads the string value from the MY_SECRET environment
variable.
$env: MY_SECRET
Note however, that it's often more convenient to use environment variable substitution instead.
File Includes
This reads a string value from the entire contents of a text file. The file path
is relative to the source config file. For example, the following reads the
contents of my-secret.txt relative to the config file itself:
$file: ./my-secret.txt
Including Files
The $include keyword can be used to load configuration values from an external
file. It's able to load and parse data from .json, .yml, and .yaml files.
It's also possible to include a URL fragment (#) to point to a value at the
given path in the file, using a dot-separated list of keys.
For example, the following would read my-secret-key from my-secrets.json:
$include: ./my-secrets.json#deployment.key
Example my-secrets.json file:
{
"deployment": {
"key": "my-secret-key"
}
}
Environment Variable Substitution
Configuration files support environment variable substitution via a ${MY_VAR}
syntax. For example:
app:
baseUrl: https://${HOST}
Note that all environment variables must be available, or the entire
configuration value will evaluate to undefined.
The substitution syntax can be escaped using $${...}, which will be resolved
as ${...}.
Parameter substitution syntax (e.g. ${MY_VAR:-default-value}) is also
supported to provide a default or fallback value for a given environment
variable if it is unset, or is declared but has no value. For example:
app:
baseUrl: https://${HOST:-localhost:3000}
In the above example, when HOST is unset or has no value, it will be
substituted with localhost:3000.
Combining Includes and Environment Variable Substitution
The Includes and Environment Variable Substitutions can be combined to do something like read a secrets configuration for a specific environment. For example:
integrations:
github:
- host: github.com
apps:
- $include: secrets.${BACKSTAGE_ENVIRONMENT}.yaml
Example secrets.prod.yaml:
appId: 1
webhookUrl: https://smee.io/foo
clientId: someGithubAppClientId
clientSecret: someGithubAppClientSecret
webhookSecret: someWebhookSecret
privateKey: |
-----BEGIN RSA PRIVATE KEY-----
SomeRsaPrivateKeySecurelyStored
-----END RSA PRIVATE KEY-----
Sensitive information, such as private keys, should not be hard coded.
We recommend that this entire file should be a secret and stored as such in a secure storage solution like Vault, to ensure they are neither exposed nor misused. This example key part only shows the format on how to use the yaml | syntax to make sure that the key is valid.