Golden Paths & Paved Roads

A golden path is the supported, opinionated route from a developer’s intent to a production-ready service, and a paved road is the set of automated checks that keep services on that route as they evolve.

This sub-section of Developer Experience & Self-Service Platforms covers how to encode that route as a maintainable artifact: a versioned template that scaffolds the right defaults, and a policy layer that continuously verifies a service has not drifted off the supported road. Done well, the golden path becomes the lowest-friction option available, so developers choose compliance because it is genuinely the easiest way to ship.

Golden path template feeding a service that is held in place by paved-road checks A versioned golden-path template scaffolds a service, which is then continuously evaluated by paved-road policy checks that either confirm conformance or open a remediation task. Golden-Path template v2.4.0 Scaffolded service repo Paved-Road policy checks Conformant badge: passing Remediation task opened
The template defines the road; checks keep the service on it, opening remediation when it drifts.

Prerequisites & Environment Baseline

  • Backstage scaffolder: @backstage/plugin-scaffolder-backend@^1.22.0 with at least one registered template location. Golden paths are delivered as scaffolder templates; the Scaffolder Template Design sub-section is the authoritative reference for action sequencing.
  • A policy engine: Open Policy Agent (open-policy-agent/opa@^0.63.0) or Conftest (open-policy-agent/conftest@^0.49.0) for evaluating paved-road rules in CI. Either can run against generated manifests and catalog-info.yaml.
  • CI runners with provider credentials: OIDC-federated identity for ${GITHUB_TOKEN} so checks can read repository contents without a long-lived secret.
  • Catalog ownership data: Every paved-road check that asserts “this service has an owner” needs Group entities resolvable in the catalog.
  • CLI toolchain: @backstage/cli@^0.27.0 and yamllint for local template validation.

Step-by-Step Configuration & Plugin Architecture

1. Define the golden-path template skeleton

Declare a Template whose parameters capture only the decisions a developer must make; everything else is an opinionated default supplied by the platform team.

# templates/microservice.yaml
# Requires scaffolder.backstage.io/v1beta3 (Backstage >= 1.22.0)
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: golden-path-microservice
  title: Golden Path — HTTP Microservice
  description: Supported route for a new Go HTTP service with CI, IaC, and ownership
  tags: [golden-path, microservice, paved-road]
spec:
  owner: group:platform-engineering
  type: service
  parameters:
    - title: Service Identity
      required: [name, owner]
      properties:
        name:
          title: Service name
          type: string
          pattern: "^[a-z][a-z0-9-]{2,38}$"
          ui:autofocus: true
        owner:
          title: Owning team
          type: string
          ui:field: OwnerPicker
          ui:options:
            catalogFilter:
              kind: Group

2. Bind the template to a versioned base module

Keep the opinionated content — Dockerfile, CI workflow, IaC — in a versioned directory so a single bump propagates to all future services without rewriting the template.

  steps:
    - id: fetch-base
      name: Render golden-path base
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.name }}
          owner: ${{ parameters.owner }}
          registry: ${CONTAINER_REGISTRY}

The detailed authoring of this skeleton is covered in Creating a Golden Path Template for Microservices.

3. Encode paved-road rules as policy

Express each standard as an OPA rule that evaluates a generated artifact. Store these rules alongside the template so the road and its checks version together.

# policy/paved_road.rego
# Requires OPA >= 0.63.0
package pavedroad

deny[msg] {
  not input.spec.owner
  msg := "service must declare a spec.owner"
}

deny[msg] {
  input.spec.lifecycle == "production"
  not input.metadata.annotations["backstage.io/techdocs-ref"]
  msg := "production services must publish TechDocs"
}

The full enforcement pipeline — running these in CI and gating merges — is documented in Enforcing Paved-Road Policies with Software Checks.

4. Restrict who can use each path

Scope template visibility through the permission framework so a golden path is offered only to teams that should adopt it, aligning with your Role-Based Access Control Setup.

# app-config.yaml
# Requires @backstage/plugin-permission-backend >= 0.5.0
permission:
  enabled: true

Validation & Health Checks

# Validate the template and dry-run it, then check the output against policy
# Requires @backstage/cli >= 0.27.0, conftest >= 0.49.0
set -euo pipefail
npx @backstage/cli catalog validate --path ./templates/microservice.yaml
# expected: "Validated 1 entity ... 0 errors"
conftest test ./dry-run-output/catalog-info.yaml --policy ./policy
# expected: "PASS - ./dry-run-output/catalog-info.yaml - pavedroad"

Confirm the rendered output contains no unresolved placeholders:

grep -R '\${{' ./dry-run-output && echo "UNRESOLVED PLACEHOLDERS" || echo "clean render"
# expected: "clean render"

Maintenance & Lifecycle Management

  • Upgrade path: Bump the ?ref= on the template location only after the new revision passes the validation pipeline against a sample service.
  • Rollback: Revert the catalog location ref to the last passing tag and restart the backend to clear cached template definitions.
  • Debug commands: Set LOG_LEVEL=debug on the backend to trace fetch:template and publish:* actions when a scaffold misbehaves.
  • Metrics: Track per-template adoption and dry-run failure rate; feed these into Developer Experience Metrics to decide which paths to invest in or retire.

Common Pitfalls & Mitigation Strategies

  • Too many parameters. Root cause: pushing platform decisions onto developers. Fix: default everything that is not a genuine product decision; a golden path should ask three questions, not thirty.
  • Policy that only runs at creation. Root cause: treating the paved road as a one-time gate. Fix: re-run paved-road checks on every pull request and on a schedule so drift is caught continuously.
  • Skeleton coupled to the template. Root cause: opinionated files inlined in the YAML. Fix: keep the skeleton in a versioned directory referenced by fetch:template so it evolves independently.
  • Silent breaking changes. Root cause: editing a published template in place. Fix: tag every template revision and reference it explicitly via ?ref=.

Frequently Asked Questions

How is a golden path different from just having good documentation?

Documentation describes the right way; a golden path executes it. The value is that the supported route is also the path of least resistance — a developer runs the template and receives a working service rather than reading instructions and assembling one by hand. Paved-road checks then keep that service compliant over time, which documentation alone can never enforce.

Should every team be forced onto the golden path?

No. Golden paths work through gravity, not mandate. Make the supported route dramatically easier than the alternatives and most teams adopt it voluntarily; reserve hard enforcement (blocking merges) for the small set of non-negotiable controls such as ownership and security gates. Teams with genuinely unusual requirements should be able to deviate with a documented exception rather than be blocked.

How many golden paths should we maintain?

Start with one per dominant service archetype — typically an HTTP service, a worker/consumer, and a frontend. Each path is a maintained product, so resist proliferation; consolidate variants into parameters of an existing template before creating a new one.