Platform Engineering
14 min readMay 6, 2026

Platform Engineering: Building Golden Paths for Developer Self-Service

A golden path is the supported, paved road for building and deploying software at your organization. It's an opinionated, curated set of tools, templates, and automation that makes the right thing the easy thing — developers get a working service with monitoring, CI/CD, secrets management, and networking configured correctly from day one, without needing to understand Kubernetes.

AJ
Ajeet Yadav
Platform & Cloud Engineer
Platform Engineering: Building Golden Paths for Developer Self-Service

The platform engineering team's job isn't to run Kubernetes — it's to make application teams more productive by abstracting away the complexity of the infrastructure layer. Golden paths are how you do that at scale: instead of every team figuring out how to configure cert-manager, ExternalDNS, HPA, and PodDisruptionBudgets independently, the platform provides a service template that creates all of these correctly with a single pull request.

The alternative — documentation and training — doesn't scale. Teams deviate from best practices under deadline pressure, and the platform team becomes a consultation bottleneck for every new service.


What a Golden Path Covers

A complete golden path for a new backend service should provide:

Service Template creates:
├── Application code scaffold (language-specific, with CI workflow)
├── Container image build pipeline (GitHub Actions + ECR push)
├── Kubernetes manifests:
│   ├── Deployment (with resource requests, probes, PDB)
│   ├── HPA (CPU/custom metrics)
│   ├── Service + Ingress (with cert-manager annotations)
│   ├── ServiceMonitor (Prometheus scraping)
│   └── ExternalSecret (AWS Secrets Manager integration)
├── GitOps registration (Argo CD / Flux application pointing at the new repo)
└── Backstage catalog-info.yaml (service registered in Software Catalog)

A developer fills in: service name, team name, resource requirements, and whether it's public or internal. Everything else is derived from these inputs and organization-wide defaults.


Backstage as the Platform Portal

Backstage (CNCF Graduated, originally from Spotify) provides the Software Catalog and Software Templates that are the foundation of most Internal Developer Platforms:

bash
1# Create a Backstage app
2npx @backstage/create-app@latest
3cd my-backstage-app
4
5# Install Kubernetes plugin
6yarn add --cwd packages/app @backstage/plugin-kubernetes
7yarn add --cwd packages/backend @backstage/plugin-kubernetes-backend

Software Catalog

The Software Catalog gives developers a searchable registry of all services, APIs, documentation, and infrastructure:

yaml
1# catalog-info.yaml — committed to each service repo
2apiVersion: backstage.io/v1alpha1
3kind: Component
4metadata:
5  name: payments-api
6  description: "Handles payment processing for all product lines"
7  annotations:
8    backstage.io/techdocs-ref: dir:.
9    prometheus.io/path: /metrics
10    prometheus.io/port: "9090"
11    # Links to Kubernetes resources
12    backstage.io/kubernetes-namespace: production
13    backstage.io/kubernetes-label-selector: "app=payments-api"
14    # Alert runbook
15    pagerduty.com/integration-key: xxxxx
16  links:
17    - url: https://grafana.internal/d/payments-api
18      title: Grafana Dashboard
19      icon: dashboard
20    - url: https://runbooks.internal/payments-api
21      title: Runbooks
22spec:
23  type: service
24  lifecycle: production
25  owner: group:payments-team
26  system: payments-platform
27  dependsOn:
28    - component:payments-db
29    - resource:payments-s3-bucket
30  providesApis:
31    - payments-api-v1

Software Templates

Templates are the mechanism for golden paths — they generate new services from parametrized scaffolding:

yaml
1# template.yaml — the golden path for a new Go backend service
2apiVersion: scaffolder.backstage.io/v1beta3
3kind: Template
4metadata:
5  name: go-backend-service
6  title: Go Backend Service
7  description: Scaffold a new Go backend service with CI/CD, monitoring, and Kubernetes manifests
8  tags: [go, kubernetes, recommended]
9spec:
10  owner: platform-team
11  type: service
12
13  parameters:
14    - title: Service Information
15      required: [name, team, description]
16      properties:
17        name:
18          title: Service Name
19          type: string
20          pattern: '^[a-z][a-z0-9-]*$'
21          description: "Lowercase, hyphens only (e.g., payments-api)"
22        team:
23          title: Team
24          type: string
25          ui:field: OwnerPicker
26          ui:options:
27            allowedKinds: [Group]
28        description:
29          title: Description
30          type: string
31
32    - title: Resource Configuration
33      properties:
34        cpuRequest:
35          title: CPU Request
36          type: string
37          default: "100m"
38          enum: ["50m", "100m", "200m", "500m", "1000m"]
39        memoryRequest:
40          title: Memory Request
41          type: string
42          default: "128Mi"
43          enum: ["64Mi", "128Mi", "256Mi", "512Mi", "1Gi"]
44        memoryLimit:
45          title: Memory Limit
46          type: string
47          default: "256Mi"
48          enum: ["128Mi", "256Mi", "512Mi", "1Gi", "2Gi"]
49        minReplicas:
50          title: Minimum Replicas
51          type: integer
52          default: 2
53          minimum: 1
54          maximum: 10
55        public:
56          title: Public endpoint (Ingress)
57          type: boolean
58          default: false
59
60  steps:
61    - id: fetch-template
62      name: Fetch Template
63      action: fetch:template
64      input:
65        url: ./skeleton    # Template skeleton files
66        values:
67          name: ${{ parameters.name }}
68          team: ${{ parameters.team }}
69          description: ${{ parameters.description }}
70          cpuRequest: ${{ parameters.cpuRequest }}
71          memoryRequest: ${{ parameters.memoryRequest }}
72          memoryLimit: ${{ parameters.memoryLimit }}
73          minReplicas: ${{ parameters.minReplicas }}
74          public: ${{ parameters.public }}
75
76    - id: publish
77      name: Publish to GitHub
78      action: publish:github
79      input:
80        allowedHosts: ["github.com"]
81        description: ${{ parameters.description }}
82        repoUrl: github.com?owner=my-org&repo=${{ parameters.name }}
83        defaultBranch: main
84        repoVisibility: internal
85
86    - id: register
87      name: Register in Catalog
88      action: catalog:register
89      input:
90        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
91        catalogInfoPath: /catalog-info.yaml
92
93    - id: register-gitops
94      name: Register GitOps Application
95      action: http:backstage:request    # Custom action to create Argo CD Application
96      input:
97        method: POST
98        path: /api/argocd/applications
99        body:
100          name: ${{ parameters.name }}
101          project: ${{ parameters.team }}
102          repoURL: ${{ steps.publish.output.remoteUrl }}
103
104  output:
105    links:
106      - title: Repository
107        url: ${{ steps.publish.output.remoteUrl }}
108      - title: Open in catalog
109        url: ${{ steps.register.output.entityRef }}

Template Skeleton Structure

The template skeleton contains the files Backstage renders for each new service:

skeleton/
├── .github/
│   └── workflows/
│       └── ci.yml           # Build, test, push to ECR
├── cmd/
│   └── server/
│       └── main.go          # Skeleton main.go
├── internal/
│   └── handler/
│       └── handler.go
├── k8s/
│   ├── base/
│   │   ├── deployment.yaml  # Parameterized with ${{ values.name }}, etc.
│   │   ├── service.yaml
│   │   ├── hpa.yaml
│   │   ├── pdb.yaml
│   │   └── kustomization.yaml
│   └── overlays/
│       └── production/
│           └── kustomization.yaml
├── catalog-info.yaml        # Pre-filled with service metadata
├── Dockerfile
├── go.mod
└── README.md

In the manifest templates:

yaml
1# skeleton/k8s/base/deployment.yaml
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5  name: ${{ values.name }}
6  labels:
7    app: ${{ values.name }}
8    team: ${{ values.team }}
9spec:
10  replicas: ${{ values.minReplicas }}
11  selector:
12    matchLabels:
13      app: ${{ values.name }}
14  template:
15    metadata:
16      labels:
17        app: ${{ values.name }}
18        team: ${{ values.team }}
19    spec:
20      containers:
21        - name: ${{ values.name }}
22          image: PLACEHOLDER_IMAGE    # CI pipeline replaces this
23          resources:
24            requests:
25              cpu: ${{ values.cpuRequest }}
26              memory: ${{ values.memoryRequest }}
27            limits:
28              memory: ${{ values.memoryLimit }}
29          readinessProbe:
30            httpGet:
31              path: /health
32              port: 8080
33            initialDelaySeconds: 10
34            periodSeconds: 10
35          livenessProbe:
36            httpGet:
37              path: /health
38              port: 8080
39            initialDelaySeconds: 30
40            periodSeconds: 30

Measuring Platform Adoption

A golden path only succeeds if developers use it. Measure:

  • Adoption rate: % of new services created via template vs. manually (target: > 80%)
  • Time to first deploy: Time from service creation to first production deployment (target: < 1 day)
  • Deviation rate: % of services that have modified the base template configuration significantly (high deviation = template doesn't meet needs)
  • Ticket volume: Support tickets from application teams per month (should decrease as golden paths improve)

Track these in Backstage's TechInsights plugin, which queries the catalog for metadata and computes aggregate metrics.


Frequently Asked Questions

How do I handle teams that need to deviate from the golden path?

Build deviation gates, not walls. The golden path should be the fastest path, but teams should be able to opt out with justification. Create a formal process: teams submit a pull request to the platform repo documenting why they need to deviate (e.g., "needs custom network configuration for regulatory requirements"), the platform team reviews and approves, and the deviation is tracked in the catalog metadata. This creates accountability without blocking legitimate use cases.

Should the platform team own the Kubernetes manifests or application teams?

The golden path template generates the initial manifests — after that, teams own their own Kubernetes manifests in their own repositories. The platform team maintains the template and adds new capabilities to it, but doesn't own running manifests. This is the self-service model: teams are autonomous within the constraints of the template's generated defaults.


For Flux GitOps that manages the applications created by Backstage templates, see Flux CD: GitOps for Kubernetes. For Kyverno policies that enforce the conventions golden paths establish (label requirements, resource limits), see Kubernetes Admission Webhooks: OPA Gatekeeper and Kyverno. For the broader Internal Developer Platform context — Portal layer, workflow orchestration, and the IDP adoption journey — see Platform Engineering: Building an Internal Developer Platform.

Building a developer platform for a growing engineering organization? Talk to us at Coding Protocols — we help platform teams design golden paths and self-service tooling that accelerate developer velocity without sacrificing operational standards.

Related Topics

Platform Engineering
Golden Path
IDP
Backstage
Developer Experience
Kubernetes
DevOps
Self-Service

Read Next