Skip to main content

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

TemplateNew repo?PR 1 targetPR 2 target
create-domain<domain>-gitopsdomain repo (entity, defaults, CI)platform-gitops (AppProject)
create-systemdomain-gitops (System entity + TechDocs)platform-gitops (ApplicationSet)
create-service<app>-repodomain-gitops (manifests + Component)new app-repo (CI/CD + TechDocs), platform-gitops (AppSet element)
create-resourcedomain-gitops (Claims + namespace)catalog repo (Resource entity)
create-secretdomain-gitops (SealedSecret)(none)
create-groupcatalog repo (Group entity)platform-gitops (RBAC)
create-usercatalog 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
ActionPackageUsed by
fetch:templatebuilt-inall
catalog:fetchbuilt-increate-system, service, resource, user, group
catalog:registerbuilt-inall
publish:github@backstage/plugin-scaffolder-backend-module-githubcreate-domain only
publish:github:pull-request@backstage/plugin-scaffolder-backend-module-githuball
roadiehq:utils:jsonata@roadiehq/scaffolder-backend-module-utilscreate-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