TT Lab
Get started
Learn Learning paths Courses

CBA — Backstage Associate

app-config Layers and Predicting the Merge

Continue in TT Lab

Goal

Split Backstage configuration into several files, and predict by hand what remains when those files are layered. At the end, you move the production configuration to Kubernetes objects and check where values come from in a real deployment.

Why it matters

Configuration layers are the place most often gotten wrong in CBA. The rules themselves are two lines, but the results run against intuition. Mappings are merged deeply key by key, but lists are not merged and are replaced wholesale. If you write three catalog entry points in the base file and only one in the override, the result is one, not four, and no warning appears. The same goes when the kind of value changes. Where there was a string and a mapping comes in, the string disappears. And credentials must never be written as values in any file, because configuration files are committed to the repository and baked into images. Backstage itself is not in this environment, so grading reads the files and cluster objects.

Steps

  1. In /root/cba-config/app-config.yaml, write the base configuration — app.title any value, app.baseUrl: http://localhost:3000, organization.name any value, backend.baseUrl: http://localhost:7007, backend.listen.port: 7007, backend.cors.origin: http://localhost:3000, backend.database.client: better-sqlite3, backend.database.connection: /tmp/portal.sqlite.
  2. In the same file, continue with integrations and proxy — host: github.com and token (an environment variable substitution form containing GITHUB) in the first item of integrations.github, and under proxy.endpoints the key /argocd/api with target (an address starting with https), changeOrigin: true, and headers.Cookie (an environment variable substitution form).
  3. In the same file, continue with the catalog configuration — catalog.import.entityFilename: catalog-info.yaml, in allow of the first item of catalog.rules the nine kinds Component, API, Resource, System, Domain, Group, User, Location, and Template, two entries in catalog.locations (the first is type: file and its target ends with entities.yaml, the second is type: url and its target is a github.com address), and in catalog.providers.github.labhubOrg organization: labhub, catalogPath: /catalog-info.yaml, schedule.frequency.minutes: 30, and schedule.timeout.minutes: 3.
  4. In /root/cba-config/app-config.local.yaml, write the override — app.baseUrl: http://portal.labhub.test, backend.baseUrl: http://portal.labhub.test:7007, backend.database.client: pg, under backend.database.connection all of host, port, user, and database in the environment variable substitution form, and only one type: url entry for catalog.locations. Do not write backend.listen.port.
  5. In /root/cba-config/merged.yaml, write as they are the values that remain when the file from step 4 is layered on top of the files from steps 1–3. Mappings are merged deeply and lists are replaced wholesale.
  6. In /root/cba-config/app-config.production.yaml, write the production configuration — both app.baseUrl and backend.baseUrl as https://portal.labhub.io, backend.listen.host: 0.0.0.0, backend.database.client: pg, auth.environment: production, clientId and clientSecret of auth.providers.github.production in the environment variable substitution form, resolver of the first item of signIn.resolvers in the same place as the name of a resolver that matches to a catalog User entity, techdocs.builder: external, and techdocs.publisher.type as an external storage type, not local.
  7. In /root/cba-config/env-names.txt, write the names of the environment variables that the three configuration files reference, without curly braces, one per line, sorted and without duplicates.
  8. Put the production configuration on the cluster. Create the namespace cba-config, and inside it create the ConfigMap portal-app-config from the file of step 6 (the key name must be app-config.production.yaml). Then create the Deployment portal — image node:22-alpine, containerPort: 7007, mount that ConfigMap as a volume, and the value of the environment variable APP_CONFIG_app_baseUrl must be exactly the same as app.baseUrl in the file of step 6.

Notes

Base app-config

In /root/cba-config/app-config.yaml, write the base configuration — app.title any value, app.baseUrl: http://localhost:3000, organization.name any value, backend.baseUrl: http://localhost:7007, backend.listen.port: 7007, backend.cors.origin: http://localhost:3000, backend.database.client: better-sqlite3, backend.database.connection: /tmp/portal.sqlite.

The frontend and the backend come up on different ports. The CORS origin is the side the browser comes from, that is, the frontend address.

Integrations and proxy

In the same file, continue with integrations and proxy — host: github.com and token (an environment variable substitution form containing GITHUB) in the first item of integrations.github, and under proxy.endpoints the key /argocd/api with target (an address starting with https), changeOrigin: true, and headers.Cookie (an environment variable substitution form).

If you write a value directly in the credential position, that secret stays in git history and image layers. Use the form that is substituted with an environment variable at startup.

Catalog rules and discovery

In the same file, continue with the catalog configuration — catalog.import.entityFilename: catalog-info.yaml, in allow of the first item of catalog.rules the nine kinds Component, API, Resource, System, Domain, Group, User, Location, and Template, two entries in catalog.locations (the first is type: file and its target ends with entities.yaml, the second is type: url and its target is a github.com address), and in catalog.providers.github.labhubOrg organization: labhub, catalogPath: /catalog-info.yaml, schedule.frequency.minutes: 30, and schedule.timeout.minutes: 3.

rules is an allowlist, so registration of a kind that is not here is rejected. If you leave out Template, the scaffolder looks completely empty.

Local override

In /root/cba-config/app-config.local.yaml, write the override — app.baseUrl: http://portal.labhub.test, backend.baseUrl: http://portal.labhub.test:7007, backend.database.client: pg, under backend.database.connection all of host, port, user, and database in the environment variable substitution form, and only one type: url entry for catalog.locations. Do not write backend.listen.port.

Write only the values to change in the override file. If you copy in values you will not change, then when the base file moves, only this side stays at the old value.

Predict the merge result

In /root/cba-config/merged.yaml, write as they are the values that remain when the file from step 4 is layered on top of the files from steps 1–3. Mappings are merged deeply and lists are replaced wholesale.

Mappings are merged deeply key by key and lists are replaced wholesale. Keys the override did not touch keep the base file's values as they are.

Production configuration

In /root/cba-config/app-config.production.yaml, write the production configuration — both app.baseUrl and backend.baseUrl as https://portal.labhub.io, backend.listen.host: 0.0.0.0, backend.database.client: pg, auth.environment: production, clientId and clientSecret of auth.providers.github.production in the environment variable substitution form, resolver of the first item of signIn.resolvers in the same place as the name of a resolver that matches to a catalog User entity, techdocs.builder: external, and techdocs.publisher.type as an external storage type, not local.

If it listens only on loopback inside a container, the service cannot reach the Pod. And authentication is split into two stages: the login provider and identity resolution.

List of referenced environment variables

In /root/cba-config/env-names.txt, write the names of the environment variables that the three configuration files reference, without curly braces, one per line, sorted and without duplicates.

All three configuration files are in scope. This list becomes the list of Secret keys you must fill in the deployment manifest.

Configuration into the cluster

Put the production configuration on the cluster. Create the namespace cba-config, and inside it create the ConfigMap portal-app-config from the file of step 6 (the key name must be app-config.production.yaml). Then create the Deployment portal — image node:22-alpine, containerPort: 7007, mount that ConfigMap as a volume, and the value of the environment variable APP_CONFIG_app_baseUrl must be exactly the same as app.baseUrl in the file of step 6.

If you create a ConfigMap from a file, the file name becomes the key as it is. And an environment variable whose name replaces the dots of the configuration path with underscores overrides that one line.