Skip to main content
Version: v1.0.x

Backstage Configuration

OpenChoreo uses Backstage as its developer portal, providing a unified interface for managing organizations, projects, components, and deployments. This guide covers configuration options for customizing Backstage in your OpenChoreo deployment.

Backstage configuration is done via Helm values in the Control Plane chart.

Core Configuration

Console/UI URL:

backstage:
baseUrl: "https://console.example.com"
http:
hostnames:
- console.example.com

backstage.baseUrl sets the URL used for OAuth redirects and internal link generation. backstage.http.hostnames configures the Gateway API HTTPRoute that routes traffic to Backstage.

OpenChoreo API URL:

openchoreoApi:
http:
hostnames:
- api.example.com
config:
server:
publicUrl: "https://api.example.com"

Authentication and Authorization

By default, OpenChoreo configures Thunder as the identity provider for Backstage with a pre-configured OAuth client for testing purposes. If you want to integrate an external identity provider instead, follow the steps below to configure both authentication and authorization for the new client.

Authentication

Create an OAuth 2.0 client with the following requirements:

OAuth Client Requirements:

  1. Grant Types: The OAuth client must support both:

    • authorization_code - For user authentication and login flows
    • client_credentials - For service-to-service authentication
  2. Token Format: Configure the client to issue JWT tokens (not opaque tokens)

  3. Redirect URLs: Add the Backstage callback URL:

    • <protocol>://<backstage-domain>/api/auth/openchoreo-auth/handler/frame
    • Replace <protocol> with http or https and <backstage-domain> with your actual Backstage domain
  4. User Claims: Configure the access token to include the following claims:

    • family_name - User's last name
    • given_name - User's first name
    • email - User's email address
    • groups - Groups the user belongs to

Helm Configuration:

Once you have created the OAuth client, create/update a Backstage credentials secret and configure Backstage to use it:

kubectl create secret generic backstage-secrets \
-n openchoreo-control-plane \
--from-literal=backend-secret="your-32-character-secret-here" \
--from-literal=client-secret="your-client-secret" \
--from-literal=jenkins-api-key="not-used" \
--dry-run=client -o yaml | kubectl apply -f -
backstage:
secretName: "backstage-secrets"
auth:
clientId: "your-client-id"
redirectUrls:
- "<protocol>://<backstage-domain>/api/auth/openchoreo-auth/handler/frame"
oidcScope: "openid profile email"
scope: ""

Scope Configuration:

FieldDescriptionDefault
backstage.auth.oidcScopeSpace-separated scopes requested during user login via the authorization_code flow"openid profile email"
backstage.auth.scopeSpace-separated scopes requested when the Backstage backend requests service tokens via client_credentials. Leave empty to use the IdP default, or set explicitly when required by your IdP (e.g. "api://client-id/.default" for Azure AD).""

See Identity Provider Configuration for detailed setup instructions.

Authorization

With authorization enabled by default, Backstage uses the client_credentials grant to authenticate with the OpenChoreo API as a service account. The API matches the sub claim in the issued JWT to identify the caller, so the new client must be granted the backstage-catalog-reader role via a bootstrap authorization mapping.

Add the following to your values override file, replacing your-client-id with the same client ID used in the authentication configuration above:

openchoreoApi:
config:
security:
authorization:
bootstrap:
mappings:
- name: backstage-catalog-reader-binding
kind: ClusterAuthzRoleBinding
system: true
roleMappings:
- roleRef:
name: backstage-catalog-reader
kind: ClusterAuthzRole
entitlement:
claim: sub
value: "your-client-id"
effect: allow

Feature Flags

backstage:
features:
workflows:
enabled: true # Requires Workflow Plane
observability:
enabled: true # Requires Observability Plane

Resource Configuration

backstage:
replicas: 1
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 200m
memory: 256Mi

autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 3

Environment Variables

The chart sets two groups of environment variables on the Backstage container:

Default Variables (backstage.env)

These are the base variables defined in the chart. They can be overridden in a values file, but avoid using --set backstage.env[0].name=... on the command line — Helm's sparse array handling can corrupt the list.

backstage:
env:
- name: NODE_ENV
value: production
- name: LOG_LEVEL
value: info
- name: PORT
value: "7007"

Extra Variables (backstage.extraEnv)

To add custom environment variables without touching the defaults, use backstage.extraEnv. It is appended after backstage.env in the container spec, so if you use the same key, the last value wins.

backstage:
extraEnv:
- name: CUSTOM_VAR
value: "my-value"
- name: LOG_LEVEL # overrides the default "info"
value: debug

All Environment Variables

The chart sets the following environment variables on the Backstage container. Variables marked "from Secret" are read from the Secret referenced by backstage.secretName.

VariableDescriptionSource
NODE_ENVNode.js environmentbackstage.env (default: production)
LOG_LEVELLogging verbositybackstage.env (default: info)
PORTHTTP server portbackstage.env (default: 7007)
BACKSTAGE_BASE_URLPublic URL for OAuth redirects and linksbackstage.baseUrl
OPENCHOREO_API_URLInternal OpenChoreo API endpointbackstage.openchoreoApi.url (auto-configured)
BACKEND_SECRETSession encryption keyfrom Secret (backend-secret)
OPENCHOREO_AUTH_CLIENT_IDOAuth client IDbackstage.auth.clientId
OPENCHOREO_AUTH_CLIENT_SECRETOAuth client secretfrom Secret (client-secret)
OPENCHOREO_AUTH_AUTHORIZATION_URLOAuth authorization endpointsecurity.oidc.authorizationUrl
OPENCHOREO_AUTH_TOKEN_URLOAuth token endpointsecurity.oidc.tokenUrl
OPENCHOREO_AUTH_OIDC_SCOPEOIDC scopesbackstage.auth.oidcScope (default: openid profile email)
OPENCHOREO_FEATURES_AUTH_ENABLEDEnable authenticationsecurity.enabled
OPENCHOREO_FEATURES_AUTHZ_ENABLEDEnable authorizationsecurity.authz.enabled
OPENCHOREO_FEATURES_AUTH_REDIRECT_FLOW_ENABLEDSilent redirect vs sign-in popupbackstage.features.auth.redirectFlow.enabled
OPENCHOREO_FEATURES_WORKFLOWS_ENABLEDShow Workflows UIbackstage.features.workflows.enabled
OPENCHOREO_FEATURES_OBSERVABILITY_ENABLEDShow Metrics/Traces/Logs UIbackstage.features.observability.enabled
DATABASE_CLIENTDatabase driver (better-sqlite3 or pg)backstage.database.type
SQLITE_STORAGE_DIRSQLite database directory (when using SQLite)backstage.database.sqlite.mountPath

HTTP Routing Configuration

OpenChoreo uses the Gateway API for traffic routing (not Kubernetes Ingress). The chart creates HTTPRoute resources that route traffic through a shared Gateway managed by kgateway.

backstage:
http:
enabled: true
hostnames:
- console.example.com

TLS is configured at the gateway level, not per-HTTPRoute. See the gateway configuration section in the Helm chart values.

Service Configuration

backstage:
service:
type: ClusterIP
port: 7007

OpenChoreo API Integration

Backstage communicates with the OpenChoreo API for backend operations:

backstage:
openchoreoApi:
url: "" # Auto-configured to internal service URL

The API URL defaults to http://openchoreo-api.{namespace}.svc.cluster.local:8080/api/v1.

Backstage Secret

Backstage credentials are loaded from a Kubernetes Secret referenced by backstage.secretName.

Required keys:

  • backend-secret: Backstage session encryption key
  • client-secret: OAuth client secret
  • jenkins-api-key: Jenkins integration API key (use a placeholder if not using Jenkins)

When backstage.database.type=postgresql, the same Secret must also include:

  • postgres-host
  • postgres-port
  • postgres-user
  • postgres-password
  • postgres-db
External Secrets Operator

If you are using External Secrets Operator (ESO) to manage backstage-secrets (as in the default k3d setup), do not use kubectl create secret directly — ESO will overwrite your changes on its next sync. Instead, add the PostgreSQL credentials to your secret store (e.g., OpenBao, AWS Secrets Manager) and update the ExternalSecret resource to include the additional keys.

backstage:
secretName: "backstage-secrets"

Database Configuration

Backstage requires a database to store catalog entities, user settings, and plugin data. OpenChoreo supports two database backends:

BackendUse CasePersistenceScalability
SQLite (default)Development, single-replicaOptional (emptyDir or PVC)Single replica only
PostgreSQLProduction, multi-replicaExternal databaseHorizontal scaling

Default Configuration (SQLite)

By default, Backstage uses SQLite with an emptyDir volume, which means data is lost when the pod restarts:

backstage:
database:
type: sqlite
sqlite:
persistence:
enabled: false # Uses emptyDir (data lost on restart)

SQLite with Persistence

For development environments where you want data to persist across restarts but don't need horizontal scaling:

backstage:
database:
type: sqlite
sqlite:
mountPath: /app/.config/backstage
persistence:
enabled: true
size: 1Gi
storageClassName: "" # Uses default storage class
accessMode: ReadWriteOnce
warning

SQLite locks the database file, preventing multiple Backstage replicas from running simultaneously. For high-availability deployments, use PostgreSQL.

PostgreSQL for Production

For production deployments requiring high availability and data durability, configure an external PostgreSQL database.

Prerequisites

  1. Provision a PostgreSQL Database: Use a managed service (AWS RDS, Google Cloud SQL, Azure Database for PostgreSQL) or a self-hosted PostgreSQL instance
  2. Create a Database: Create a database for Backstage (e.g., backstage)
  3. Create a Database User: Create a user with full access to the database

Backstage automatically creates per-plugin databases (e.g., backstage_plugin_catalog, backstage_plugin_auth) using the provided credentials.

Helm Configuration

For managed PostgreSQL services or external databases:

kubectl create secret generic backstage-secrets \
-n openchoreo-control-plane \
--from-literal=backend-secret="your-32-character-secret-here" \
--from-literal=client-secret="your-client-secret" \
--from-literal=jenkins-api-key="not-used" \
--from-literal=postgres-host="postgres.example.com" \
--from-literal=postgres-port="5432" \
--from-literal=postgres-user="backstage" \
--from-literal=postgres-password="your-secure-password" \
--from-literal=postgres-db="backstage" \
--dry-run=client -o yaml | kubectl apply -f - && \
helm upgrade --install openchoreo-control-plane oci://ghcr.io/openchoreo/helm-charts/openchoreo-control-plane \
--version 1.0.0 \
--namespace openchoreo-control-plane \
--reuse-values \
--set backstage.secretName="backstage-secrets" \
--set backstage.database.type=postgresql \
--set backstage.database.postgresql.ssl=true
tip

Set backstage.database.postgresql.ssl=true when connecting to managed PostgreSQL services (AWS RDS, Google Cloud SQL, Azure Database for PostgreSQL) that enforce SSL connections.

PostgreSQL Configuration Reference

ParameterDescriptionDefault
backstage.database.typeDatabase backend (sqlite or postgresql)sqlite
backstage.database.postgresql.sslEnable SSL for PostgreSQL connections (sets PGSSLMODE=require)false
backstage.secretNameSecret containing Backstage and PostgreSQL credentials""

PostgreSQL connection fields are read from Secret keys (postgres-host, postgres-port, postgres-user, postgres-password, postgres-db) referenced by backstage.secretName.

Verifying PostgreSQL Connection

After deploying with PostgreSQL, verify the connection:

# Check Backstage pod is running
kubectl get pods -n openchoreo-control-plane -l app.kubernetes.io/component=backstage

# Verify database environment variables are set to PostgreSQL
kubectl get deployment backstage -n openchoreo-control-plane \
-o jsonpath='{range .spec.template.spec.containers[0].env[*]}{.name}={.value}{"\n"}{end}' \
| grep -E "DATABASE|POSTGRES"

# Check logs for database connection errors
kubectl logs -n openchoreo-control-plane deployment/backstage | head -50

If PostgreSQL is configured correctly, Backstage will create per-plugin databases automatically:

# List databases created by Backstage (run against your PostgreSQL instance)
psql -U backstage -d backstage -c "\l" | grep backstage_plugin

Expected output shows databases like backstage_plugin_catalog, backstage_plugin_auth, etc.

Health Checks

Backstage health checks are pre-configured:

CheckEndpointDefault
Liveness/healthcheckEnabled
Readiness/healthcheckEnabled

Example Configurations

backstage:
enabled: true
replicas: 1
baseUrl: "http://openchoreo.localhost:8080"
http:
hostnames:
- openchoreo.localhost
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
features:
workflows:
enabled: true
observability:
enabled: true

Troubleshooting

Backstage Not Loading

Check pod status and logs:

kubectl get pods -n openchoreo-control-plane -l app.kubernetes.io/component=backstage
kubectl logs -n openchoreo-control-plane deployment/backstage

Authentication Issues

Verify OAuth configuration by checking the deployment spec (the Backstage image is distroless, so exec commands like env are not available):

# Check Backstage auth-related environment variables
kubectl get deployment backstage -n openchoreo-control-plane \
-o jsonpath='{range .spec.template.spec.containers[0].env[*]}{.name}={.value}{"\n"}{end}' \
| grep -E "AUTH|THUNDER"

# Check Thunder is running
kubectl get pods -n thunder -l app.kubernetes.io/name=thunder

API Connection Errors

Verify OpenChoreo API is accessible:

# Check API pod
kubectl get pods -n openchoreo-control-plane -l app.kubernetes.io/component=api-server

# Check API logs for errors
kubectl logs -n openchoreo-control-plane deployment/openchoreo-api --tail=20

Database Connection Issues

If Backstage fails to start with database errors:

For SQLite errors:

# Check for SQLite-specific errors in logs
kubectl logs -n openchoreo-control-plane deployment/backstage | grep -i "sqlite\|database"

# Check the volume type (emptyDir vs PVC)
kubectl get deployment backstage -n openchoreo-control-plane \
-o jsonpath='{.spec.template.spec.volumes}' | python3 -m json.tool

For PostgreSQL errors:

# Verify PostgreSQL environment variables are configured
kubectl get deployment backstage -n openchoreo-control-plane \
-o jsonpath='{range .spec.template.spec.containers[0].env[*]}{.name}={.value}{"\n"}{end}' \
| grep -E "DATABASE|POSTGRES"

# Check for connection errors in logs
kubectl logs -n openchoreo-control-plane deployment/backstage | grep -i "postgres\|connection\|ECONNREFUSED"

# Verify the secret has all required postgres keys
kubectl get secret backstage-secrets -n openchoreo-control-plane \
-o jsonpath='{.data}' | python3 -c "import json,sys; print('\n'.join(sorted(json.load(sys.stdin).keys())))"

Common issues:

ErrorCauseSolution
ECONNREFUSEDPostgreSQL not reachableVerify hostname, port, and network policies
password authentication failedInvalid credentialsCheck postgres-password in the Secret referenced by backstage.secretName
database does not existMissing databaseCreate the database on PostgreSQL server
connection.filename is not supportedOutdated Backstage imageUpgrade to latest control plane chart

Next Steps