Docs · Tower
Installation
Prerequisites
- Backstage 1.17+
- Access to a Kubernetes cluster running:
- ArgoCD (for sync/promotion controls)
- Tekton Pipelines (for CI/CD visibility)
- External Secrets Operator (optional, for secret visibility)
- Crossplane (optional, for resource composition visibility)
Install Tower into Backstage
Tower needs the new frontend system (createApp({ features: [...] }) from @backstage/frontend-defaults). It is
a versioned package installed from a git tag on this repo; there is no npm registry (see RELEASING.md).
1. Add the package
yarn workspace app add "@jfillman/tower@<owner>/tower#vX.Y.Z" # <owner>: where this repo lives
2. Register Tower and the Hangar sign-in page
Edit packages/app/src/App.tsx:
import { createApp } from '@backstage/frontend-defaults';
import { createHangarSignInModule, githubSignInProvider, towerPlugin } from '@jfillman/tower';
export default createApp({
features: [
// ...your other features
towerPlugin,
createHangarSignInModule({
providers: [githubSignInProvider],
guest: process.env.NODE_ENV === 'development',
}),
],
});
towerPlugin brings its own route (/tower), sidebar entry and icon; there is no nav wiring.
3. Build and run
yarn start # http://localhost:3000, Tower at /tower
Sign-in page
createHangarSignInModule replaces Backstage’s stock sign-in page with the Hangar one: the dial mark and wordmark,
the product family (Tower, Glidepath, Airframe, Apron, Autopilot), and one sign-in box. It follows the user’s light
or dark theme.
-
One sign-in page per app. The module overrides the app’s
sign-in-pageextension. Remove any otherSignInPageBlueprintmodule fromfeatures, or the app fails to start with a duplicate extension. -
Providers are yours. Pass the auth providers your backend runs.
githubSignInProvideris ready-made; any other OAuth provider is an object:import { microsoftAuthApiRef } from '@backstage/core-plugin-api'; createHangarSignInModule({ providers: [ githubSignInProvider, { id: 'microsoft-auth-provider', title: 'Microsoft', apiRef: microsoftAuthApiRef, message: 'Company accounts only' }, ], });The first provider gets the amber primary button. Keep
idstable: it is how the page remembers the provider between visits. -
Guest is for local development.
guest: trueadds “Continue as guest”. It needs the backend guest provider (@backstage/plugin-auth-backend-module-guest-provider), which refuses sign-in outsideNODE_ENV=development. The legacy guest token (no auth backend) is not offered. -
Sessions carry over. The page remembers the provider under the same browser key as the stock page, and
githubSignInProvideruses the stock GitHub id, so a user signed in before the switch stays signed in. Signing out forgets the provider. -
taglinereplaces the line under the wordmark.
Configuration
Tower reads cluster configuration from:
- Kubernetes API access (
kubernetes.clusterLocatorMethodsin Backstage’sapp-config.yaml) - ArgoCD API endpoint (configurable via env or backstage.io labels)
- Tekton dashboard URL (auto-discovered or configured)
Cluster access
Don’t give Backstage a long-lived ServiceAccount token for any cluster.
- The cluster Backstage runs in: use the pod’s own in-cluster identity (
authProvider: serviceAccount, no token,caFileset to the in-cluster CA). - Every other cluster: make that cluster trust the home cluster’s ServiceAccount issuer (apron
components.clusterIdentity). Then point the entry at:- a projected token file for reads (
authMetadatahangar.io/token-file); - an actor ServiceAccount that is minted per write (
hangar.io/actor-*).
- a projected token file for reads (
- On a managed cloud cluster (EKS, GKE, AKS), use the provider’s workload-identity exchange instead.
The full setup, including a serviceAccountToken placeholder the Kubernetes plugin needs, is in
Cluster credentials.
Environment Variables
# ArgoCD
ARGOCD_API=https://argocd.example.com
ARGOCD_TOKEN=<token> # Optional, uses RBAC by default
# Tekton
TEKTON_DASHBOARD=https://tekton.example.com
TEKTON_NAMESPACE=tekton-pipelines
# Cluster identification
CLUSTER_NAME=prod
CLUSTER_DOMAIN=example.com
Backstage Configuration
Tower can also be configured via app-config.yaml:
tower:
argocdUrl: https://argocd.example.com
tektonUrl: https://tekton.example.com
clusterName: prod
Clusters and links
Tower names no cluster, GitHub owner or lab host. It reads them from tower: in app-config; a cluster’s name is its
registry name, which is also its kubectl context and its kubernetes.clusterLocatorMethods entry:
tower:
# The control-plane (dev) cluster: Tekton, Glidepath's release records, Airframe's app XRs. Required for the
# Pipelines, Ops Wall and Provisioning views; they say so when it is missing.
controlPlaneCluster: <dev-cluster>
clusters: # all optional; an unset value hides its link or lookup
<dev-cluster>:
tenantsRepo: <owner>/<tenants-repo> # the registry record's tenantsRepo, where XR requests are opened
infisicalUrl: https://<infisical-ui>
grafanaUrl: https://<grafana>
<prod-cluster>:
grafanaUrl: https://<grafana>
airframe: # the fleet's default environment chart
repoURL: https://github.com/<owner>/airframe.git
DORA metrics source (Ops Wall)
The Ops Wall’s DORA panel queries dora-exporter’s series (dora_deployments_total, dora_releases_total,
dora_lead_time_seconds, dora_time_to_restore_seconds_experimental) with PromQL, through the Kubernetes proxy of
the cluster named here. Its windows are 7, 30 and 90 days, so point it at the long-term query layer (Thanos Query,
Mimir), not a Prometheus with short local retention:
tower:
dora:
metrics:
cluster: <dev-cluster> # as registered in kubernetes.clusterLocatorMethods
namespace: observability
service: thanos-query-frontend
port: 9090
Without this block Tower uses kube-prometheus-stack-prometheus:9090 in observability on tower.controlPlaneCluster. The
Kubernetes plugin’s identity needs get on services/proxy for that Service.
Release records
Release records need Backstage’s backend from the backstage repo (routes under /api/glidepath/release-* and the
release-signoff-poll / release-record-poll tasks) and Backstage’s notifications backend for the announcements.
The backend uses its GitHub integration credential, which needs, on the app repos, gitops-<app> repos and the
release-modes repo: read contents, issues, milestones, PRs and reviews; write contents (the draft, and branches for
PRs); open PRs; set commit statuses; and create releases (only for apps that opt in to GitHub Releases).
tower:
# Where each app's release mode lives. Defaults shown (owner: the app's GitHub owner).
releaseModes:
repo: gitops-cluster-dev-tenants
path: release-modes.yaml
# Jira keys (ABC-123) in PR titles, bodies and branches link here. Unset: no Jira links.
jira:
baseUrl: https://your-org.atlassian.net
release-modes.yaml does not need to exist: every app is Assisted until an admin changes one, and the first change
opens the PR that creates it. Only group:default/admins gets tower.release.mode; it is never granted to owners.
Verification
After installation, Tower should be available in the Backstage sidebar. Verify by:
- Navigating to
/towerin your Backstage instance - Checking that you see the Overview tab loading
- Confirming that Releases and Pipelines tabs appear once cluster data loads
If Tower doesn’t appear, check:
- Browser console for module load errors
- Backstage backend logs for API connectivity issues
- Kubernetes connectivity (in-cluster vs. external access): see the troubleshooting table in Cluster credentials