Show Kubernetes Objects for a Component in Backstage (Backstage Manages Itself, In-Cluster)

This is a follow-up to:

This post enables the Backstage Kubernetes view without changing any Backstage source code and without installing any extra plugin. We only add Kubernetes-side configuration:

  • Add Backstage Kubernetes plugin config (in app-config.k8s.yaml)
  • Create a read-only ServiceAccount + RBAC for Backstage to read cluster objects, events, logs, and metrics
  • Map the Component to Kubernetes objects using backstage.io/kubernetes-id
  • Label the Kubernetes objects with the same backstage.io/kubernetes-id so they show up under the Component

What you get in the UI

Inside the homelab-backstage Component page → Kubernetes tab:

  • Deployments / Pods
  • Services / Ingress
  • Events
  • Pod logs
  • Pod + node metrics (if Metrics Server is available)

  1. Map the Backstage Component to Kubernetes objects (Catalog annotations)

Update your Component entity so Backstage knows which cluster, which namespace, and which kubernetes-id to query.

catalog-info.yaml

diff --git a/catalog-info.yaml b/catalog-info.yaml
index a8abe33..164d342 100644
--- a/catalog-info.yaml
+++ b/catalog-info.yaml
@@ -8,7 +8,9 @@ metadata:
     jenkins.io/job-full-name: backstage/homelab-backstage-main-ci
     backstage.io/techdocs-ref: dir:.
     harbor.maksonlee.com/repository: backstage/homelab-backstage
-
+    backstage.io/kubernetes-id: homelab-backstage
+    backstage.io/kubernetes-namespace: backstage
+    backstage.io/kubernetes-cluster: in-cluster
 spec:
   type: website
   owner: user:default/maksonlee

  1. Add Kubernetes plugin config (in-cluster)

Backstage needs to know how to locate clusters and how to authenticate. Here we use:

  • serviceLocatorMethod: multiTenant
  • clusterLocatorMethods: config with a single cluster in-cluster
  • authProvider: serviceAccount so it uses the Pod’s ServiceAccount token automatically

kubernetes/app-config.k8s.yaml

diff --git a/kubernetes/app-config.k8s.yaml b/kubernetes/app-config.k8s.yaml
index 3feca55..04d5aa4 100644
--- a/kubernetes/app-config.k8s.yaml
+++ b/kubernetes/app-config.k8s.yaml
@@ -7,3 +7,16 @@ backend:
   cors:
     origin: https://backstage-k8s.maksonlee.com
     credentials: true
+
+kubernetes:
+  serviceLocatorMethod:
+    type: multiTenant
+
+  clusterLocatorMethods:
+    - type: config
+      clusters:
+        - name: in-cluster
+          url: https://kubernetes.default.svc
+          authProvider: serviceAccount
+          skipTLSVerify: false
+          skipMetricsLookup: false

  1. Grant Backstage read-only access to Kubernetes (RBAC)

The Kubernetes tab can show more than just Deployments/Pods; common “extra info” includes:

  • Events
  • Pod logs
  • Pod + node metrics (from metrics.k8s.io)

So we add a read-only ServiceAccount and RBAC.

kubernetes/backstage-k8s-rbac.yaml (new file)

diff --git a/kubernetes/backstage-k8s-rbac.yaml b/kubernetes/backstage-k8s-rbac.yaml
new file mode 100644
index 0000000..05c4034
--- /dev/null
+++ b/kubernetes/backstage-k8s-rbac.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: backstage-k8s-reader
+  namespace: backstage
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: backstage-k8s-readonly
+rules:
+  - apiGroups: ['', 'apps', 'batch', 'autoscaling', 'networking.k8s.io']
+    resources:
+      - namespaces
+      - pods
+      - pods/log
+      - services
+      - endpoints
+      - configmaps
+      - limitranges
+      - resourcequotas
+      - deployments
+      - replicasets
+      - daemonsets
+      - statefulsets
+      - jobs
+      - cronjobs
+      - horizontalpodautoscalers
+      - ingresses
+      - events
+    verbs: ['get', 'list', 'watch']
+  - apiGroups: ['metrics.k8s.io']
+    resources: ['pods', 'nodes']
+    verbs: ['get', 'list']
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: backstage-k8s-readonly
+subjects:
+  - kind: ServiceAccount
+    name: backstage-k8s-reader
+    namespace: backstage
+roleRef:
+  kind: ClusterRole
+  name: backstage-k8s-readonly
+  apiGroup: rbac.authorization.k8s.io

  1. Label Kubernetes resources with backstage.io/kubernetes-id

Backstage links Kubernetes objects to a Component by matching the annotation:

  • backstage.io/kubernetes-id: homelab-backstage

…to Kubernetes resources that contain the label:

  • backstage.io/kubernetes-id: homelab-backstage

So the fix is: add this label to all workloads/services you want to appear under that Component.

kubernetes/homelab-backstage.yaml

diff --git a/kubernetes/homelab-backstage.yaml b/kubernetes/homelab-backstage.yaml
index 02ab74c..38beb02 100644
--- a/kubernetes/homelab-backstage.yaml
+++ b/kubernetes/homelab-backstage.yaml
@@ -3,6 +3,9 @@ kind: Deployment
 metadata:
   name: homelab-backstage
   namespace: backstage
+  labels:
+    app: homelab-backstage
+    backstage.io/kubernetes-id: homelab-backstage
 spec:
   replicas: 1
   selector:
@@ -12,7 +15,9 @@ spec:
     metadata:
       labels:
         app: homelab-backstage
+        backstage.io/kubernetes-id: homelab-backstage
     spec:
+      serviceAccountName: backstage-k8s-reader
       initContainers:
         - name: wait-for-postgres
           image: postgres:16
@@ -30,8 +35,6 @@ spec:
           ports:
             - name: http
               containerPort: 7007
-
-          # Load env vars (OIDC client for k8s, plus your existing secrets)
           envFrom:
             - secretRef:
                 name: backstage-env
@@ -64,6 +67,9 @@ kind: Service
 metadata:
   name: homelab-backstage
   namespace: backstage
+  labels:
+    app: homelab-backstage
+    backstage.io/kubernetes-id: homelab-backstage
 spec:
   type: ClusterIP
   selector:
@@ -79,6 +85,9 @@ kind: Ingress
 metadata:
   name: homelab-backstage
   namespace: backstage
+  labels:
+    app: homelab-backstage
+    backstage.io/kubernetes-id: homelab-backstage
 spec:
   ingressClassName: traefik
   rules:

  1. Label Postgres resources so they also appear under the same Component

Same idea: if Postgres is part of this Backstage “system”, label it with the same kubernetes-id.

kubernetes/postgres.yaml

diff --git a/kubernetes/postgres.yaml b/kubernetes/postgres.yaml
index 58300c6..630621a 100644
--- a/kubernetes/postgres.yaml
+++ b/kubernetes/postgres.yaml
@@ -3,6 +3,9 @@ kind: Service
 metadata:
   name: backstage-postgres
   namespace: backstage
+  labels:
+    app: backstage-postgres
+    backstage.io/kubernetes-id: homelab-backstage
 spec:
   type: ClusterIP
   selector:
@@ -18,6 +21,9 @@ kind: StatefulSet
 metadata:
   name: backstage-postgres
   namespace: backstage
+  labels:
+    app: backstage-postgres
+    backstage.io/kubernetes-id: homelab-backstage
 spec:
   serviceName: backstage-postgres
   replicas: 1
@@ -28,6 +34,7 @@ spec:
     metadata:
       labels:
         app: backstage-postgres
+        backstage.io/kubernetes-id: homelab-backstage
     spec:
       securityContext:
         fsGroup: 999

  1. Include the new RBAC manifest in Kustomize

kubernetes/kustomization.yaml

diff --git a/kubernetes/kustomization.yaml b/kubernetes/kustomization.yaml
index 1057fcd..28ba11a 100644
--- a/kubernetes/kustomization.yaml
+++ b/kubernetes/kustomization.yaml
@@ -6,6 +6,7 @@ namespace: backstage
 resources:
   - postgres.yaml
   - homelab-backstage.yaml
+  - backstage-k8s-rbac.yaml

 generatorOptions:
   disableNameSuffixHash: false

  1. Apply and verify

Apply:

kubectl apply -k kubernetes/
kubectl -n backstage rollout status deploy/homelab-backstage

Verify Backstage can read objects (quick sanity checks):

kubectl -n backstage get sa backstage-k8s-reader
kubectl get clusterrole backstage-k8s-readonly
kubectl get clusterrolebinding backstage-k8s-readonly

Then open Backstage:

  • Catalog → homelab-backstageKubernetes tab

Did this guide save you time?

Support this site

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top