PostgreSQLInstance YAML file is still too much friction. The holy grail of our industry is the Self-Service Portal. Let’s build a Web GUI for Crossplane using Backstage.1. What is Backstage?#
Backstage is an open-source framework created by Spotify. It is used to build Internal Developer Portals (IDPs).
It acts as a centralized catalog for all microservices in a company. More importantly, it features the Software Templates engine.
Software Templates allow Platform Engineers to design beautiful Web UIs (React forms) that capture developer inputs (like “Storage Size” or “Environment”). When the developer clicks “Create”, Backstage executes a CI pipeline in the background.
2. The Backstage -> GitOps -> Crossplane Pipeline#
We are going to stitch together everything we have learned in this entire series into one elegant flow.
The Workflow:
- Developer logs into Backstage Web UI.
- Developer fills out a web form for a new Database.
- Developer clicks “Submit”.
- Backstage automatically generates the
PostgreSQLInstanceYAML Claim and commits it directly to theapp-infrastructureGitHub repository. - ArgoCD detects the new Git commit and applies the YAML to the Kubernetes cluster.
- The Validating Webhook inspects the Claim for compliance.
- Crossplane receives the Claim and executes the Go Composition Function.
- The AWS Provider boots the physical RDS database.
- Crossplane writes the live AWS Endpoint URL back to the Claim Status.
- Backstage reads the Kubernetes API and displays the live DB URL to the developer on the Web UI.
Zero human intervention. Zero terminal commands. Absolute security.
3. Creating the Backstage Template#
To achieve Step 4, we must write a Backstage Template.
A Backstage template is a YAML file that defines a UI form using JSON Schema, and then defines an action (like writing a file to GitHub).
Create a file named template-database.yaml in your Backstage repository:
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: create-postgres-db
title: Provision a PostgreSQL Database
description: Request a compliant RDS instance from the Platform Team.
spec:
owner: platform-engineering
type: service
# 1. The Web UI Form Definition
parameters:
- title: Database Details
required:
- dbName
- storageGB
- environment
properties:
dbName:
title: Database Name
type: string
description: The logical name of your database.
storageGB:
title: Storage Size (GB)
type: integer
minimum: 10
maximum: 500 # Client-side validation!
environment:
title: Target Environment
type: string
enum: ["dev", "staging", "prod"]
# 2. The Actions to execute when "Submit" is clicked
steps:
# Action A: Render the Crossplane YAML file dynamically
- id: render-yaml
name: Render Crossplane Claim
action: fetch:template
input:
# We point it to a skeleton file in our repo (see below)
url: ./skeleton
values:
name: ${{ parameters.dbName }}
storage: ${{ parameters.storageGB }}
env: ${{ parameters.environment }}
# Action B: Push the rendered YAML to the GitOps Repo
- id: publish-github
name: Commit to GitOps Repository
action: publish:github:pull-request
input:
repoUrl: github.com?repo=app-infrastructure&owner=acmecorp
branchName: create-db-${{ parameters.dbName }}
title: "Infrastructure Request: ${{ parameters.dbName }}"
description: "Automated PR generated by Backstage."The Skeleton File#
Inside the ./skeleton directory referenced above, you place the raw Crossplane YAML with Backstage variables injected.
./skeleton/database-claim.yaml:
apiVersion: database.acmecorp.com/v1alpha1
kind: PostgreSQLInstance
metadata:
name: ${{ values.name }}
namespace: ${{ values.env }}
spec:
parameters:
storageGB: ${{ values.storage }}
environment: ${{ values.env }}4. The Developer Experience#
Let’s look at the result.
- The Developer opens Backstage (
https://developer.acmecorp.com). - They click the “Create…” button.
- They see a clean, company-branded form titled “Provision a PostgreSQL Database”.
- They type
auth-service-db,50, and selectdevfrom the dropdown. - They click “Submit”.
A loading bar appears. Behind the scenes, Backstage opens a Pull Request on GitHub. The PR contains the perfectly formatted Crossplane Claim.
The developer’s Tech Lead clicks “Approve” on the GitHub PR.
ArgoCD and Crossplane take over. Five minutes later, the developer receives a Slack notification containing the live connection string for their brand new AWS RDS instance.
Conclusion#
Congratulations. You have reached the pinnacle of Platform Engineering.
In this series, you learned that Crossplane is not just an alternative to Terraform. It is a completely different paradigm.
- You learned how to harness the Kubernetes Reconciliation loop to prevent drift (Ep 1).
- You learned how to build custom APIs using XRDs (Ep 4).
- You learned how to inject global variables using EnvironmentConfigs (Ep 7).
- You escaped the limitations of YAML by writing Go Composition Functions (Ep 11).
- You built a mathematical firewall using Validating Webhooks (Ep 14).
- Finally, you wrapped the entire architecture in a beautiful Web UI using Backstage (Ep 15).
You have transformed your Kubernetes cluster into a Universal Control Plane.
Thank you for following along with the Crossplane learning path!

