What PlatformOps is
Link to section “What PlatformOps is”In 30 seconds
Link to section “In 30 seconds”PlatformOps is an occasional name for running an internal developer platform as a product; the established name for the field is platform engineering. DORA calls it “a sociotechnical discipline” that builds toolchains and workflows, “often called an ‘Internal Developer Platform’”, offering shared tools, services and “golden paths”.[1] The CNCF white paper defines “a platform for cloud-native computing” as “an integrated collection of capabilities defined and presented according to the needs of the platform’s users”.[2]
This page covers the operating half, which CNCF’s maturity model calls Operations: “running and supporting its capabilities and their features over their whole lifetime”, up to “even deprecation and termination”. It matters because “most of the work to provide a capability is expended after its initial release”.[3]
Reading routes. Beginners: the first two parts, use cases and FAQ. Engineers: reference implementation and labs. Leaders: metrics, decision tree and judgement call. Last verified: 2026-10-17.
At a glance
Link to section “At a glance”| Goal | Self-service capabilities that teams choose, that behave predictably, and that can change and retire safely |
| Users | Application developers first; also data and ML teams, testers, security and finance reviewers |
| In scope | Platform-team charter, golden paths, portals and catalogs, platform APIs, capability service levels, support tiers, shared capacity, upgrades, deprecation, adoption measurement |
| Out of scope (linked) | Delivery culture and metrics, friction measurement, IaC, clusters, reconciliation, landing zones, SLO method: see neighbours |
| Prerequisites | DevOps basics: CI/CD, ownership, delivery metrics |
| Key outputs | Capability catalogue (owners, lifecycle states); golden-path templates; SLOs; support tiers; deprecation calendar; scorecard |
| Term status | Overlapping synonym for platform engineering, with competing vendor meanings (see naming) |
The problem it solves
Link to section “The problem it solves”Without a platform, every product team assembles its own pipeline, runtime, secrets and dashboards. Spotify described a fragmented tooling ecosystem where the only way to learn how to do something was to ask a colleague: “Rumour-driven development”.[4] The CNCF notes that growing autonomy shifted supporting services onto product teams, “forcing them to spend more and more time and cognitive energy on infrastructure concerns”.[2] Building a platform solves that once; operating it solves the problem a year later:
| Without platform operations | With platform operations |
|---|---|
| Templates copied once; every service a different vintage | Conformance checked; upgrades tracked and assisted |
| “Is the platform down or is it me?” asked in chat | Published objective, status signal and support tier per capability |
| Old capabilities live forever | Lifecycle states; deprecations with dates, tooling and staff |
| Platform team works a ticket queue | Requests become self-service; tickets are a defect signal |
| Value argued from anecdotes | Adoption, task success and satisfaction measured with stable questions |
What it is not: naming, boundaries and misconceptions
Link to section “What it is not: naming, boundaries and misconceptions”The label. No standards body defines “PlatformOps”, and none of the CNCF, DORA, Thoughtworks, Team Topologies, Microsoft or Google pages cited here uses it. “Platform Ops” appears mostly in vendor content, with three meanings:
| Source | Meaning given to “Platform Ops” |
|---|---|
| Quali blog, 2020[5] | The whole field: DevOps capabilities turned into products and services; “I believe it was Gartner that coined the term Platform Ops” |
| EchoStor, 2021[6] | The team, quoting Gartner: “the team that delivers, maintains and improves a platform as a service (PaaS)” (2020 report; paywalled, not read) |
| F5 glossary, undated[7] | The team: “the team responsible for curating, maintaining, connecting, and securing the platform” for Dev, DevOps, SecOps and (less) NetOps teams |
| Mia-Platform blog, 2024[8] | The operating side: platform engineering designs the toolchains, while “Platform Ops focuses on maintaining and operating these systems” |
This page follows the last reading: it gives the label a job no other Atlas page does and matches the CNCF model’s Operations aspect. That is an editorial distinction; everything here is platform engineering, and the literature uses that name.
Boundaries. PlatformOps is not:
- A renamed operations or “DevOps” team. Thoughtworks warns against “just renaming existing internal teams “platform teams” while leaving ways of working and organizational structures unchanged”.[9]
- A portal. A portal is one interface; the CNCF says a very simple platform “could be a wiki page with links to standard operating procedures”.[2]
- Running all the infrastructure. “A platform team doesn’t necessarily run compute, network, storage or other services”;[2] it owns interfaces and experience over providers.
- Application operations or SRE. Application teams stay on call for their services (roles); SRE supplies the objective method that PlatformOps applies.
- Mandatory. The CNCF calls platforms “optional and composable”.[2]
Where it comes from and where it sits
Link to section “Where it comes from and where it sits”Origins and evolution
Link to section “Origins and evolution”The practice is older than either name:
| When | Milestone |
|---|---|
| About 2014 | Spotify’s first Golden Path tutorial, from a Hack Week (“six years ago” in 2020).[4] |
| Mid-2010s | Google’s SRE book recommends “a platform of SRE-validated infrastructure, upon which they can build their systems”.[10] |
| 2017–2021 | Thoughtworks Radar: “Platform engineering product teams” at Assess (March 2017), Adopt (April 2021).[9] |
| 2018–2019 | Bottcher defines a digital platform as an internal product;[11] Team Topologies makes the platform team one of four team types.[12][13] |
| 2020–2021 | “Platform Ops” appears in vendor articles.[5][6] |
| March 2022 | Backstage, Spotify’s open-source portal framework, becomes a CNCF incubating project.[14][15] |
| 2023 | CNCF publishes the Platforms White Paper (v1, March) and the Platform Engineering Maturity Model (v1, October).[2][3] |
| 2024–2026 | DORA finds platforms raise productivity but can lower throughput and stability; by 2025, it says, 90% of organisations (in its research) reported using one.[16][1] |
Glossary, mental model and building blocks
Link to section “Glossary, mental model and building blocks”Mental model: a platform is a bundle of promises (“ask for a database, get one in minutes, backed up and monitored”). Platform engineering makes each promise; platform operations keeps it, measures it and retires it honestly.
| Term | Meaning on this page | Example |
|---|---|---|
| Internal developer platform (IDP) | Self-service capabilities, golden paths, documentation and support for internal teams[1] | Database, pipeline and dashboards on request |
| Capability | One self-service offering, with an owner and a lifecycle state | “Managed PostgreSQL, standard tier” |
| Golden path | Spotify’s “opinionated and supported” path to build something; teams may leave it but lose that support[4] (Netflix: “paved road”[11]) | A Python service template with pipeline and dashboards |
| Thinnest viable platform (TVP) | Team Topologies: a platform that provides “just enough capability without unnecessary complexity”[13] | Three templates and a page of procedures |
| Portal, catalog, platform API | A user interface; an inventory of services, APIs and owners; programmatic provisioning | Backstage; a database custom resource |
| Cognitive load | What teams must hold in mind; Team Topologies asks organisations to respect its limits[13] | Ten tools to ship one service |
| Team types | Team Topologies’ four: stream-aligned (a flow of business work), enabling (helps teams overcome obstacles), complicated-subsystem (specialist expertise), platform (an internal product)[13] | Checkout team; coaching team; codec team; platform team |
| Interaction modes | Collaboration (together for a defined period), X-as-a-Service (one provides, one consumes), facilitation (one helps and mentors another)[13] | Collaborate with a design partner in alpha, then X-as-a-Service |
| Day 0, Day 1, Day 2 | Design and build; first rollout; running, upgrading and retiring | The lifecycle |
| Design partner | A product team that agrees to use an alpha capability and give feedback | First two users of a database API |
| Lifecycle state | Proposed, alpha, beta, generally available (GA), deprecated, retired | Backstage suggests experimental, production, deprecated but accepts any value[17] |
| SLI / SLO | Service level indicator, “a carefully defined quantitative measure of some aspect of the level of service”, and objective, its target[18] | 98% of provisioning requests succeed |
| Error budget | The failures an objective allows (100% minus the SLO); overspending it triggers an agreed policy (SRE) | 2% of 2,400 requests = 48 failures |
| Support tier | A response promise for a type of request: hours, first response, route (tiers) | Incident: 15 minutes, page on-call |
| Ticket-ops | DORA’s “vending machine” trap:[1] a team stuck in toil, manual work that “scales linearly as a service grows”[19] | A queue of namespace requests |
How PlatformOps relates to other Ops
Link to section “How PlatformOps relates to other Ops”Diagram source (Mermaid)
flowchart LR INF["InfraOps, CloudOps: tested modules, accounts"] --> PO K8S["KubernetesOps: supported clusters"] --> PO GIT["GitOps: the reconciler"] --> PO SRE["SRE: the SLO method"] --> PO PO["PlatformOps: capabilities with owner, objective, support tier, lifecycle state"] PO --> PT["Product teams: golden paths, support, migration help"] PO <--> DX["DevExOps: friction findings in; fixes and usage data out"] PO --> FIN["FinOps: cost per capability and team"] PO --> INC["IncidentOps: platform incidents"] classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 class PO core class INF,K8S,GIT,SRE,PT,DX,FIN,INC neutral
Text equivalent: Left to right. InfraOps and CloudOps supply tested modules and accounts, KubernetesOps supported clusters, GitOps the reconciler and SRE the SLO method. PlatformOps turns them into capabilities with an owner, objective, support tier and lifecycle state, and hands golden paths and support to product teams, cost data to FinOps and platform incidents to IncidentOps. With DevExOps it trades both ways: friction findings in, fixes and usage data out.
| Neighbour | Shared scope | This page’s focus | Choose the neighbour when |
|---|---|---|---|
| DevOps | Fast, safe delivery | The product that makes DevOps practices cheap to adopt | You need culture, CI/CD or delivery metrics |
| DevExOps | Developer friction | Shipping and running fixes (editorial distinction: DevExOps diagnoses) | You need to measure friction first |
| InfraOps | Provisioning | Wrapping infrastructure as self-service | You are writing IaC modules |
| KubernetesOps | Clusters under the platform | The experience on top | You are upgrading or debugging a cluster |
| CloudOps | Accounts, quotas, guardrails | Hiding them from product teams | You are designing the landing zone |
| ServiceOps | Service ownership | Capabilities as services | You are defining the company’s service model |
| NoOps | Less operations for product teams | Who absorbs the remaining work | You are evaluating the “no ops” claim |
Principles and operating model
Link to section “Principles and operating model”Published principles: what others actually said
Link to section “Published principles: what others actually said”Three short texts summarise the field’s recommendations; none is a standard.
- CNCF Platforms White Paper: seven attributes, from platform as a product to secure by default, and “Users must not be responsible for operating the services offered by the platform.”[2]
- Team Topologies Platform Manifesto: “adoption & engagement over mandates & standards”, among three such preferences, and “superlinear impact with sublinear growth”.[20]
- Evan Bottcher: platforms “must be compelling to use, they cannot stand on a mandate alone”: self-service for most cases, composable, no forced way of working, quick and cheap to start, a rich user community, secure and compliant by default, and “up to date”, the operations clause.[11]
This Atlas’s proposed charter
Link to section “This Atlas’s proposed charter”This charter is this Atlas’s proposal; nobody has standardised one. Mission: make the supported way the easiest way to build and run software, and keep it trustworthy while it is offered.
Principles.
- Every capability has an owner, a lifecycle state, a published objective and a support tier. Nothing is “just there”.
- Promise only what you can operate. A capability without on-call, runbooks, capacity plan and upgrade budget stays in beta.
- Self-service first, tickets as a defect signal. A recurring ticket is a missing feature or document.
- Pull, not push. Adoption is earned; mandates are reserved for security and legal requirements, and explained.
- Run the thinnest layer. Prefer managed services and internal providers; own the interface and the experience.
- Change is a product feature. Upgrades and migrations ship with tooling, documentation and dates.
- Removal is part of the roadmap. At product maturity, says CNCF’s model, “Feature removal is a key part of the conversation”.[3]
Goals (calibrate locally). Coverage rises each quarter; each GA capability meets its objective in 11 of 12 months; no deprecation passes its date with consumers missing from the reconciled count. Non-goals: running application services, approving teams’ changes, owning every tool.
Roles, ownership and RACI
Link to section “Roles, ownership and RACI”Bottcher’s on-call split is the rule: application teams are on call for what they provision and deploy on the platform, and “Platform teams build, deploy, monitor, and are on call for the platform components and underlying platform infrastructure.”[11] Roles: a platform product manager, involved “from the start” as the CNCF advises;[2] platform engineers on a rota; a documentation owner; capability providers; and the stream-aligned teams that use the platform.
| Activity | Platform PM | Platform engineers | Capability provider | Product teams | Security / compliance |
|---|---|---|---|---|---|
| Decide what capability to build next | A | R | C | C | C |
| Define a capability’s SLO and support tier | A | R | C | C | I |
| Operate and be on call for the capability | I | A, R | R (backing service) | I | I |
| Upgrade upstream components | I | A, R | R | I | I |
| Migrate services off a deprecated capability | A (plan, deadline) | R (tooling, help) | I | R (own services) | C |
| Grant an exception to a mandated path | C | C | I | R (request) | A |
R = responsible, A = accountable, C = consulted, I = informed.
Capability lifecycle: Day 0, Day 1, Day 2
Link to section “Capability lifecycle: Day 0, Day 1, Day 2”Capabilities move through states with explicit exit criteria. Google’s AIP-181 shows the habit to copy: a beta component’s deprecation period is “defined at the time of being marked beta”, a stable one’s turn-down process “at the time of being marked stable”.[21]
Diagram source (Mermaid)
stateDiagram-v2 [*] --> Proposed Proposed --> Alpha: design partner agrees Alpha --> Beta: exit criteria met Alpha --> Retired: experiment stopped Beta --> GA: SLO, runbook, support ready Beta --> Retired: cancelled, users moved GA --> Deprecated: successor GA or withdrawal decided Deprecated --> Retired: declared and observed consumers at zero, or deadline Retired --> [*]
Text equivalent: Proposed becomes Alpha when a design partner agrees. Alpha stops (Retired) or meets its exit criteria (Beta). Beta is cancelled once users move (Retired) or becomes GA when SLO, runbook and support are ready. GA becomes Deprecated when a successor is GA or withdrawal is decided, and Retired when declared and observed consumers reach zero or the deadline is enforced.
| State | Day | Promise to users | Exit criteria (to leave the state) |
|---|---|---|---|
| Proposed | 0 | None | Problem validated with at least one team; owner named |
| Alpha | 0–1 | Known users; breaking changes expected | Used in a real delivery flow; feedback recorded |
| Beta | 1 | Open to all; changes with notice | Docs, self-service, telemetry, capacity plan, rollback tested |
| GA | 2 | Published SLO, support tier, versioning policy | Successor GA, or a documented decision to withdraw without replacement |
| Deprecated | 2 | Still operated; no new consumers | Declared and observed consumers at zero (reconciled), or the deadline enforced |
| Retired | — | None | — (data, credentials and access removed) |
Catalog mapping (this page’s convention, used by Lab 2): Backstage accepts any spec.lifecycle value,[17] so record
alpha and beta as experimental, GA as production, deprecated as deprecated; remove the entity on retirement.
In Kubernetes, beta API versions “are no longer served 9 months or 3 minor releases after deprecation (whichever is longer)”, and GA versions “must not be removed within a major version”.[22] Write yours down before users depend on them.
Operating model and governance
Link to section “Operating model and governance”| Ceremony | Cadence | Who | Output |
|---|---|---|---|
| Intake triage | Weekly | PM, tech lead | Feature, document, exception or a reasoned “no” |
| Office hours | Weekly | Platform engineers, users | Unblocked users; recurring questions logged |
| Platform review | Monthly | PM, owners, user representatives | Scorecard reviewed; one improvement chosen |
| Lifecycle review | Quarterly | PM, owners, security, users | Promotions, deprecations, removal dates; what will not be built |
Decision rights. The platform PM decides what is offered and retired; product teams whether to use optional capabilities; security and compliance what is mandatory, and they own exceptions. Funding. CNCF’s model describes a move from a central cost centre to investment “as product”, where chargeback may reflect impact;[3] mechanics: FinOps.
Support tiers (this Atlas’s proposal), published in the charter and the catalog:
| Tier | What counts | Hours | First response | Route |
|---|---|---|---|---|
| Platform incident | A GA capability down or below objective for several teams, or the production deploy path blocked | 24×7 if on the production deploy path; else business hours | 15 minutes | Page platform on-call; IncidentOps process |
| Blocked team | One team cannot ship because of a platform defect | Business hours | 4 business hours | Support channel; rota engineer |
| How-to and requests | Questions, feature requests, exceptions | Business hours | 1 business day | Channel, office hours, intake form |
One engineer a week holds the rota; the others do roadmap work. Track interrupts and pages per shift: a question asked twice becomes documentation; a request that always needs an engineer is a missing feature.
Diagram source (Mermaid)
flowchart TB
R["Support request or alert"] --> Q{{"Which tier?"}}
Q -->|"incident"| P["Page platform on-call"]
Q -->|"blocked team"| B["Rota engineer, 4 business hours"]
Q -->|"how-to, request"| H["Channel, office hours, intake"]
P --> T["Tag the cause: feature, docs, bug or exception"]
B --> T
H --> T
TEL["Telemetry: SLIs, usage, task success, cost"] --> REV
T --> REV["Monthly platform review: scorecard, one improvement"]
REV --> CH["Change: feature, docs, template, quota or deprecation"]
REV -->|"two months below objective, or deadline at risk"| ESC["Engineering leadership, with options"]
CH -->|"effect shows in next month's data"| TEL
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
class B,H,T,REV,CH,TEL core
class P,ESC risk
class Q decision
class R neutral
Text equivalent: A request or alert is routed by tier: incidents page on-call, a blocked team gets the rota engineer within four business hours, questions go to the channel, office hours or intake. Each is tagged by cause (missing feature, missing documentation, bug, exception). Tags and telemetry from portal, core and providers feed the monthly review, which picks a change (its effect shows in next month’s telemetry, closing the loop) or escalates to engineering leadership with options after two months below objective or when a deadline is at risk.
Architecture and workflow
Link to section “Architecture and workflow”Reference architecture
Link to section “Reference architecture”The CNCF describes platforms as a bridge “from underlying capability providers to platform users like application developers”.[2] All three layers emit the telemetry that feeds the operating loop.
Diagram source (Mermaid)
flowchart TB U["Users: developers, data and ML teams, reviewers"] I1["Portal and catalog"] I2["CLI, IDE, APIs"] I3["Templates and docs"] C1["Platform core: orchestration and policy; holds provider credentials (trust boundary)"] P1["Cloud and managed services"] P2["Clusters and runtimes"] P3["CI/CD and registries"] U --> I1 U --> I2 U --> I3 I1 --> C1 I2 --> C1 I3 --> C1 C1 --> P1 C1 --> P2 C1 --> P3 classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef boundary fill:#fbeceb,stroke:#b4332b,stroke-width:2px,stroke-dasharray:6 3,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 class I1,I2,I3 core class C1 boundary class U,P1,P2,P3 neutral
Text equivalent: Top to bottom. Users reach three platform-owned interfaces (portal and catalog; CLI, IDE and APIs; templates and docs), which all call the platform core for orchestration and policy. The core, drawn with a dashed red border, is the trust boundary: it holds credentials for the providers it drives (cloud and managed services, clusters and runtimes, CI/CD and registries).
Trust boundary. The core holds credentials broader than any user’s; Backstage’s Scaffolder “often has elevated permissions to for example create repositories in a GitHub organization”.[23] Shrink the boundary: act with the user’s own credentials, as Artefact 2 does.
Alternatives. Documentation-first suits a few teams; portal-centred suits many teams and services; API-centred (custom resources, for example via Crossplane, CNCF graduated since October 2025[24]) suits organisations that want everything declarative. Mature platforms mix them to meet users “where they are”.[2]
End-to-end workflow: a new service on the golden path
Link to section “End-to-end workflow: a new service on the golden path”The CNCF suggests measuring this key workflow: the “latency to build and deploy a brand new service into production”.[2]
Diagram source (Mermaid)
flowchart TB
A["Developer picks a template in the portal"] --> B["Scaffolder creates repo, pipeline, catalog entry"]
B --> C{"Provisioning succeeds?"}
C -->|"yes"| D["First deploy to staging"]
D --> E["Service registered: owner, SLO, dashboards"]
E --> F["Usage and task-success data feed the backlog"]
C -->|"no"| X["Clear error and runbook link"]
X --> Y{"Can the developer fix it?"}
Y -->|"yes"| B
Y -->|"no"| Z["Support request with run ID"]
Z --> W["Platform fix and review; template or docs updated"]
W --> F
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
class A,B,D,E,F core
class X,Z,W risk
class C,Y decision
Text equivalent: A developer picks a template; the scaffolder creates repository, pipeline and catalog entry. On success the service deploys to staging, is registered with owner, SLO and dashboards, and usage data feed the backlog. On failure the developer sees a clear error and runbook link and retries, or raises a support request with the run ID; the platform team fixes the cause and updates the template or docs, which also feeds the backlog.
| Step | Actor | System | Output | Measured by |
|---|---|---|---|---|
| Choose template | Developer | Portal | Parameters | Template views versus starts |
| Scaffold | Platform automation | Scaffolder, Git host | Repository, pipeline, catalog entry | Provisioning success rate and duration |
| First deploy | Developer, pipeline | CI/CD, runtime | Running service | Time to first deploy |
| Register | Automation | Catalog, observability | Owner, SLO, dashboards | Catalog completeness |
| Exception | Developer, platform on-call | Support channel | Fix and template change | Requests per 100 runs; time to resolution |
DORA found the capability most correlated with a positive user experience is “clear feedback on the outcome of my tasks”;[1] the exception path is part of the product.
Practices, frameworks and tools
Link to section “Practices, frameworks and tools”Core practices and playbooks
Link to section “Core practices and playbooks”Playbook 1 (capability owner, from beta): give a capability an objective.
- Measure the user’s journey, not a server: provisioning success and duration, queue time, deploy success.
- Set the target from user needs, not “a target based on current performance”.[18]
- Offer tiers when needs conflict; the SRE book’s “one way” is offering infrastructure “at multiple independent levels of service”, such as low-latency and throughput Bigtable clusters.[25]
- Test that consumers survive the objective: “Users build on the reality of what you offer, rather than what you say you’ll supply”.[18] If you beat it widely for months, raise it or inject failures (use case 2).
- Publish objective, support tier and status signal in the catalog (SRE teaches the method).
Playbook 2 (platform engineers): run the upgrade treadmill. Keep a calendar of every upstream end of support. As of October 2026, Kubernetes maintains the three newest minor releases, each with about a year of patches,[26] and Backstage releases monthly; “Each new minor version can contain new functionality, breaking changes, and bug fixes”.[27] Roll out in stages (platform environments, volunteer teams, everyone), testing at each.
Playbook 3 (platform PM): deprecate with staff, not hope. Software Engineering at Google separates advisory deprecations (no deadline) from compulsory ones: “Deprecation warnings can help prevent new uses, but rarely lead to migration of existing systems”, and compulsory ones should be “actively staffed by a specialized team through completion”, or teams see an “unfunded mandate”.[28]
Diagram source (Mermaid)
flowchart TB
A["Successor GA or withdrawal decided; marked deprecated, new use blocked"] --> C["Reconcile declared consumers with callers in access logs"]
C --> D{{"Undeclared or silent callers?"}}
D -->|"yes"| E["Contact their owners; add them to the burn-down"]
D -->|"no"| F["Notices, automated pull requests, staffed migrations"]
E --> F
F --> G{{"Actual pace at least the required pace?"}}
G -->|"no"| H["Escalate: add staff, or move the date openly"]
G -->|"yes"| J{{"Declared and observed consumers at zero?"}}
H --> J
J -->|"no: repeat"| C
J -->|"yes, after an optional brown-out"| K["Remove the capability, its data, credentials and access"]
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
class A,C,F,K core
class E,H risk
class D,G,J decision
Text equivalent: With the successor GA (or withdrawal decided) and new use blocked, reconcile declared consumers with callers in the access logs, and add undeclared or silent callers to the burn-down after contacting their owners. Send notices, open automated pull requests, staff the migrations. At each review, escalate if the pace is too slow: add staff or move the date openly. Remove the capability, data, credentials and access only when declared and observed consumers are zero, optionally after a brown-out; otherwise reconcile again.
Notices must be actionable and relevant, the book’s two tests;[28] the platform team migrates the hardest consumers itself, and the pace arithmetic is in the worked calculation.
Playbook 4 (template owners): track conformance. Spotify’s “Golden State” is “a list of checks that engineers can use to know
if their systems are following the Golden Path”;[4] Backstage’s backstage.io/source-template annotation helps “to track
adherence to software standards across the Catalog”.[29] Lab 2 builds the report;
keep the records current.
Methods, standards and frameworks
Link to section “Methods, standards and frameworks”| Source | Kind | Use it for |
|---|---|---|
| CNCF Platforms White Paper[2] | Community recommendation | Definitions, attributes, capability domains, metrics |
| CNCF Platform Engineering Maturity Model[3] | Community model | Self-assessment: investment, adoption, interfaces, operations, measurement |
| Microsoft Platform Engineering Capability Model[30] | Vendor model aligned to CNCF | Six capabilities: CNCF’s aspects except operations, plus governance, and provisioning and management |
| Team Topologies[13] | Organisational design | Team types, interaction modes, TVP |
| DORA platform engineering capability[1] | Research-based guidance | Pitfalls, balanced measurement, J-curve |
| Google SRE book[18] | Practitioner method | SLOs, error budgets |
| Kubernetes deprecation policy[22], AIP-181[21], Semantic Versioning[31] | Normative for their own projects; models for yours | Versioning and deprecation rules |
None is mandatory; regulation may mandate controls inside capabilities, not the platform model.
Tooling landscape
Link to section “Tooling landscape”Choose tools by job; the examples come from the CNCF white paper.[2]
| Job | Examples named by CNCF | Operating concern |
|---|---|---|
| Portal and catalog | Backstage (CNCF incubating since 2022-03-15)[15] | Upgrades; plug-ins; catalog freshness |
| Provisioning APIs | Kubernetes, Crossplane, Operator Framework, Helm, KubeVela | API versioning; drift; controller credentials |
| Build and test automation | Tekton, Jenkins, Buildpacks | Runner capacity and isolation |
| Delivery and verification | Argo, Flux, Flagger, OpenFeature | Reconciliation → GitOps |
| Observability | OpenTelemetry, Prometheus, Grafana, OpenCost | Platform SLIs and cost signals |
| Identity and secrets | Keycloak, External Secrets, SPIFFE/SPIRE, cert-manager | Rotation; scope of platform credentials |
| Policy and security | OPA, Kyverno, Falco | False positives that block users |
Assemble, rent or buy (as of October 2026). The examples illustrate four kinds of option, not a ranking; trial them on your own journeys.
| Option | Examples | You still own |
|---|---|---|
| Open-source framework you run | Backstage, “an open source framework for building developer portals”[14] | Hosting, plug-ins, upgrades, all below |
| Backstage from a vendor | Roadie (“a SaaS version of Backstage”);[32] Harmonix (“an AWS Partner solution based on Backstage.io”)[33] | Catalog data, templates, integrations |
| Commercial portal products | OpsLevel, an “Internal Developer Portal”[34] | Catalog data, templates, an exit plan |
| Cloud-provider services | AWS Service Catalog, for “catalogs of IT services that are approved for AWS”;[35] Google Cloud App Design Center, where platform teams share templates[36] | Templates, guardrails, moving off when the provider changes course |
Selection criteria. Who operates it, at what upgrade cadence? Does it have an API, not just a UI? Can you leave it? AWS Proton, “primarily a CI/CD tool for deploying infrastructure”, lost support on 2026-10-07: sign-ups closed a year ahead and deployed stacks kept running, but every delivery pipeline built on it had to move.[33] Microsoft retires Azure Deployment Environments on 2027-02-22.[37]
Automation and AI
Link to section “Automation and AI”Automation is the platform: each self-service capability replaces a manual step. In DORA’s 2025 research, “When platform quality is high, the effect of AI adoption on organizational performance becomes strong and positive”; with low quality it “is negligible”.[1] More generated code loads runners and deploy paths. Use AI where the platform checks the output (draft migration pull requests, support triage), never to approve exceptions or run privileged actions unreviewed.
PlatformOps in practice
Link to section “PlatformOps in practice”Reference implementation
Link to section “Reference implementation”Assumptions. A mid-sized organisation with Backstage (v1.55 was stable when checked), a GitHub organisation, Kubernetes
runtimes behind a deploy API, and Prometheus. example-org,
names ending in example.com and the platform.example.com/... annotations are placeholders and this page’s conventions, not
Backstage features.
Artefact 1: capabilities as catalog entities. Backstage requires spec.lifecycle on components and APIs.[17] Modelling
each platform API as an entity makes owners, consumers and deprecations queryable.
Example status: YAML syntax-checked, not loaded into a running Backstage instance.
# catalog/platform.yaml: platform capabilities as catalog entities
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
name: internal-platform
description: Self-service capabilities run by the platform team
spec:
owner: group:default/platform
---
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: deploy-api-v2
description: Deploy a container image to a managed runtime
annotations:
platform.example.com/slo: "99% of deploy requests succeed over 28 days"
platform.example.com/support: "https://chat.example.com/platform-help"
spec:
type: openapi
lifecycle: production
owner: group:default/platform
system: internal-platform
definition:
$text: ./openapi/deploy-v2.yaml
---
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: deploy-api-v1
description: "Deprecated: use deploy-api-v2. Removal on 2027-03-31."
annotations:
platform.example.com/removal-date: "2027-03-31"
platform.example.com/replacement: api:default/deploy-api-v2
spec:
type: openapi
lifecycle: deprecated
owner: group:default/platform
system: internal-platform
definition:
$text: ./openapi/deploy-v1.yaml
Consumers declare spec.consumesApis in their own catalog-info.yaml, creating consumesApi relations;[17] that is a
claim, not proof (is the catalog telling the truth?). The owner field “is not to be used by
automated processes to for example assign authorization in runtime systems”.[17]
Artefact 2: a golden-path template. A Software Template (scaffolder.backstage.io/v1beta3) renders a skeleton, creates a
repository with the user’s own GitHub token and registers it.[38] backstage.io/time-saved is a documented annotation (an
ISO 8601 duration) for estimating time saved.[17] The last step uses failure(), which runs a step only when an earlier one
failed.[38]
Example status: YAML syntax-checked, not executed (needs a Backstage instance with GitHub sign-in and a GitHub integration).
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: python-service
title: Python service (golden path)
description: Repository, pipeline, deploy wiring, dashboards, catalog entry
tags: [recommended, python]
annotations:
backstage.io/time-saved: PT4H
spec:
owner: group:default/platform
type: service
parameters:
- title: Service details
required: [name, owner]
properties:
name:
title: Name
type: string
pattern: '^[a-z][a-z0-9-]{2,30}$'
owner:
title: Owning team
type: string
ui:field: OwnerPicker
ui:options:
catalogFilter:
kind: Group
- title: Repository
required: [repoUrl]
properties:
repoUrl:
title: Repository location
type: string
ui:field: RepoUrlPicker
ui:options:
allowedHosts:
- github.com
allowedOwners:
- example-org
requestUserCredentials:
secretsKey: USER_OAUTH_TOKEN
steps:
- id: fetch
name: Render skeleton
action: fetch:template
input:
url: ./skeleton
values:
name: ${{ parameters.name }}
owner: ${{ parameters.owner }}
templateVersion: '3.2.0'
- id: publish
name: Create repository
action: publish:github
input:
repoUrl: ${{ parameters.repoUrl }}
token: ${{ secrets.USER_OAUTH_TOKEN }}
repoVisibility: private
defaultBranch: main
- id: register
name: Register in catalog
action: catalog:register
input:
repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
catalogInfoPath: /catalog-info.yaml
- id: explain-failure
name: Tell the user what to do next
action: debug:log
if: ${{ failure() }}
input:
message: >-
Scaffolding failed. Post this page's URL in the platform-help channel
and the platform team will follow up.
output:
links:
- title: Repository
url: ${{ steps.publish.output.remoteUrl }}
- title: Open in catalog
icon: catalog
entityRef: ${{ steps.register.output.entityRef }}
Who can do what.
- Runs it: “By default all internal users are allowed to execute templates in the scaffolder”; a permission policy can limit who creates tasks and which actions they may execute.[23][39]
- Edits it: only reviewers of the template repository; the threat model says to limit create and update “to trusted parties”.[23]
- Creates the repository: the user’s OAuth token (
requestUserCredentials,token), so users create repositories only where GitHub lets them, and only inexample-org(allowedOwners). This needs GitHub sign-in wired to Backstage’s SCM auth;[38] the backend’s own integration can then be read-only.[23] - Registers it:
catalog:registercalls the catalog with the initiating user’s credentials, so the user’s catalog permissions apply.[40]
The skeleton’s catalog-info.yaml records the service’s origin for conformance reports; upgrade pull requests must bump its
template-version.
Example status: YAML syntax-checked as a file; it contains ${{ values.* }} placeholders that fetch:template fills in.
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: ${{ values.name }}
annotations:
backstage.io/source-template: template:default/python-service
platform.example.com/template-version: "${{ values.templateVersion }}"
spec:
type: service
lifecycle: production
owner: ${{ values.owner }}
consumesApis:
- api:default/deploy-api-v2
Artefact 3: SLI rules for a capability. Recording rules precompute expressions, alerting rules fire on them, and promtool check rules validates both.[41][42] Metric names are placeholders the provisioning service must emit; the 98%
objective is illustrative.
Example status: YAML syntax-checked; all five PromQL expressions parsed without error by the public Prometheus demo server
(v3.13.0 on 2026-10-16; re-checked after reformatting on 2026-10-17); not checked with promtool and not run against real metrics.
groups:
- name: platform-capability-slis
interval: 1m
rules:
- record: capability:platform_provision_success:ratio_rate1h
expr: |
sum by (capability) (
rate(platform_provision_requests_total{outcome="success"}[1h])
)
/
sum by (capability) (rate(platform_provision_requests_total[1h]))
- record: capability:platform_provision_duration_seconds:p95_1h
expr: |
histogram_quantile(0.95, sum by (capability, le) (
rate(platform_provision_duration_seconds_bucket[1h])
))
- alert: PlatformProvisioningBelowObjective
expr: |
capability:platform_provision_success:ratio_rate1h < 0.98
and on (capability)
sum by (capability) (
increase(platform_provision_requests_total[1h])
) >= 20
for: 30m
labels:
severity: ticket
team: platform
annotations:
summary: >-
Provisioning success for {{ $labels.capability }} is below 98%
over the last hour
runbook_url: https://runbooks.example.com/platform/provisioning
- alert: PlatformProvisioningNoRequests
expr: |
sum by (capability) (
increase(platform_provision_requests_total[6h])
) == 0
for: 30m
labels:
severity: ticket
team: platform
annotations:
summary: >-
No provisioning requests for {{ $labels.capability }} in 6 hours;
check the service
- alert: PlatformProvisioningMetricsMissing
expr: absent(platform_provision_requests_total)
for: 15m
labels:
severity: ticket
team: platform
annotations:
summary: >-
No provisioning metrics at all; the service or its scrape target
is down
Low-volume capabilities need guards: at 10 requests an hour one failure reads as 90%, hence the minimum volume; with no traffic
the ratio has no value, hence rules for six quiet hours (tune them) and missing series (absent()).[43] Burn-rate
alerts are better (SRE).
Hands-on labs
Link to section “Hands-on labs”Lab 1 needs only a document. Lab 2’s Python steps were executed (Python 3.8.10; last run 2026-10-17) and outputs are copied from that run. Lab 3 needs a local Backstage and was not executed.
Lab 1 (beginner): choose a thinnest viable platform
Link to section “Lab 1 (beginner): choose a thinnest viable platform”Objective: pick the first golden path and write the promise you will operate. Prerequisites: three to five product teams; 60–90 minutes; no cost or risk.
- With each team, list critical user journeys, such as DORA’s “spinning up a new service” or “debugging a production issue”.[1]
- Score each journey 1–5 for frequency, current pain and sameness across teams; multiply the three.
- Take the highest score and check whether a managed service covers it; the CNCF suggests starting with capabilities “frequently required and undifferentiated, like pipelines, databases and observability”.[2]
- Write the promise: what users get, how fast, how reliably (draft SLO), support tier, what happens on failure.
- Name a design partner and the alpha and beta exit criteria (lifecycle).
- List three things you will not build this quarter.
Expected output: a one-page statement (charter template). Validation: the design partner agrees the journey is real; the promise has a number, an owner and a support tier; the “not” list is not empty. Troubleshooting: if all journeys score alike, re-interview recent doers. Teardown: none.
Lab 2 (intermediate): a conformance and deprecation report from the catalog
Link to section “Lab 2 (intermediate): a conformance and deprecation report from the catalog”Objective: measure template-recorded coverage, currency and exposure to a deprecated capability, check declared consumers against an access log, and list actions per team. Prerequisites: Python 3.8+; no network, cost or risk. Time: 40 minutes.
- Save the script as
platform_health.py. - Save the sample catalog export (shaped like the catalog API’s
itemslist) ascatalog.jsonand the policy asgolden-paths.json. - Run
python3 platform_health.py catalog.json golden-paths.json 2026-10-16and thenecho $?. - Edit
catalog.json: setsearchto template version3.2.0andaccount-webto2.1.0. Run step 3 again. - Run once more with the date
2027-01-15instead of2026-10-16. - Save the second script as
reconcile_consumers.pyand the access-log summary asaccess-summary.csv, then runpython3 reconcile_consumers.py catalog.json access-summary.csv api:default/deploy-api-v1andecho $?. - Optional: export a real catalog with the script below, then repeat steps 3 and 6.
Example status: both Python scripts executed with the JSON and CSV inputs below (Python 3.8.10, 2026-10-17).
"""Platform health report from a Backstage catalog export (Python 3.8+).
Usage: python3 platform_health.py catalog.json golden-paths.json [YYYY-MM-DD]
Exit code 0 = healthy; 1 = action needed (an unsupported template version,
or a removal date within the warning window).
"""
import json
import sys
from collections import defaultdict
from datetime import date
TEMPLATE = "backstage.io/source-template" # well-known Backstage annotation
VERSION = "platform.example.com/template-version" # this page's convention
REMOVAL = "platform.example.com/removal-date" # this page's convention
WARN_DAYS = 90
def ref(entity):
meta = entity["metadata"]
kind = entity["kind"].lower()
return "{}:{}/{}".format(kind, meta.get("namespace", "default"),
meta["name"])
def full_ref(short, kind):
if ":" not in short:
short = kind + ":" + short
if "/" not in short:
k, name = short.split(":", 1)
short = k + ":default/" + name
return short
def consumes(entity, api_ref):
apis = entity["spec"].get("consumesApis", [])
return any(full_ref(a, "api") == api_ref for a in apis)
def main(catalog_file, policy_file, today):
items = json.load(open(catalog_file))["items"]
paths = json.load(open(policy_file))["golden_paths"]
deprecated = {ref(e): e for e in items if e["kind"] == "API"
and e["spec"].get("lifecycle") == "deprecated"}
services = [e for e in items if e["kind"] == "Component"
and e["spec"].get("lifecycle") == "production"]
on_path, current, from_template, urgent = 0, 0, 0, False
actions = defaultdict(list)
for s in services:
ann = s["metadata"].get("annotations", {})
owner, name = s["spec"]["owner"], s["metadata"]["name"]
tpl, ver = ann.get(TEMPLATE), ann.get(VERSION, "0.0.0")
if tpl in paths:
from_template += 1
if int(ver.split(".")[0]) in paths[tpl]["supported_majors"]:
on_path += 1
current += ver == paths[tpl]["current"]
else:
urgent = True
actions[owner].append(
"{}: {} {} is unsupported; upgrade to {}".format(
name, tpl.split("/")[-1], ver,
paths[tpl]["current"]))
for dref, dep in deprecated.items():
if consumes(s, dref):
removal = date.fromisoformat(
dep["metadata"]["annotations"][REMOVAL])
days = (removal - today).days
urgent = urgent or days <= WARN_DAYS
actions[owner].append(
"{}: uses deprecated {} (removal {}, {} days)".format(
name, dep["metadata"]["name"], removal, days))
n = len(services)
print("Production services: {}".format(n))
print("Template-recorded coverage (supported version): "
"{}/{} = {:.1%}".format(on_path, n, on_path / n))
print("On current template version: {}/{} template-created = "
"{:.1%}".format(current, from_template, current / from_template))
for dref, dep in sorted(deprecated.items()):
removal = date.fromisoformat(dep["metadata"]["annotations"][REMOVAL])
users = [s for s in services if consumes(s, dref)]
weeks = (removal - today).days / 7
pace = len(users) / weeks if weeks > 0 else float("inf")
print("{}: {} declared consumers, {:.1f} weeks left, needed pace "
"{:.2f}/week".format(dep["metadata"]["name"], len(users),
weeks, pace))
print("Actions by owner:")
for owner in sorted(actions):
print(" " + owner)
for line in actions[owner]:
print(" " + line)
return 1 if urgent else 0
if __name__ == "__main__":
day = (date.fromisoformat(sys.argv[3]) if len(sys.argv) > 3
else date.today())
sys.exit(main(sys.argv[1], sys.argv[2], day))
{"items": [
{"kind": "API", "metadata": {"name": "deploy-api-v1", "annotations": {"platform.example.com/removal-date": "2027-03-31"}}, "spec": {"type": "openapi", "lifecycle": "deprecated", "owner": "group:default/platform"}},
{"kind": "API", "metadata": {"name": "deploy-api-v2"}, "spec": {"type": "openapi", "lifecycle": "production", "owner": "group:default/platform"}},
{"kind": "Component", "metadata": {"name": "checkout", "annotations": {"backstage.io/source-template": "template:default/python-service", "platform.example.com/template-version": "3.2.0"}}, "spec": {"type": "service", "lifecycle": "production", "owner": "group:default/payments", "consumesApis": ["api:default/deploy-api-v2"]}},
{"kind": "Component", "metadata": {"name": "payments-ledger", "annotations": {"backstage.io/source-template": "template:default/python-service", "platform.example.com/template-version": "3.1.4"}}, "spec": {"type": "service", "lifecycle": "production", "owner": "group:default/payments", "consumesApis": ["api:default/deploy-api-v2"]}},
{"kind": "Component", "metadata": {"name": "pricing", "annotations": {"backstage.io/source-template": "template:default/python-service", "platform.example.com/template-version": "3.0.0"}}, "spec": {"type": "service", "lifecycle": "production", "owner": "group:default/payments", "consumesApis": ["api:default/deploy-api-v2"]}},
{"kind": "Component", "metadata": {"name": "search", "annotations": {"backstage.io/source-template": "template:default/python-service", "platform.example.com/template-version": "2.8.0"}}, "spec": {"type": "service", "lifecycle": "production", "owner": "group:default/discovery", "consumesApis": ["api:default/deploy-api-v1"]}},
{"kind": "Component", "metadata": {"name": "catalog-web", "annotations": {"backstage.io/source-template": "template:default/web-frontend", "platform.example.com/template-version": "2.1.0"}}, "spec": {"type": "service", "lifecycle": "production", "owner": "group:default/discovery", "consumesApis": ["api:default/deploy-api-v2"]}},
{"kind": "Component", "metadata": {"name": "account-web", "annotations": {"backstage.io/source-template": "template:default/web-frontend", "platform.example.com/template-version": "1.9.0"}}, "spec": {"type": "service", "lifecycle": "production", "owner": "group:default/identity", "consumesApis": ["api:default/deploy-api-v1"]}},
{"kind": "Component", "metadata": {"name": "notifications", "annotations": {"backstage.io/source-template": "template:default/python-service", "platform.example.com/template-version": "3.2.0"}}, "spec": {"type": "service", "lifecycle": "production", "owner": "group:default/identity", "consumesApis": ["api:default/deploy-api-v1"]}},
{"kind": "Component", "metadata": {"name": "recommendations"}, "spec": {"type": "service", "lifecycle": "production", "owner": "group:default/discovery", "consumesApis": ["api:default/deploy-api-v1"]}},
{"kind": "Component", "metadata": {"name": "legacy-reports"}, "spec": {"type": "service", "lifecycle": "production", "owner": "group:default/finance-it", "consumesApis": ["api:default/deploy-api-v1"]}},
{"kind": "Component", "metadata": {"name": "feature-store"}, "spec": {"type": "service", "lifecycle": "experimental", "owner": "group:default/data", "consumesApis": ["api:default/deploy-api-v2"]}}
]}
{"golden_paths": {
"template:default/python-service": {
"current": "3.2.0", "supported_majors": [3]},
"template:default/web-frontend": {
"current": "2.1.0", "supported_majors": [2]}
}}
Expected output of step 3 (exit code 1, because two services use unsupported template versions):
Production services: 9
Template-recorded coverage (supported version): 5/9 = 55.6%
On current template version: 3/7 template-created = 42.9%
deploy-api-v1: 5 declared consumers, 23.7 weeks left, needed pace 0.21/week
Actions by owner:
group:default/discovery
search: python-service 2.8.0 is unsupported; upgrade to 3.2.0
search: uses deprecated deploy-api-v1 (removal 2027-03-31, 166 days)
recommendations: uses deprecated deploy-api-v1 (removal 2027-03-31, 166 days)
group:default/finance-it
legacy-reports: uses deprecated deploy-api-v1 (removal 2027-03-31, 166 days)
group:default/identity
account-web: web-frontend 1.9.0 is unsupported; upgrade to 2.1.0
account-web: uses deprecated deploy-api-v1 (removal 2027-03-31, 166 days)
notifications: uses deprecated deploy-api-v1 (removal 2027-03-31, 166 days)
Validation. Step 4: coverage 7/9 = 77.8%, currency 5/7 template-created = 71.4%, no unsupported-version lines, exit code
0 (removal is 166 days away, outside the 90-day window). Step 5: 75 days, pace 0.47/week, exit code 1. The experimental
feature-store is never counted.
Step 6: are the declared consumers the real ones? The second script compares consumesApis with 30 days of callers from the
access log or API gateway, keyed by workload identity (here, the component name).
"""Compare declared API consumers with callers seen in an access-log summary.
Usage: python3 reconcile_consumers.py catalog.json access-summary.csv API_REF
Exit code 0 = every caller is declared; 1 = undeclared callers found.
"""
import csv
import json
import sys
def full_ref(short):
if ":" not in short:
short = "api:" + short
if "/" not in short:
kind, name = short.split(":", 1)
short = kind + ":default/" + name
return short
def main(catalog_file, log_file, api):
api = full_ref(api)
items = json.load(open(catalog_file))["items"]
components = {e["metadata"]["name"]: e
for e in items if e["kind"] == "Component"}
declared = {name for name, e in components.items()
if any(full_ref(a) == api
for a in e["spec"].get("consumesApis", []))}
observed = {}
with open(log_file, newline="") as f:
for row in csv.DictReader(f):
calls = int(row["requests_30d"])
if full_ref(row["api"]) == api and calls > 0:
observed[row["caller"]] = calls
print("{}: {} declared, {} seen in the log".format(
api, len(declared), len(observed)))
for name in sorted(declared - set(observed)):
print(" declared, no calls in 30 days: {} (confirm before removal)"
.format(name))
undeclared = sorted(set(observed) - declared)
for name in undeclared:
where = (components[name]["spec"]["owner"] if name in components
else "not in catalog")
print(" calls but not declared: {} ({} requests; {})".format(
name, observed[name], where))
print("Consumers to migrate (declared or seen): {}".format(
len(declared | set(observed))))
return 1 if undeclared else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1], sys.argv[2], sys.argv[3]))
caller,api,requests_30d
search,deploy-api-v1,412
recommendations,deploy-api-v1,230
account-web,deploy-api-v1,96
notifications,deploy-api-v1,51
pricing,deploy-api-v1,12
batch-exporter,deploy-api-v1,3
checkout,deploy-api-v2,388
pricing,deploy-api-v2,140
Expected output of step 6 (exit code 1):
api:default/deploy-api-v1: 5 declared, 6 seen in the log
declared, no calls in 30 days: legacy-reports (confirm before removal)
calls but not declared: batch-exporter (3 requests; not in catalog)
calls but not declared: pricing (12 requests; group:default/payments)
Consumers to migrate (declared or seen): 7
Validation. Seven services must migrate, not five: pricing declares only the new API but still calls the old one, and
batch-exporter is not in the catalog. legacy-reports made no calls in 30 days; it may be a quarterly job, so ask its owner.
Troubleshooting. KeyError: 'platform.example.com/removal-date': a deprecated API lacks a date. ZeroDivisionError: no
production or template-created components. Missing consumers may sit in another namespace (the scripts assume default).
Teardown: delete the five files.
Optional export from Backstage. GET /entities/by-query takes repeated filter parameters and pages with
pageInfo.nextCursor.[44] Give the script its own static token, restricted to reading the catalog: without
accessRestrictions “the access method has unlimited access to all functionality of all plugins”, and permissionAttribute
applies only where permission checks are enabled.[45]
Example status: YAML syntax-checked; not loaded into a Backstage backend.
backend:
auth:
externalAccess:
- type: static
options:
token: ${CATALOG_EXPORT_TOKEN}
subject: catalog-export
accessRestrictions:
- plugin: catalog
permissionAttribute:
action: read
Example status: bash syntax-checked with bash -n; not executed (needs a Backstage instance, curl and jq).
#!/usr/bin/env bash
# Export components and APIs from a Backstage catalog into catalog.json.
set -euo pipefail
base="${BACKSTAGE_URL:-http://localhost:7007}/api/catalog/entities/by-query"
: "${BACKSTAGE_TOKEN:?set BACKSTAGE_TOKEN to the read-only catalog token}"
# printf is a builtin and -H @- reads the header from stdin,
# so the token never appears in the process list.
get() {
printf 'Authorization: Bearer %s\n' "$BACKSTAGE_TOKEN" |
curl -fsS -G -H @- "$base" "$@"
}
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
: > "$tmp/items.ndjson"
page=$(get --data-urlencode 'filter=kind=component' \
--data-urlencode 'filter=kind=api' --data-urlencode 'limit=200')
while :; do
# One entity per line: a growing variable passed as one argument
# fails past 128 KiB on Linux.
jq -c '.items[]' <<<"$page" >> "$tmp/items.ndjson"
cursor=$(jq -r '.pageInfo.nextCursor // empty' <<<"$page")
[ -n "$cursor" ] || break
# The cursor replaces filter, not limit, so send limit again.
page=$(get --data-urlencode "cursor=$cursor" --data-urlencode 'limit=200')
done
jq -s '{items: .}' "$tmp/items.ndjson" > catalog.json
echo "exported $(jq '.items | length' catalog.json) entities"
Lab 3 (advanced): a golden path in a local Backstage
Link to section “Lab 3 (advanced): a golden path in a local Backstage”Objective: dry-run a template and inspect the catalog entry it would register. Prerequisites (the Backstage guide’s list, as
of October 2026): Linux, macOS or WSL; 20 GB of disk and 6 GB of memory; an account with rights to install dependencies; make
and build-essential (or Xcode command-line tools); curl or wget; Node.js 22 or 24; Yarn 4.4.1 via corepack; Docker; git; ports
3000 and 7007 if in a VM or container.[46] Allow one to two hours. Cost and risk: none in dry-run mode; the optional
step creates a real repository.
- Run
npx @backstage/create-app@latest, thenyarn startin the new directory; openhttp://localhost:3000.[46] - Save Artefact 2 as
golden-paths/python-service/template.yamlwithout thepublishandregistersteps, theRepositorypage andoutput, so nothing leaves your machine; save the skeletoncatalog-info.yamlunderskeleton/. - Open
/create/edit, choose Load Template Directory and fill in the form. Create runs the template “in dry-run mode”;[38] open the renderedcatalog-info.yamlin the result.
Expected output: your service name and owner group, the backstage.io/source-template annotation and template-version: "3.2.0". Validation: the name pattern rejects My_Service; the owner picker lists only groups. Troubleshooting: if local
directories cannot be loaded, register the template and use Edit Template Form, which previews without saving;[38] if
yarn start fails, check the Node.js version. Optional: restore the removed parts, configure GitHub sign-in and run it
against a sandbox organisation in allowedOwners, because a real run creates and registers a repository with your token.
Teardown: stop yarn start; delete the app directory and any sandbox repository.
Use cases and scenarios
Link to section “Use cases and scenarios”Case 1: Spotify’s golden paths (sourced). Problem: fragmented tooling. Method: a Hack Week tutorial grew into one Golden Path per discipline (backend, client, data, web and others), with “blessed” tools shown in Backstage. Operating lesson: “When one tool gets swapped out and another swapped in … the Golden Path tutorial should be updated at the same time”; Golden State checks then let systems on one path be upgraded automatically. Trade-off: teams may leave the path, “but then you will not have the same support”.[4]
Case 2: an infrastructure service that was too reliable (sourced). Problem: global outages of Google’s Chubby lock service were so rare that owners assumed it never failed, so its rare failures broke their services. Method: SRE keeps global Chubby at, not significantly above, its objective; in any quarter in which a true failure has not dropped availability below the target, “a controlled outage will be synthesized by intentionally taking down the system”. The book also names “throttling some requests, or designing the system so that it isn’t faster under light loads”.[18] Lesson: an objective also limits what users may assume; test it. A small platform can start with failure tests outside production (this page’s suggestion).
Measure, govern, pay
Link to section “Measure, govern, pay”Metrics and KPIs
Link to section “Metrics and KPIs”DORA recommends a balanced scorecard (delivery metrics, satisfaction, adoption and retention from H.E.A.R.T., task success), because platforms “can sometimes lead to a decrease in throughput and change stability if not carefully managed”.[1][47]
| KPI | Formula | Data source | Pitfall | Review |
|---|---|---|---|---|
| Template-recorded coverage | Production services recorded on a supported template version ÷ production services | Catalog (Lab 2) | Creation-time record; stale unless upgrades update it | Monthly |
| Template currency | Services on the current template version ÷ template-created services | Catalog | Rewards forced upgrades with no user value | Monthly |
| Time to first deploy | Template start to first production deploy, median and 90th percentile | Scaffolder, pipeline events | Skipping hard steps looks fast | Monthly |
| Self-service rate | Requests fulfilled without human action ÷ all requests | Platform API, tickets | Tickets moved to chat disappear | Monthly |
| SLO attainment | Good events ÷ valid events per capability | Platform telemetry | Server uptime instead of user journeys | Weekly |
| Task success | Successful runs ÷ attempted runs of a key workflow | Scaffolder, CLI logs | Abandoned attempts never logged | Monthly |
| Retention | Teams active this and last quarter ÷ teams active last quarter | Usage logs | Mandates inflate it | Quarterly |
| Satisfaction | CSAT or NPS, same questions every time | Survey | Changed questions negate “the ability to track progress”[3] | Quarterly |
| Deprecation pace | Remaining consumers ÷ weeks to removal (required) versus migrations per week (actual) | Catalog reconciled with access logs | Undeclared callers; counting announcements | Fortnightly |
Delivery metrics are defined in DevOps. Expect a “J-curve”: early gains “followed by a dip as complexity increases”, then recovery.[1]
Worked calculation (illustrative numbers). A provisioning capability has an objective of 98% over 28 days. It served 2,400
valid requests, so the error budget is 2% × 2,400 = 48 failures; 61 failed, overspending by 13 (27%). Meanwhile 46 services
(reconciled count) still use deploy-api-v1, 20 weeks before removal: the required pace is 46 ÷ 20 = 2.3 migrations a week.
The last four weeks averaged 1.5, which takes 46 ÷ 1.5 ≈ 31 weeks, 11 weeks late. The first number pauses feature work under an
error-budget policy (SRE); the second triggers staffing or an open date change
(judgement call).
Is the catalog telling the truth?
Link to section “Is the catalog telling the truth?”Catalog numbers are only as true as the catalog; three records are weak:
- Consumers are self-declared. A service that calls an API without listing it in
spec.consumesApisis invisible until removal breaks it. Software Engineering at Google says much of the initial work of deprecation is “determining who is using the old system—and in which unanticipated ways”, and that “Logging and runtime sampling in production help discover issues with dynamic dependencies”.[28] Reconcile declared consumers with callers in access logs over a full business cycle (Lab 2, step 6). A short, announced brown-out before removal is a last check (this page’s suggestion). - Template records are written once.
backstage.io/source-templatenames the template “originally used”,[29] and a version set at scaffold time never changes by itself. Have upgrade pull requests bump it, or compute conformance from repository checks, as Spotify’s Golden State does;[4] until then, call the metric template-recorded coverage. - Owners drift. Check monthly that each owner reference resolves to a group with someone to page.
Quality, security and compliance
Link to section “Quality, security and compliance”- The platform is a privileged path. The Scaffolder “often has elevated permissions” and, by default, any internal user may run templates:[23] restrict running and editing, and use users’ own credentials (Artefact 2).
- Least privilege for scripts. Static tokens without
accessRestrictionsreach every plugin.[45] Catalog owners are for people, not access control.[17] - Tenant isolation. Teams sharing a cluster often trust each other only partly; Kubernetes calls RBAC, quotas and network policies “essential to safely and fairly share clusters”. Network policies are ignored without a network plugin that implements them, and containers are “a weaker isolation boundary than virtual machines”, so run untrusted or regulated workloads in sandboxes or on dedicated nodes.[48] Hardening: KubernetesSecOps.
- Secure by default. Scanning, signing and policy go into templates and pipelines once (DevSecOps).
- Evidence. CNCF’s model notes that service inventories make audits such as PCI DSS or HIPAA easier.[3] Keep scaffolder logs, catalog history and SLO reports for your retention period.
- The platform’s own recovery. Portal database, task history, controller state and each capability’s infrastructure state are platform data. Entities kept as YAML next to the code can be harvested again,[49] but what lives only in the portal’s database cannot: list it, back it up before every upgrade, test a restore each quarter.
Cost, capacity and tenancy
Link to section “Cost, capacity and tenancy”Shared capacity. All teams share CI runners, clusters, provisioning back ends and the portal. Kubernetes’ multi-tenancy guide names the challenges: “security, fairness, and managing noisy neighbors”.[48] Google’s SRE book says global overload “occurs quite frequently (especially for internal services that tend to have many clients run by many teams)” and answers with capacity provisioned from negotiated usage and per-customer quotas.[50] This page suggests:
- Quotas per team: namespace quotas (KubernetesOps), CI concurrency, provisioning rate limits, published next to the SLO.
- Fair share: separate pools for deploys and batch work, so one team’s backfill cannot delay everyone’s deploy.
- Headroom: room for the largest team’s burst plus next quarter’s onboarding, reviewed monthly.
- Noisy-neighbour signals: queue time per team (90th percentile), share of runner minutes or API calls, throttled requests. A rise for everyone is capacity; a rise from one team is fairness.
Worked example (illustrative numbers). A pool of 60 CI runners. Outside month-end, the 95th percentile of concurrent jobs over four weeks was 41; the largest team’s month-end batch adds 9; three teams joining next quarter add about 3 each. Need: 41 + 9 + 9 = 59 of 60, so add runners before the onboarding, and cap each team at 15 concurrent jobs (a quarter of the pool). Namespace quotas follow the same arithmetic, with one trap: once a quota covers CPU and memory, pods without requests or limits may be rejected, so golden-path templates must set them or a LimitRange must supply defaults.[51]
Cost. The team, its tools and endless upgrades. The CNCF advises “the thinnest viable platform layer over implementations from managed providers”, staffed “appropriately for their domain and number of customers”.[2] Microsoft says “It isn’t uncommon for teams with fewer than 20 individuals to be able to support thousands of developers and hundreds of projects”, a vendor observation, not a benchmark.[52] Show costs back with FinOps; tiered service levels avoid paying for the strictest tier everywhere;[25] idle capacity has an environmental cost (GreenOps).
Assurance of the operating model
Link to section “Assurance of the operating model”Review the scorecard monthly and lifecycles quarterly; twice a year, ask whether the metrics still mean anything (CNCF’s top measurement level is “familiar with and sensitive to” Goodhart’s Law).[3] Warning signs: coverage up while satisfaction falls, more support requests after a release, ownerless capabilities.
When things go wrong
Link to section “When things go wrong”Common failures and troubleshooting
Link to section “Common failures and troubleshooting”| Symptom | Likely cause | Diagnosis | Remediation | Prevention |
|---|---|---|---|---|
| Teams bypass the golden path | Path too long or rigid | Interview the last five teams who left | Shorten it; add an escape hatch | Task success per step |
| Ticket queue grows | Missing self-service features | Categorise a month of requests | Ship the top three as features | Tickets as defects |
| Templates never upgraded | No conformance data or tooling | Lab 2 report | Automated upgrade pull requests | Golden-state checks |
| Deprecation never ends | Advisory, no owner or date | Consumers by owner | Staffed migration, deadline | Lifecycle policy at GA |
| One capability outage breaks many teams | Dependence beyond the objective | Consumers in catalog and logs | Degrade gracefully; publish status | Test failure at the objective |
| Deploys slow for everyone at month-end | One team’s batch saturates runners | Queue time, runner minutes per team | Separate batch pool or quota | Quotas; headroom review |
Worked runbook: golden-path provisioning fails after a platform upgrade
Link to section “Worked runbook: golden-path provisioning fails after a platform upgrade”Situation (illustrative). After a portal upgrade, Python-template provisioning success falls from 99% to 70%; the Artefact 3 alert fires.
- Declare (minutes 0–10). Platform on-call opens an incident (IncidentOps), posts status and pauses the rollout.
- Bound the impact (10–20). Which templates fail, at which step? Task logs show the failing action.
- Compare with the change (20–30). Read the release notes; Backstage minor releases may break things.[27]
- Restore (30–45). Roll back if the upgrade is the cause; if its database migrations cannot be reversed, restore the pre-upgrade backup. Otherwise disable the failing template and publish a manual path for urgent needs.
- Verify. Run the template end to end in a test organisation; watch success stay above objective for an hour.
- Clean up. Find half-created repositories and catalog entries; tell their requesters.
- Learn. Test every template against each release candidate; record budget spend.
Rollback criterion: no cause within 30 minutes means roll back first, investigate afterwards.
Anti-patterns and myths
Link to section “Anti-patterns and myths”DORA names five pitfalls: “Build it and they will come”, “Ivory tower”, “Ticket-ops”, “Big bang” and a “One-size-fits-all” mentality that creates a rigid “golden cage”.[1] Three more matter for operations:
- Relabelling. Renaming an operations team without changing how it works; Thoughtworks also warns against “ticket-driven platform operating models”.[9]
- Mandate as strategy. The CNCF says teams relying on “top-down mandates to achieve adoption are almost certain to find resistance and resentment from their users”.[2]
- Launch and leave. Treating GA as done, although most of the work comes after release.
Deciding and adopting
Link to section “Deciding and adopting”When to use and when not to use
Link to section “When to use and when not to use”Use a platform team when several teams repeat the same undifferentiated work, onboarding a service takes weeks, security controls are rebuilt in every repository, and leadership will fund the team, its on-call and upgrades for years; the CNCF calls that support “vital to the long-term sustainability and success of platform teams”.[2]
Do not build one when you have one or two product teams; needs barely overlap; a managed application platform already meets them (NoOps, ServerlessOps); or nobody will fund operating what you launch. CNCF’s model warns that “reaching the highest level should not be a goal in itself”.[3] Alternatives: shared docs and templates, a time-boxed enabling team, or a product (options). Hidden responsibilities: on-call, support, shared capacity, upgrades, user research, staffed deprecations.
Decision framework
Link to section “Decision framework”Diagram source (Mermaid)
flowchart TB
Q1{{"3+ teams repeat the same work?"}}
Q1 -->|"no"| N1["Don't build: share docs and scripts, revisit in six months"]
Q1 -->|"yes"| Q2{{"Managed service meets the need?"}}
Q2 -->|"no"| Q3{{"Funded, with on-call, for 2+ years?"}}
Q2 -->|"yes"| N2["Thinnest viable platform over the managed service"]
Q3 -->|"no"| N3["Time-boxed enabling team; launch nothing you cannot operate"]
Q3 -->|"yes"| Q4{{"PM and design partner?"}}
Q4 -->|"yes"| N5["Platform team: one golden path, SLOs, support, lifecycle policy"]
Q4 -->|"no"| N4["Fix that first, or it becomes an ivory tower"]
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
class Q1,Q2,Q3,Q4 decision
class N2,N5 core
class N1,N3,N4 risk
Text equivalent: Four questions. Three or more teams repeating the same work? If not, share docs and scripts and revisit in six months. Does a managed service meet the need? If so, run a thinnest viable platform over it. Funding for a team and on-call for two or more years? If not, use a time-boxed enabling team. A product manager and a design partner? If so, form a platform team with one golden path, SLOs, support and a lifecycle policy; if not, fix that first, or it becomes an ivory tower.
The thresholds (“three teams”, “two years”) are this page’s heuristics, not research findings; record yours.
Maturity model
Link to section “Maturity model”This is this Atlas’s model, focused on operations; CNCF’s own levels are Provisional, Operational, Scalable and Optimizing.[3]
| Level | Observable behaviour | Exit criteria (evidence) |
|---|---|---|
| 0 Ad hoc | Scripts and wiki pages owned by their authors | Owners named for the three most-used tools |
| 1 Shared tooling | Central team, ticket queue, no published promises | Capability inventory with owners; a PM appointed |
| 2 Product | Roadmap, user research, one golden path in beta or GA | Adoption and satisfaction measured twice, same questions |
| 3 Operated | Every GA capability has SLO, support tier, lifecycle state, quotas; upgrades on a calendar | Nothing past end of support; reconciled burn-downs; restore tested |
| 4 Participatory | Other teams contribute capabilities to the same GA criteria; most upgrades automatic | Deprecations finish on time; unused capabilities retired |
Adoption roadmap: 30/60/90 days
Link to section “Adoption roadmap: 30/60/90 days”| Days | Actions | Milestone | Metric |
|---|---|---|---|
| 1–30 | Interview five teams; inventory capabilities, owners, end-of-life dates; run Lab 1; categorise a month of tickets | Charter and TVP agreed | Baseline time to first deploy; ticket categories |
| 31–60 | Build one golden path with a design partner; add telemetry; draft SLO and support tiers; run Lab 2 | Beta with two teams | Task success; provisioning success; first survey |
| 61–90 | Promote to GA with runbook and on-call; publish lifecycle policy; announce a deprecation with tooling; start monthly review | GA capability; scorecard reviewed | SLO attainment; coverage; second survey, same questions |
Toolkit
Link to section “Toolkit”Checklists
Link to section “Checklists”Launch readiness (beta → GA)
- Owner, support tier and on-call rota in the catalog
- SLO from user journeys, with a dashboard and low-traffic alert guards
- Runbook for the top three failure modes; rollback tested
- Quotas and headroom set for the shared capacity it uses
- Its state backed up and a restore tested
- Versioning and deprecation policy written down
- Docs and golden-path tutorial match the real steps
- Credentials scoped; template execution and authorship restricted
Quarterly operations review
- Every upstream component in its support window; next upgrade dated
- SLO attainment and error-budget spend reviewed per capability
- Top ticket categories turned into backlog items
- Survey run with unchanged questions; results shared
- Quotas, headroom and noisy-neighbour signals reviewed
- Each deprecation’s required pace compared with its actual pace
Templates
Link to section “Templates”One-page platform team charter
Platform: <name> Owner: <platform PM> Review: <YYYY-MM-DD>
Users and top journeys: <teams> / <journeys from Lab 1>
Promise: <capabilities offered, each with SLO and support tier>
Support tiers: incident <24x7 or hours, 15 min> / blocked <4 h> / how-to <1 day>
Shared capacity: <quotas per team, headroom target, review date>
Not offered (this year): <list>
Interaction mode: X-as-a-Service by default; collaboration time-boxed
On-call: platform components only; application teams own their services
Lifecycle policy: criteria per state (alpha to retired); notice: <n> months
Measures: coverage, task success, SLO attainment, satisfaction, deprecation pace
Funding and cost model: <central / showback / chargeback>
Capability request (intake)
Requesting team: Date:
Journey blocked or slowed: <what you were trying to do>
How often across teams (if known):
Current workaround and its cost (hours per month):
Is a managed service or existing capability close? <yes/no, which>
Needed by / consequence of delay:
Willing to be design partner? <yes/no>
Triage outcome: feature | documentation | exception | declined (reason)
Deprecation notice
Subject: [Action by <date>] <capability> is deprecated; move to <successor>
What changes: <one sentence>
Why: <security, cost, upstream end of support>
Your services affected: <list generated from the catalog>
What to do: <three numbered steps, link to guide and automated pull request>
Help: <office hours, channel>; the platform team migrates <categories> for you
Removal date: <date>. After it: <what stops working>
Capability scorecard (monthly)
| Capability (lifecycle state) | SLO (target / actual) | Task success | Support requests per 100 runs | Consumers on supported version | Action |
|---|
Deep comparisons
Link to section “Deep comparisons”| Model | Who runs production services | Typical failure | When it fits |
|---|---|---|---|
| Central operations team | Operations, for developers | Handoffs and queues | Few changes; strict separation required |
| “DevOps team” between Dev and Ops | Unclear | A new silo (DevOps) | Rarely |
| Platform team (this page) | Application teams; platform team runs the platform | Ivory tower, ticket-ops | Several teams, shared needs |
| SRE team (SRE) | Shared, governed by SLOs | The ops team by another name | Demanding reliability needs |
| Fully managed platform (NoOps) | Provider, plus residual duties | Unowned residual work | Workloads that fit the product |
Is PlatformOps the same as platform engineering? In practice yes; search for “platform engineering” to find the literature.
Do we need Backstage? No; a portal is one interface. Adopt one when discovery and ownership across many services become the problem, and budget for its monthly releases.[27]
Should using the platform be mandatory? Mostly not: mandate what security or law requires and earn the rest. In Bottcher’s account, Netflix teams that skip the paved road are “responsible for all the costs of maintaining their own alternatives”.[11]
How big should the platform team be? No reliable ratio exists. Size it from promised capabilities, their support tiers, shared capacity and the upgrade calendar.
What happens to a capability that few teams use? Interview them, then improve it, or withdraw it with a documented decision (lifecycle); removal is part of product maturity, not failure.[3]
Going further
Link to section “Going further”Advanced topics and emerging direction
Link to section “Advanced topics and emerging direction”- Shift down. Google describes embedding “decisions and responsibilities into underlying internal developer platforms (IDPs), thereby reducing the operational burden on developers”, and says its ecosystem types are “not a maturity model”.[53] Whether this transfers to smaller organisations is open.
- AI-written code and agents. DORA links platform quality to whether AI adoption pays off.[1] Agents calling platform APIs are new; treat vendor claims as unverified.
- Versioned platform APIs. When capabilities are custom resources (Crossplane and similar),[24] Kubernetes’
CRD versioning is the lifecycle in code: it is “perfectly safe for some clients to use the old version while others use the new
version”; schema changes that need custom logic call for a conversion webhook; API-server logs show clients still on the old
version; and setting
served: falseon it can be reverted, like a brown-out.[54] - Participatory platforms. DORA advises designing “for extensibility”;[1] contributed capabilities must meet GA criteria.
Learning path
Link to section “Learning path”| Stage | Do this |
|---|---|
| Prerequisites | DevOps basics; SRE SLIs and SLOs |
| 1-day primer | Read the CNCF white paper and DORA’s capability page; do Lab 1 |
| 1-week practice | Labs 2 and 3; an SLO, support tier and runbook for one capability |
| 30 days | One platform review with users; a lifecycle policy; one proper deprecation |
| Specialisation | KubernetesOps or InfraOps (capability layer); DevExOps (measurement); FinOps (cost) |
| Portfolio project | A golden-path template, its conformance report and a finished deprecation, with metrics |
Wrap-up
Link to section “Wrap-up”Executive recap and cheat sheet
Link to section “Executive recap and cheat sheet”| Topic | Remember |
|---|---|
| Core decisions | What to offer (and not); objectives; support tiers; quotas; lifecycle and removal rules; few mandates |
| Essential practices | Capability SLOs; upgrade calendar; staffed, reconciled deprecation; conformance checks; tickets as defects |
| Watch-outs | Ticket-ops; ivory tower; golden cage; relabelled teams; mandates; launch-and-leave; self-declared data; surplus reliability |
| Next actions | Inventory capabilities and owners; run Lab 1; publish one SLO and support tier; date one deprecation |
A judgement call: extend or enforce a deprecation?
Link to section “A judgement call: extend or enforce a deprecation?”Situation (illustrative). deploy-api-v1 runs on a component whose upstream support ends 2027-04-30; removal is set for
2027-03-31. With 20 weeks left, 46 services (declared and seen in logs) still use it; at 1.5 migrations a week it finishes about
11 weeks late (worked calculation). Product leads ask for three more months.
Option A: extend. It feels kind, but runs past upstream support onto unpatched software, and warnings alone “rarely lead to migration of existing systems”.[28]
Option B: enforce, with staff. Keep the date and fund the work, as the same book recommends.[28] Assume (illustrative) that automated pull requests handle 70% of services, about 32, at half an hour of review each (16 hours across teams), and the other 14 need two days of platform-engineer help each: 28 person-days, about six person-weeks, so two engineers for three to four weeks.
Why the attractive choice can be wrong. Option A looks cheaper because its cost is deferred and shared: teams run unsupported software and the platform team operates two versions. Recommendation: Option B, plus a security-owned exception route for a handful of services, each with compensating controls and its own date. Uncertainty: if far fewer migrations can be automated, the estimate doubles; pause roadmap work rather than extend. Were the deadline only about tidiness, extending would be reasonable.
Learning objectives and mastery check
Link to section “Learning objectives and mastery check”You should now be able to explain why PlatformOps is a synonym with competing meanings; design a capability’s promise (SLO, support tier, quotas, lifecycle); measure adoption, conformance and deprecation pace, and check the data; run a deprecation; and decide whether a platform team is needed at all.
- Your provisioning capability achieved 99.95% success against a 99% objective for a year. Why might that be a problem?
Show answer for question 1
Users “build on the reality of what you offer”, so they may stop handling provisioning failures. Test the assumption: raise the published objective to one you will keep, or inject failures (planned outages, throttling, or tests outside production) so consumers handle failure at the objective.
- Thirty services use a deprecated capability, removal is 12 weeks away, and the last month averaged 2 migrations a week. On track?
Show answer for question 2
No. Required pace is 30 ÷ 12 = 2.5 a week; at 2 a week it takes 15 weeks, three weeks late. Escalate now: add tooling or staff, or move the date openly. Also reconcile the 30 with access logs; the real number may be higher.
- A team needs a graph database that the platform does not offer. What do you do?
Show answer for question 3
Let them run it themselves: the CNCF calls platforms “optional and composable”. Make clear they own its operation, and record the request; if more teams need it, it becomes a candidate capability.
- Template-recorded coverage rose from 40% to 85% in a quarter, but satisfaction fell. What do you suspect, and what do you check?
Show answer for question 4
A mandate, or a required but painful path. Check retention, task success per step and support requests, and interview teams that moved.
- Name two situations in which you should not build a platform team.
Show answer for question 5
Any two of: only one or two product teams; little overlap in their needs; a managed service already fits; no long-term funding for operating what is launched; no product manager or design partner available.
- Deploy queue times doubled this month. How do you tell a capacity problem from a fairness problem, and what do you do?
Show answer for question 6
Compare queue time per team and each team’s share of runner minutes. If every team’s queue time rose, it is capacity: add runners and restore headroom. If one team’s share jumped, for example a month-end backfill, it is fairness: give batch work its own pool or cap that team’s concurrency.
Conclusion
Link to section “Conclusion”“PlatformOps” is a minor label for a major, well-described practice, platform engineering. It points at the part easiest to underfund: keeping promises after launch. Platforms rarely fail on launch day. They fail slowly, when templates drift, shared capacity runs out, upgrades slip, deprecations stall on consumers nobody knew about, and the ticket queue quietly returns. Treat every capability as a promise with an owner, an objective, a support tier and an end date; check the data you measure it with; and staff the work of changing it, or the platform becomes the legacy system the next platform is built to replace.
Sources and further reading
Link to section “Sources and further reading”Sources were opened on 2026-10-16, except the five added in revision r2 and two re-checked in the post-score polish, opened on
2026-10-17, when the time-sensitive facts (Backstage release and prerequisites, Kubernetes releases, Proton, multi-tenancy) were
also checked again. Dates are the source’s own; product facts are as of October 2026. Claim-level support is in the evidence
ledger (research/platformops-ledger.md).
Further reading, not consulted for this page. Camille Fournier and Ian Nowland, Platform Engineering: A Guide for Technical, Product, and People Leaders (O’Reilly, 2024): the main book on the field; its publisher pages could not be fetched, so nothing on this page relies on it.
Changelog.
- 2026-10-16 — r0: first version (researcher, architect, teacher and diagram roles in sequence). Lab 2 executed; Labs 1 and 3 not.
- 2026-10-16 — r1: answered independent review
platformops-r1(3 major, 14 minor): least-privilege golden-path template; catalog-truth checks and a second executed Lab 2 script; support tiers, capacity and tenancy, platform recovery; lifecycle, diagrams, alerts and export script fixed; option categories; repetition cut. - 2026-10-17 — r2: answered independent score
platformops-score(90, no critical or major finding): architecture figure split; new figures for the operating loop and staffed deprecation; two-way handoff map; retirement rule aligned; current cloud-provider examples; tenant isolation, capacity arithmetic and CRD versioning added; Lab 3 prerequisites completed; code narrowed (Lab 2 re-executed);catalog:registerevidence pinned to v1.55.0; cuts elsewhere to stay in band. - 2026-10-17, post-score polish: answered
platformops-score-r2(94): Microsoft model row corrected (no Operations aspect); export script resendslimitwith the cursor; operating loop drawn closed; deprecation-loop label shortened for phones.
Capabilities: Platform engineering — DORA, updated 2026-01-12. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
CNCF Platforms White Paper — CNCF TAG App Delivery; v1 March 2023, web edition maintained. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Platform Engineering Maturity Model — CNCF TAG App Delivery; v1 October 2023. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
How We Use Golden Paths to Solve Fragmentation in Our Software Ecosystem — Gary Niemen, Spotify Engineering, 2020-08-17. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Reinventing the Wheels in DevOps Doesn’t Scale. Platform Ops Does. — Quali blog, 2020-07-14 (page metadata). Checked 2026-10-16. ↩︎ ↩︎
Why Every Enterprise Should Be Implementing Platform Operations — EchoStor, 2021-03-02. Checked 2026-10-16. ↩︎ ↩︎
What Is Platform Ops? — F5 glossary, undated. Checked 2026-10-16. ↩︎
Shifting the focus to Platform Ops: Here’s why — Dario Esposito, Mia-Platform blog, 2024-07-24. Checked 2026-10-16. ↩︎
Platform engineering product teams — Thoughtworks Technology Radar, 2017–2021. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
The Evolving SRE Engagement Model — Cruz and Bhambhani, Site Reliability Engineering (Google; online edition © 2017). Checked 2026-10-16. ↩︎
What I Talk About When I Talk About Platforms — Evan Bottcher, martinfowler.com, 2018-03-05. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Team Topologies (book) — Skelton and Pais, IT Revolution; 2019-09-07, second edition 2025-09-23. Checked 2026-10-16. ↩︎
Key Concepts — Team Topologies, undated. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
What is Backstage? — Backstage (stable v1.55.0 when checked). Checked 2026-10-17. ↩︎ ↩︎
Backstage project page — CNCF. Checked 2026-10-16. ↩︎ ↩︎
Accelerate State of DevOps Report 2024 — DORA, 2024 (summary page). Checked 2026-10-16. ↩︎
Descriptor Format of Catalog Entities — Backstage. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Service Level Objectives — Jones, Wilkes and Murphy with Smith, Site Reliability Engineering (online edition © 2017). Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Eliminating Toil — Vivek Rau, Site Reliability Engineering (online edition © 2017). Checked 2026-10-16. ↩︎
Platform Manifesto — Team Topologies, undated. Checked 2026-10-16. ↩︎
AIP-181: Stability levels — Google API Improvement Proposals, 2019-02-18. Checked 2026-10-16. ↩︎ ↩︎
Kubernetes Deprecation Policy — Kubernetes, updated 2026-08-21. Checked 2026-10-16. ↩︎ ↩︎
Backstage Threat Model — Backstage. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Crossplane project page — CNCF. Checked 2026-10-16. ↩︎ ↩︎
Embracing Risk — Marc Alvidrez, Site Reliability Engineering (online edition © 2017). Checked 2026-10-16. ↩︎ ↩︎
Releases — Kubernetes (1.37 newest when checked). Checked 2026-10-17. ↩︎
Release & Versioning Policy — Backstage. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Deprecation (chapter 15) — Hyrum Wright, Software Engineering at Google (O’Reilly, 2020). Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Well-known Annotations on Catalog Entities — Backstage. Checked 2026-10-16. ↩︎ ↩︎
Use the Platform Engineering Capability Model — Microsoft Learn, updated 2025-10-27. Checked 2026-10-17. ↩︎
Semantic Versioning 2.0.0 — semver.org. Checked 2026-10-16. ↩︎
Roadie vs Backstage — Roadie (vendor page), undated. Checked 2026-10-16. ↩︎
AWS Proton Service Deprecation and Migration Guide — AWS documentation, undated (end of support 2026-10-07). Checked 2026-10-17. ↩︎ ↩︎
OpsLevel home page — OpsLevel (vendor page), undated. Checked 2026-10-16. ↩︎
What Is Service Catalog? — AWS Service Catalog Administrator Guide, undated. Checked 2026-10-17. ↩︎
Application Design Center overview — Google Cloud documentation, updated 2026-10-13. Checked 2026-10-17. ↩︎
What is Azure Deployment Environments? — Microsoft Learn, updated 2026-09-14 (retirement notice for 22 February 2027). Checked 2026-10-17. ↩︎
Writing Templates — Backstage. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Authorizing scaffolder tasks, parameters, steps, and actions — Backstage. Checked 2026-10-16. ↩︎
catalog:registeraction source at release v1.55.0 — Backstage repository, tag v1.55.0 (commit 23161ae, 2026-09-15). Checked 2026-10-17. ↩︎Defining recording rules — Prometheus. Checked 2026-10-16. ↩︎
Alerting rules — Prometheus. Checked 2026-10-16. ↩︎
Query functions — Prometheus. Checked 2026-10-16. ↩︎
Software Catalog API — Backstage. Checked 2026-10-17. ↩︎
Service to Service Auth — Backstage. Checked 2026-10-16. ↩︎ ↩︎
Getting Started (standalone installation) — Backstage. Checked 2026-10-17. ↩︎ ↩︎
Unlocking product success with DORA and H.E.A.R.T. — Eric Maxwell, Google Cloud Blog, 2025-07-25. Checked 2026-10-16. ↩︎
Multi-tenancy — Kubernetes, updated 2025-05-22. Checked 2026-10-17. ↩︎ ↩︎
Backstage Software Catalog — Backstage. Checked 2026-10-16. ↩︎
Handling Overload — Alejandro Forero Cuervo, Site Reliability Engineering (online edition © 2017). Checked 2026-10-16. ↩︎
Resource Quotas — Kubernetes, updated 2026-09-28. Checked 2026-10-17. ↩︎
What is platform engineering? — Microsoft Learn, updated 2025-10-23. Checked 2026-10-16. ↩︎
How Google does it: Your guide to platform engineering — Rivers and Brookbank, Google Cloud Blog, 2025-08-13. Checked 2026-10-16. ↩︎
Versions in CustomResourceDefinitions — Kubernetes, updated 2026-06-29. Checked 2026-10-17. ↩︎