Scaffolder Template Design
Scaffolder Template Design serves as the foundational architecture for standardizing service creation across modern developer portals. By defining reusable, parameterized workflows, platform teams can enforce architectural guardrails while accelerating developer onboarding. Effective template design integrates seamlessly with the broader Plugin Ecosystem & Custom Extensions, ensuring that automated provisioning aligns with organizational compliance, infrastructure standards, and team-specific operational requirements.
Prerequisites
Before architecting a new template, verify that your environment meets core operational requirements. Platform engineers must establish a secure, auditable baseline for template execution and entity registration.
- Repository Access & Credentials: Configure provider-specific tokens via environment variables (
GITHUB_TOKEN,GITLAB_TOKEN,BITBUCKET_TOKEN). Store secrets in a centralized vault (e.g., HashiCorp Vault, AWS Secrets Manager) and inject them into the Backstage backend viaapp-config.yaml. - RBAC & Template Visibility: Implement role-based access control using the
@backstage/plugin-permission-backend. Define policies that restrict template visibility to authorized groups (catalog:read,scaffolder:template:use). - Software Catalog Schema Alignment: Ensure your
catalog-info.yamlschema matches the target entity types (Component,API,System). Validate against thebackstage.io/v1alpha1specification. - CI/CD Runner Permissions: Provision OIDC workload identity or IAM roles for execution runners. Runners require
repo:write,actions:write, and infrastructure provisioning permissions (e.g., AWSsts:AssumeRole, GCPiam.serviceAccountTokenCreator) to bootstrap pipelines without manual credential rotation. - CLI Toolchain: Install
@backstage/cli@^0.26.0and ensureyarnornpmis available for local dry-runs and schema validation.
Step-by-Step Configuration
The configuration phase focuses on structuring the YAML definition, defining input parameters, and chaining execution actions. Templates must be deterministic, idempotent, and strictly validated before deployment.
Core YAML Architecture & Parameterization
Start by declaring the apiVersion and kind: Template, followed by a spec block containing parameters and steps. Use JSON Schema validation (required, type, pattern, ui:widget) to enforce type safety and required fields for user inputs.
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: standardized-service-template
title: Standardized Service Template
description: Bootstraps a new service with CI/CD and catalog registration
tags:
- platform
- scaffolder
spec:
owner: platform-engineering
type: service
parameters:
- title: Service Configuration
required:
- repoName
- owner
- targetEnvironment
properties:
repoName:
title: Repository Name
type: string
pattern: "^[a-z0-9-]+$"
ui:autofocus: true
owner:
title: Team Owner (Group)
type: string
ui:field: EntityPicker
ui:options:
allowedKinds:
- Group
targetEnvironment:
title: Deployment Environment
type: string
enum:
- staging
- production
ui:widget: radio
steps:
- id: fetch-base
name: Fetch Base Template
action: fetch:template
input:
url: ./template
values:
repoName: ${{ parameters.repoName }}
environment: ${{ parameters.targetEnvironment }}
githubOrg: ${GITHUB_ORG}
Action Sequencing & Custom Extensions
Chain actions sequentially to handle repository creation, file templating, and initial CI/CD pipeline generation. Each step must output artifacts required by subsequent actions using the output field. When extending default capabilities, consider Building Custom Backstage Plugins to inject proprietary provisioning logic directly into the scaffolder pipeline.
- id: create-repo
name: Create Repository
action: publish:github
input:
repoUrl: ${GITHUB_HOST}?owner=${GITHUB_ORG}&repo=${{ parameters.repoName }}
defaultBranch: main
description: "Auto-provisioned service for ${{ parameters.owner }}"
topics:
- backstage-scaffolded
- ${{ parameters.targetEnvironment }}
protectDefaultBranch: true
- id: register-catalog
name: Register Component
action: catalog:register
input:
repoContentsUrl: ${{ steps['create-repo'].output.repoContentsUrl }}
catalogInfoPath: /catalog-info.yaml
Validation
Rigorous validation prevents deployment failures and ensures catalog consistency. Implement automated checks at both the developer workstation and CI pipeline levels.
Dry-Run Execution & Schema Linting
Implement dry-run execution to verify parameter resolution, action sequencing, and file generation before publishing. Cross-reference the generated catalog-info.yaml against established Catalog Integration Patterns to guarantee seamless entity registration, ownership mapping, and metadata propagation.
# catalog-info.yaml (Injected during scaffolding)
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: ${{ parameters.repoName }}
description: Auto-generated service component
tags:
- scaffolder
- automated
- ${{ parameters.targetEnvironment }}
spec:
type: service
lifecycle: experimental
owner: ${{ parameters.owner }}
system: platform-core
dependsOn:
- resource:platform-db-cluster
CI Pipeline Integration
Integrate automated schema linting and template parsing checks into your CI pipeline to catch structural regressions early.
#!/usr/bin/env bash
set -euo pipefail
# 1. Validate template syntax and JSON schema compliance
backstage-cli template validate --path ./templates/service-template.yaml
# 2. Run dry-run simulation with mock parameters
backstage-cli scaffolder dry-run \
--template ./templates/service-template.yaml \
--output ./dry-run-output \
--values '{"repoName":"test-svc-01","owner":"group:default/platform-team","targetEnvironment":"staging"}'
# 3. Verify generated catalog entity
cat ./dry-run-output/catalog-info.yaml | yq '.spec.type' | grep -q "service" && echo "Catalog schema valid"
Debugging & Deployment Verification
- Log Inspection: Enable
scaffolderdebug logging viaLOG_LEVEL=debugin the Backstage backend. Monitoraction:fetch:templateandaction:publish:*execution traces. - Artifact Inspection: After dry-run, verify
./dry-run-output/contains all expected files. Check for unrendered${{ parameters.* }}placeholders. - Deployment: Commit validated templates to a dedicated Git repository (
git@github.com:org/backstage-templates.git). Configure Backstagecatalog.providers.scaffolderto poll this repository.
Maintenance
Template maintenance requires a structured versioning strategy and backward compatibility planning. Use semantic versioning for template releases and maintain a clear deprecation policy for legacy schemas. Monitor usage metrics to identify underutilized parameters and refactor complex workflows into modular sub-templates. For language-specific implementations, refer to Writing custom scaffolder templates for Node.js services as a reference for structuring dependency resolution, runtime configuration, and automated testing hooks.
Versioning & Rollback Procedures
- Version Control: Tag template directories using Git tags (
v1.2.0). Reference specific versions in Backstage viatemplateRef: github.com?owner=org&repo=backstage-templates&path=templates/service-template&ref=v1.2.0. - Rollback Strategy: If a template update causes pipeline failures:
- Revert the Git tag to the last stable commit.
- Update
app-config.yamlto pointcatalog.providers.scaffolderto the stable ref. - Restart Backstage backend pods to clear cached template definitions.
- Manually clean up orphaned repositories using
gh repo delete <org>/<repo>and remove stale catalog entities via the Backstage API (DELETE /api/catalog/entities/by-uid/<uid>).
Monitoring & Refactoring
Track template execution success rates, average completion time, and parameter usage distribution. Decompose monolithic templates into reusable fetch:template sub-modules. Implement conditional steps using if: ${{ parameters.targetEnvironment === 'production' }} to reduce branching complexity.
Common Pitfalls
| Pitfall | Mitigation Strategy |
|---|---|
| Overloading templates with environment-specific logic | Leverage parameterization, conditional steps, and dynamic file templating (fetch:template with values mapping). |
Failing to validate catalog-info.yaml generation |
Implement CI linting (@backstage/cli template validate) and dry-run assertions to prevent orphaned entities. |
| Hardcoding repository URLs or branch names | Always use ${{ parameters.* }} interpolation and ${ENV_VAR} placeholders for host/org resolution. |
| Neglecting RBAC scoping | Enforce catalog:read and scaffolder:template:use permissions via CASL policies; audit template visibility quarterly. |
| Skipping dry-run validation | Mandate backstage-cli scaffolder dry-run in PR checks before merging template updates. |
FAQ
How do I enforce mandatory security checks within a scaffolder template?
Integrate a fetch:template or custom action:run step that executes security linting (e.g., Trivy, Snyk, npm audit) before the repository is published. Use conditional parameters to block execution if critical vulnerabilities are detected. Example:
- id: security-scan
name: Security Linting
action: run:command
input:
command: npx trivy fs . --severity CRITICAL --exit-code 1
workingDirectory: ${{ steps['fetch-base'].output.workspacePath }}
Can scaffolder templates be versioned independently of the Backstage core?
Yes. Templates are stored as YAML files in a Git repository and can be tagged using semantic versioning. The Backstage catalog can reference specific template versions via templateRef, allowing teams to roll out updates without breaking existing workflows. Use catalog.providers.scaffolder polling intervals to sync new tags automatically.
What is the recommended approach for handling multi-environment deployments in templates?
Define environment-specific parameters (e.g., targetEnvironment, region, clusterName) and use conditional action steps or dynamic file templating to inject environment-appropriate configurations. Avoid hardcoding infrastructure endpoints; instead, resolve them via a centralized configuration service or parameter lookup. Utilize fetch:template with environment-specific directories (./template/staging/, ./template/production/) selected via if conditions in the steps array.