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:
-
Grant Types: The OAuth client must support both:
authorization_code- For user authentication and login flowsclient_credentials- For service-to-service authentication
-
Token Format: Configure the client to issue JWT tokens (not opaque tokens)
-
Redirect URLs: Add the Backstage callback URL:
<protocol>://<backstage-domain>/api/auth/openchoreo-auth/handler/frame- Replace
<protocol>withhttporhttpsand<backstage-domain>with your actual Backstage domain
-
User Claims: Configure the access token to include the following claims:
family_name- User's last namegiven_name- User's first nameemail- User's email addressgroups- 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:
| Field | Description | Default |
|---|---|---|
backstage.auth.oidcScope | Space-separated scopes requested during user login via the authorization_code flow | "openid profile email" |
backstage.auth.scope | Space-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.
| Variable | Description | Source |
|---|---|---|
NODE_ENV | Node.js environment | backstage.env (default: production) |
LOG_LEVEL | Logging verbosity | backstage.env (default: info) |
PORT | HTTP server port | backstage.env (default: 7007) |
BACKSTAGE_BASE_URL | Public URL for OAuth redirects and links | backstage.baseUrl |
OPENCHOREO_API_URL | Internal OpenChoreo API endpoint | backstage.openchoreoApi.url (auto-configured) |
BACKEND_SECRET | Session encryption key | from Secret (backend-secret) |
OPENCHOREO_AUTH_CLIENT_ID | OAuth client ID | backstage.auth.clientId |
OPENCHOREO_AUTH_CLIENT_SECRET | OAuth client secret | from Secret (client-secret) |
OPENCHOREO_AUTH_AUTHORIZATION_URL | OAuth authorization endpoint | security.oidc.authorizationUrl |
OPENCHOREO_AUTH_TOKEN_URL | OAuth token endpoint | security.oidc.tokenUrl |
OPENCHOREO_AUTH_OIDC_SCOPE | OIDC scopes | backstage.auth.oidcScope (default: openid profile email) |
OPENCHOREO_FEATURES_AUTH_ENABLED | Enable authentication | security.enabled |
OPENCHOREO_FEATURES_AUTHZ_ENABLED | Enable authorization | security.authz.enabled |
OPENCHOREO_FEATURES_AUTH_REDIRECT_FLOW_ENABLED | Silent redirect vs sign-in popup | backstage.features.auth.redirectFlow.enabled |
OPENCHOREO_FEATURES_WORKFLOWS_ENABLED | Show Workflows UI | backstage.features.workflows.enabled |
OPENCHOREO_FEATURES_OBSERVABILITY_ENABLED | Show Metrics/Traces/Logs UI | backstage.features.observability.enabled |
DATABASE_CLIENT | Database driver (better-sqlite3 or pg) | backstage.database.type |
SQLITE_STORAGE_DIR | SQLite 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 keyclient-secret: OAuth client secretjenkins-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-hostpostgres-portpostgres-userpostgres-passwordpostgres-db
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:
| Backend | Use Case | Persistence | Scalability |
|---|---|---|---|
| SQLite (default) | Development, single-replica | Optional (emptyDir or PVC) | Single replica only |
| PostgreSQL | Production, multi-replica | External database | Horizontal 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
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
- Provision a PostgreSQL Database: Use a managed service (AWS RDS, Google Cloud SQL, Azure Database for PostgreSQL) or a self-hosted PostgreSQL instance
- Create a Database: Create a database for Backstage (e.g.,
backstage) - 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
- External PostgreSQL
- In-Cluster PostgreSQL
- Values File
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
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.
For PostgreSQL running within the same Kubernetes cluster:
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.your-namespace.svc.cluster.local" \
--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
Using a values file for better secret management:
# backstage-db-values.yaml
backstage:
secretName: "backstage-secrets"
database:
type: postgresql
postgresql:
ssl: true
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 \
-f backstage-db-values.yaml
PostgreSQL Configuration Reference
| Parameter | Description | Default |
|---|---|---|
backstage.database.type | Database backend (sqlite or postgresql) | sqlite |
backstage.database.postgresql.ssl | Enable SSL for PostgreSQL connections (sets PGSSLMODE=require) | false |
backstage.secretName | Secret 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:
| Check | Endpoint | Default |
|---|---|---|
| Liveness | /healthcheck | Enabled |
| Readiness | /healthcheck | Enabled |
Example Configurations
- Development
- Production
- API-only Mode
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
backstage:
enabled: true
replicas: 2
baseUrl: "https://console.example.com"
secretName: "backstage-secrets"
http:
hostnames:
- console.example.com
# PostgreSQL for production (required for multi-replica)
database:
type: postgresql
postgresql:
ssl: true
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 5
targetCPUUtilizationPercentage: 70
features:
workflows:
enabled: true
observability:
enabled: true
For headless deployments where only the API is needed:
backstage:
enabled: false
# Security can also be disabled for development
security:
enabled: false
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:
| Error | Cause | Solution |
|---|---|---|
ECONNREFUSED | PostgreSQL not reachable | Verify hostname, port, and network policies |
password authentication failed | Invalid credentials | Check postgres-password in the Secret referenced by backstage.secretName |
database does not exist | Missing database | Create the database on PostgreSQL server |
connection.filename is not supported | Outdated Backstage image | Upgrade to latest control plane chart |
Next Steps
- Identity Provider Configuration: Configure an external IdP
- Deployment Topology: Set up organizations and environments
- Helm Charts Reference: Complete Backstage configuration options