Backstage Templates
The Template Chain
Templates encode the entire platform convention. Developers fill a form — templates generate every YAML, PR, and catalog registration automatically.
People layer (run any time)
──────────────────────────────────────────────────────────────────
create-group → Group entity + RBAC + ArgoCD role instructions
create-user → User entity + RBAC + ArgoCD user instructions
Platform layer (run in order)
──────────────────────────────────────────────────────────────────
create-domain
│ Domain entity, {domain}-gitops repo created, AppProject
▼
create-system
│ System entity, ApplicationSet skeleton (empty)
▼
├── create-service ├── create-resource ├── create-secret
│ Component entity, │ Resource entity, │ SealedSecret/env
│ App Repo (CI/CD, docs) │ Crossplane Claim │
│ k8s manifests/env, │ infra namespace │
│ service → AppSet │ │
▼ ▼
[merge domain PR] [merge domain PR]
[merge platform PR] [merge catalog PR]
What Each Template Produces
| Template | New repo? | PR 1 target | PR 2 target |
|---|---|---|---|
create-domain | ✅ <domain>-gitops | domain repo (entity, defaults, CI) | platform-gitops (AppProject) |
create-system | ❌ | domain-gitops (System entity + TechDocs) | platform-gitops (ApplicationSet) |
create-service | ✅ <app>-repo | domain-gitops (manifests + Component) | new app-repo (CI/CD + TechDocs), platform-gitops (AppSet element) |
create-resource | ❌ | domain-gitops (Claims + namespace) | catalog repo (Resource entity) |
create-secret | ❌ | domain-gitops (SealedSecret) | (none) |
create-group | ❌ | catalog repo (Group entity) | platform-gitops (RBAC) |
create-user | ❌ | catalog repo (User entity) | platform-gitops (RBAC) |
Files Generated Per Template
create-domain — new repo + AppProject PR
{domain}-gitops/
├── README.md ← convention reference + contacts
├── CODEOWNERS
├── catalog-info.yaml ← Backstage Location registration
├── catalog/domain.yaml ← Domain entity
├── k8s/_defaults/
│ ├── resourcequota.yaml ← quota template (sized by profile)
│ └── networkpolicy.yaml ← default isolation policy
└── .github/
├── pull_request_template.md ← PR checklist enforcing convention
└── workflows/validate-conventions.yaml ← CI: kubeconform + naming + label checks
platform-gitops/argocd/projects/{domain}.yaml ← AppProject + RBAC roles + sync windows
create-system — System entity + ApplicationSet
domain-gitops/catalog/systems/{system}.yaml ← System entity
domain-gitops/docs/index.md ← TechDocs base
domain-gitops/mkdocs.yml ← TechDocs config
domain-gitops/catalog-info.yaml ← updated targets list
platform-gitops/argocd/applicationsets/{domain}-{system}.yaml ← AppSet (elements: [])
create-service — k8s manifests + Component + AppSet update
{app-repo}/
Dockerfile ← base build instructions
mkdocs.yml ← TechDocs config
docs/index.md ← TechDocs base
.github/workflows/ci.yaml ← Build and push workflow
domain-gitops/k8s/{env}/{service}/
namespace.yaml ← 9-label set
deployment.yaml ← sized by env + serviceType + resource profile
service.yaml ← ClusterIP (skipped for worker/cronjob)
config.yaml ← ServiceAccount + ConfigMap
policies.yaml ← HPA + PDB (prod) + ResourceQuota + NetworkPolicy
domain-gitops/catalog/components/{system}-{service}.yaml ← Component entity
platform-gitops/argocd/applicationsets/{domain}-{system}.yaml ← service element appended
create-resource — Crossplane Claims + Resource entity
domain-gitops/crossplane/claims/namespace.yaml ← infra namespaces
domain-gitops/crossplane/claims/{env}/{type}-{name}.yaml ← Claim per env
catalog-repo/catalog/resources/{provider}-{domain}-{env}-{type}-{name}.yaml
create-secret — SealedSecrets
domain-gitops/k8s/{env}/{service}/sealedsecret-{name}.yaml ← encrypted secret
create-group — Group entity + RBAC
catalog-repo/groups/{groupName}.yaml ← Group entity
platform-gitops/k8s/rbac/groups/{groupName}.yaml ← RoleBinding per domain/env
platform-gitops/k8s/rbac/groups/{groupName}-argocd-patch.yaml ← AppProject instructions
create-user — User entity + RBAC
catalog-repo/users/{username}.yaml ← User entity
platform-gitops/k8s/rbac/users/{username}.yaml ← RoleBinding per domain/env
platform-gitops/k8s/rbac/users/{username}-argocd-patch.yaml ← AppProject instructions
Catalog Pickers Enforce Referential Integrity
Templates use EntityPicker — you select from what already exists in the catalog. You cannot create a System for a Domain that does not exist:
create-system → picks Domain → domain name + repo URL resolved from entity
create-service → picks System → domain + AppProject + sync policy all derived
create-resource → picks Domain → infra namespace prefix derived
create-user → picks Groups → RBAC scope derived from group membership
Convention Validation in CI
create-domain generates a GitHub Actions workflow for every domain repo:
create-domain generates a GitHub Actions workflow for every domain repo. It enforces rules automatically, blocking PR merges if violations occur:
.github/workflows/validate-conventions.yaml
kubeconform — validates all YAML against Kubernetes API schemas
naming check — runs validate-namespaces.sh (every namespace matches {domain}-{env}-{service})
label check — all 9 required labels present on namespace files
argocd diff — dry-run diff against dev cluster
Template Registration
# platform-gitops/backstage-templates/catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: platform-templates
spec:
targets:
- ./create-domain/template.yaml
- ./create-system/template.yaml
- ./create-service/template.yaml
- ./create-resource/template.yaml
- ./create-secret/template.yaml
- ./create-group/template.yaml
- ./create-user/template.yaml
# app-config.yaml
catalog:
locations:
- type: url
target: https://github.com/myorg/platform-gitops/blob/main/backstage-templates/catalog-info.yaml
rules:
- allow: [Template]
Scaffolder Actions Required
yarn workspace backend add \
@backstage/plugin-scaffolder-backend-module-github \
@roadiehq/scaffolder-backend-module-utils
| Action | Package | Used by |
|---|---|---|
fetch:template | built-in | all |
catalog:fetch | built-in | create-system, service, resource, user, group |
catalog:register | built-in | all |
publish:github | @backstage/plugin-scaffolder-backend-module-github | create-domain only |
publish:github:pull-request | @backstage/plugin-scaffolder-backend-module-github | all |
roadiehq:utils:jsonata | @roadiehq/scaffolder-backend-module-utils | create-system, service, resource, user, group |
Onboarding Workflows — Merge Order
Onboarding a New Team
1. create-group → merge catalog PR → merge platform PR
2. create-user → repeat per member
Onboarding a New Domain
Platform team Domain team
──────────────────────────────────────────────────────
create-domain → merge AppProject PR
create-system → merge both PRs
create-service → merge both PRs (repeat per service)
create-resource → merge both PRs (optional)
create-group → merge both PRs
create-user → repeat per member