A ready-to-use environment for modern platform engineering experimentation, combining (B)ackstage, (A)rgoCD, (C)rossplane, and (K)yverno! ๐
Interfaces and Capabilities of a Development Platform. Source: CLOUD NATIVE COMPUTING FOUNDATION. Platforms whitepaper. Available at: https://tag-app-delivery.cncf.io/whitepapers/platforms/#capabilities-of-platforms.
๐ง Under construction ๐ง
Platform Engineering requires integrating multiple tools to provide developers with a seamless and efficient experience. Building an Internal Developer Platform (IDP) involves solutions for automation, infrastructure provisioning, access control, observability, and continuous delivery workflows, which makes it challenging for both beginners and experienced teams.
Tools like Backstage, Crossplane, and ArgoCD are commonly used to create a unified developer experience, but experimenting with and understanding how they work together can be hard without a properly configured environment. Each technology brings its own concepts and abstraction layers, making the learning and implementation process fragmented.
This project was created to meet the need for an easy-to-run local environment, enabling quick experimentation with the core technologies involved in platform engineering. The goal is to provide a functional and reproducible stack, reducing initial complexity and enabling hands-on exploration before deploying these solutions in a production setting.
With this stack, you can:
- โ Quickly test integration between Backstage, Crossplane, and ArgoCD.
- โ Simulate an IDP experience in a local setup.
- โ Understand the challenges and benefits of each tool.
- โ Create and modify infrastructure compositions using GitOps.
If you're interested in platform engineering and want to explore how these tools fit together, this repository is a great place to start! ๐
This repository brings together essential tools to build and experiment with a local Internal Developer Platform (IDP). Below is a brief description of each component:
-
Backstage: An open-source developer portal created by Spotify, designed to unify tools, services, and documentation into a single interface. It provides a service catalog, allowing teams to organize and discover APIs, infrastructure, and documentation centrally, promoting standardization and development efficiency.
-
ArgoCD: A GitOps continuous delivery controller for Kubernetes, responsible for managing and syncing applications defined via Git-based manifests.
-
Crossplane: A Kubernetes-native infrastructure provisioning tool that enables declarative cloud and on-prem resource management via Compositions.
-
Kyverno: A policy engine for Kubernetes that enables enforcement and validation of compliance and security rules in clusters.
-
LocalStack: A fully functional local AWS cloud emulator that enables developers to test and build applications interacting with AWS services without needing a real AWS account.
-
Crossview: A web-based UI for visualizing and managing Crossplane resources. It provides an intuitive interface to explore XRDs, Compositions, and composite resources, helping users understand the control plane structure and accelerate troubleshooting.
-
Helm: A package manager for Kubernetes that simplifies deploying complex applications using reusable charts.
-
kind (Kubernetes in Docker): A tool for running local Kubernetes clusters using Docker containers, ideal for testing and development.
-
kubectl: The official Kubernetes command-line tool (CLI) for interacting with clusters, applying configurations, and managing resources.
Make sure the following dependencies are installed before running any commands:
kind(version v0.27.0 or higher);kubectl(version v1.32.2 or higher);argocd(version v2.14.2 or higher);helm(version v3.17.1 or higher);yq(version v4.45.1 or higher);- Docker (version 27.4.0 or higher);
skaffold(version v2.24.0 or higher) โ optional, only needed for the Backstage dev loop described below.
โ ๏ธ Installation of these basic tools is not covered here as it varies by operating system. The following scripts assume you have each one properly set up.
Before proceeding, make sure you have also completed the following steps:
- Fork this repository to your GitHub account.
- Clone your fork locally:
git clone https://github.com/<your-username>/<repository-name>.git cd <repository-name>
- Create a
.envfile at the root of the project based on the provided.env.example:cp .env.example .env
- Generate a GitHub Personal Access Token with full
repopermissions by visiting https://github.com/settings/tokens/new.
- Update the
.envfile with your generated token, replacing the"placeholder"value.
๐ก The GitHub token will be used to open pull requests in the repository and to read the catalog file for loading into Backstage.
This project uses a Makefile to simplify environment setup and teardown. Follow the steps below to get started.
If you don't have the make command in your environment, install it according to your operating system:
Ubuntu/Debian
sudo apt update && sudo apt install -y makeVerify installation:
make --versionInstall build tools if needed:
sudo apt update && sudo apt install -y build-essentialFedora
sudo dnf install -y makeVerify installation:
make --versionInstall build tools if needed:
sudo dnf groupinstall -y "Development Tools"Arch Linux
sudo pacman -Syu makeVerify installation:
make --versionInstall build tools if needed:
sudo pacman -Syu base-develmacOS (via Homebrew)
brew install makeVerify installation:
make --versionInstall Xcode development tools if needed:
xcode-select --installWindows (via MSYS2)
- Download and install MSYS2.
- Open the MSYS2 terminal and run:
pacman -S makeVerify installation:
make --versionFor a complete development environment:
pacman -S base-develTo set up the environment, run:
make upThis command will:
- Check if required dependencies are installed.
- Create a Kubernetes cluster named
platform(if it does not already exist). - Run bootstrap scripts for LocalStack, Crossplane, Crossview, and ArgoCD.
To tear down the environment, run:
make downThis command will:
- Check if required dependencies are installed.
- Delete the Kubernetes
platformcluster if it exists.
make up builds the Backstage image once and then leaves it alone. If you are
editing Backstage itself โ plugins, app-config.yaml, the catalog โ
skaffold gives you a rebuild-and-redeploy
loop against the same cluster:
make dev-backstageIt watches backstage/, rebuilds the image on every change, side-loads it into
the platform kind cluster, rolls the pod, streams the container logs, and holds
the port-forward on http://localhost:3000. Ctrl-C
stops the loop and leaves the last build running.
Two things to know before the first run:
- Commit and push
argocd/apps/backstage/app.yamlfirst. Skaffold deploys the Deployment with a content-addressed image tag, which diffs against thebackstage:latestrecorded in Git. TheignoreDifferencesrule in that file tells Argo CD to leave that one field alone; without itselfHealreverts every redeploy within seconds. Argo CD readsApplicationobjects from Git, so editing the file locally is not enough. - A real sync resets the image.
backstage-appsyncs withReplace=true, so anything you later push under.bootstrap/backstage/manifests/replaces the Deployment wholesale and restoresbackstage:latest. Just re-runmake dev-backstage.
The Skaffold config (skaffold.yaml) deliberately covers Backstage only. Cluster
creation, the argocd CLI bootstrap, secret templating from .env, and the
Crossplane/Kyverno readiness polling have no Skaffold equivalent and stay in
make up and .bootstrap/*/up.sh.
Kargo watches the podinfo image and walks each new tag through nine zones โ three environments spread over three availability zones each, one namespace standing in for each cluster:
warehouse (new podinfo tag)
โ
โผ
dev1-0 canary โโโฌโโโบ dev1-1
โโโโบ dev1-2
โฎ all three dev zones verified
โผ
test1-0 canary โโโฌโโโบ test1-1
โโโโบ test1-2
โฎ all three test zones verified
โผ
prod1-0 canary โโโฌโโโบ prod1-1
โโโโบ prod1-2
Zone 0 of each environment is its canary: Freight lands there first and the two
sibling zones promote in parallel only after it verifies. The next environment
opens only once all three zones of the previous one have verified โ that is
sources.availabilityStrategy: All on the canary Stage; Kargo's default would
let a single verified zone open the gate.
Verification is a real check, not a formality: after each promotion an Argo
Rollouts AnalysisTemplate curls podinfo's /healthz through the Service that
Crossplane composed, three times, in the zone that was just promoted. A failure
stops the rollout there instead of reporting it afterwards.
A promotion does not edit a manifest in main. It renders one and commits the
result to that zone's own branch:
| Branch | Holds |
|---|---|
main |
The Helm chart and the values chain โ the source. |
stage/<zone> (ร9) |
manifests.yaml, fully rendered โ the desired state. |
Each Argo CD Application tracks stage/<zone> rather than a path in main,
so nothing re-renders at sync time and git show stage/prod1-0:manifests.yaml
is an honest answer to "what is running in that zone?". The promotion steps are
in kargo/stages/<zone>.yaml: clone main and the stage branch, git-clear
the branch, helm-template into it, commit, push, then wait for Argo CD to
report the zone healthy before the analysis starts.
delivery/ holds the source, and values are layered narrowest-last:
delivery/
โโโ chart/ # 1. chart defaults โ values.yaml
โโโ envs/
โโโ dev/
โ โโโ values-dev.yaml # 2. environment policy (replicas: 1)
โ โโโ values-dev1-0.yaml # 3. one zone only (identity, overrides)
โ โโโ values-dev1-1.yaml
โ โโโ values-dev1-2.yaml
โโโ test/ # values-test.yaml + values-test1-{0,1,2}.yaml
โโโ prod/ # values-prod.yaml sets replicas: 2
Each file is named for exactly what it configures, so a -f chain reads as the
scope it applies: environment, then zone.
The promoted image tag appears in none of them โ it comes from Kargo through
setValues at render time, so no file in main can claim a version that is not
actually deployed. Render any zone exactly the way the pipeline does with:
helm template podinfo ./delivery/chart --namespace prod1-0 \
-f ./delivery/envs/prod/values-prod.yaml \
-f ./delivery/envs/prod/values-prod1-0.yamlThe nine stage/* branches are seeded by .bootstrap/kargo/up.sh on first run,
which needs GITHUB_TOKEN in .env to have write access to your fork.
Branches that already exist are left alone.
If you encounter issues, ensure that:
- All required binaries are installed and available in your system
PATH. - The
kindclusters are running before applying anykubectlinstructions.
For additional help, refer to the documentation links in the prerequisites section.
Applications are exposed via nohup + kubectl port-forward.
| Application | Address | Notes |
|---|---|---|
| Backstage | http://localhost:3000 | Enter as a Guest User. |
| Argo CD | http://localhost:8080 | Username: admin Password: 12345678 |
| Crossview | http://localhost:3001 | - |
| Kargo | https://localhost:3002 | Username: admin Password: admin Serves TLS with a self-signed cert. |
| Localstack | http://localhost:4566 | Manage it via: https://app.localstack.cloud/instances |
Below is a high-level architecture diagram showing how the components interact:
This section outlines upcoming improvements and planned changes for this project:
-
Reduce the responsibility of the
.bootstrap/**/up.shscripts: shift tool installation and configuration to ArgoCD so that it manages not only Crossplane resources but also the cluster setup itself โ making the environment closer to real-world GitOps practices. -
Improve the Kyverno GitHub Action: update the CI pipeline to apply only the policies related to the resources changed in a given Pull Request.
-
Evaluate the use of the TeraSky Kubernetes Ingestor plugin for Backstage (link): adopted at
v4.0.0, replacing the hand-writtenCrossplaneEntityProvider. It understands Crossplane v2 natively โ it branches on the XRD'sspec.scope, so aNamespacedXRD has its XR ingested directly rather than through the claim v2 no longer has โ and it generates a scaffolder template and an API entity per XRD, so neither has to be hand-maintained. No ingestion loop was observed on this version; documentation is still thin, so two behaviours worth recording:kubernetesIngestor.components.enabled: falsedoes not filter workloads. It short-circuits the whole entity provider and removes everything it had already tracked, which silently stops Crossplane XRs being ingested too. Exclude the platform's own namespaces withcomponents.excludedNamespacesinstead.- The API entities it generates hardcode
system: kubernetes-auto-ingestedโ the value is a string literal in the plugin, not derived frommappings.systemModelโ so thatSystemhas to exist in the catalog or every generated entity reports a dangling relation.
-
Evaluate the TeraSky Crossplane Resources plugin for Backstage (link): adopted, together with the Kyverno policy-reports plugin and RoadieHQ's Argo CD plugin. Its
*Selectorcomponents resolve the v1 or v2 implementation from the entity itself, so the resource table and graph work for legacy claims and v2 namespaced XRs alike. The tabs are gated onisCrossplaneAvailable, so they only appear for Crossplane entities. The dependency on the Kubernetes Ingestor is real โ the ingestor supplies the annotations these components read โ and is satisfied by the item above.
I welcome contributions! ๐
Before starting, please take a moment to review the Contributing Guidelines.
