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.

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:
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-backendSoftware Catalog
The Software Catalog gives developers a searchable registry of all services, APIs, documentation, and infrastructure:
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-v1Software Templates
Templates are the mechanism for golden paths — they generate new services from parametrized scaffolding:
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:
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: 30Measuring 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.


