The Ops Atlas xOps School

Delivery & Platform Overlapping synonym Sources verified

PlatformOps

PlatformOps is a less common name for platform engineering. This page uses it for the operating half: running an internal developer platform as a product, with service levels, support, upgrades and deprecation that keep its self-service golden paths trustworthy.

Key takeaways

  • PlatformOps is not a standard term. The field is called platform engineering (CNCF, DORA); vendors use "Platform Ops" for the whole field, for the platform team, or for its operating side. This page covers the operating side.
  • A platform is a product for internal users: self-service capabilities, golden paths, documentation and support. Its team owns interfaces and experience, and should run as little infrastructure itself as it can.
  • CNCF's maturity model notes that most of the work on a capability comes after its first release. Budget for support, capacity, upgrades, migrations and removal before you build.
  • Give each generally available capability an owner, a published objective, a support tier and a lifecycle state, and test that consumers survive the objective. If you beat it by a wide margin for long, raise it or inject failures so teams do not depend on the surplus.
  • Adoption should be pulled, not mandated. Measure adoption, retention, task success and satisfaction alongside delivery metrics, and expect a dip before gains.
  • Catalog data is self-declared. Before removing anything, reconcile declared consumers with callers seen in logs; staff the migration, because warnings alone rarely move existing users.
  • With only a few teams, or needs a managed service already meets, a wiki page of procedures can be the right platform.

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.

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)

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]

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
Figure 1. Handoffs in both directions: what PlatformOps receives from its neighbours and what it hands on
InfraOps, CloudOps: testedmodules, accountsPlatformOps: capabilities withowner, objective, support tier,lifecycle stateKubernetesOps: supportedclustersGitOps: the reconcilerSRE: the SLO methodProduct teams: golden paths,support, migration helpDevExOps: friction findingsin; fixes and usage data outFinOps: cost per capabilityand teamIncidentOps: platformincidents
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

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 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.

  1. Every capability has an owner, a lifecycle state, a published objective and a support tier. Nothing is “just there”.
  2. Promise only what you can operate. A capability without on-call, runbooks, capacity plan and upgrade budget stays in beta.
  3. Self-service first, tickets as a defect signal. A recurring ticket is a missing feature or document.
  4. Pull, not push. Adoption is earned; mandates are reserved for security and legal requirements, and explained.
  5. Run the thinnest layer. Prefer managed services and internal providers; own the interface and the experience.
  6. Change is a product feature. Upgrades and migrations ship with tooling, documentation and dates.
  7. 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.

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.

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]

Figure 2. Lifecycle of a platform capability, with the gate to pass at each step
design partner agreesexit criteria metexperiment stoppedSLO, runbook, support readycancelled, users movedsuccessor GA or withdrawaldecideddeclared and observedconsumers at zero, ordeadlineProposedAlphaBetaRetiredGADeprecated
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.

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.

Figure 3. The operating loop: support requests and telemetry become changes, or an escalation
incidentblocked teamhow-to, requesttwo months below objective,or deadline at riskeffect shows in next month'sdataSupport request or alertWhich tier?Page platform on-callRota engineer, 4 businesshoursChannel, office hours, intakeTag the cause: feature, docs,bug or exceptionTelemetry: SLIs, usage, tasksuccess, costMonthly platform review:scorecard, one improvementChange: feature, docs,template, quota ordeprecationEngineering leadership, withoptions
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.

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.

Figure 4. Reference architecture: interfaces and core owned by the platform team, providers behind them
Users: developers, data andML teams, reviewersPortal and catalogCLI, IDE, APIsTemplates and docsPlatform core: orchestrationand policy; holds providercredentials (trust boundary)Cloud and managed servicesClusters and runtimesCI/CD and registries
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]

Figure 5. Creating a service on the golden path, with the exception path
yesnoyesnoDeveloper picks a template inthe portalScaffolder creates repo,pipeline, catalog entryProvisioning succeeds?First deploy to stagingService registered: owner,SLO, dashboardsUsage and task-success datafeed the backlogClear error and runbook linkCan the developer fix it?Support request with run IDPlatform fix and review;template or docs updated
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.

Playbook 1 (capability owner, from beta): give a capability an objective.

  1. Measure the user’s journey, not a server: provisioning success and duration, queue time, deploy success.
  2. Set the target from user needs, not “a target based on current performance”.[18]
  3. 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]
  4. 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).
  5. 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]

Figure 6. A staffed deprecation: reconcile consumers, track the pace, remove only at zero
yesnonoyesno: repeatyes, after an optionalbrown-outSuccessor GA or withdrawaldecided; marked deprecated,new use blockedReconcile declaredconsumers with callers inaccess logsUndeclared or silent callers?Contact their owners; addthem to the burn-downNotices, automated pullrequests, staffed migrationsActual pace at least therequired pace?Escalate: add staff, or movethe date openlyDeclared and observedconsumers at zero?Remove the capability, itsdata, credentials and access
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.

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.

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 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.

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 in example-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:register calls 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).

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.

  1. With each team, list critical user journeys, such as DORA’s “spinning up a new service” or “debugging a production issue”.[1]
  2. Score each journey 1–5 for frequency, current pain and sameness across teams; multiply the three.
  3. 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]
  4. Write the promise: what users get, how fast, how reliably (draft SLO), support tier, what happens on failure.
  5. Name a design partner and the alpha and beta exit criteria (lifecycle).
  6. 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.

  1. Save the script as platform_health.py.
  2. Save the sample catalog export (shaped like the catalog API’s items list) as catalog.json and the policy as golden-paths.json.
  3. Run python3 platform_health.py catalog.json golden-paths.json 2026-10-16 and then echo $?.
  4. Edit catalog.json: set search to template version 3.2.0 and account-web to 2.1.0. Run step 3 again.
  5. Run once more with the date 2027-01-15 instead of 2026-10-16.
  6. Save the second script as reconcile_consumers.py and the access-log summary as access-summary.csv, then run python3 reconcile_consumers.py catalog.json access-summary.csv api:default/deploy-api-v1 and echo $?.
  7. 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.

  1. Run npx @backstage/create-app@latest, then yarn start in the new directory; open http://localhost:3000.[46]
  2. Save Artefact 2 as golden-paths/python-service/template.yaml without the publish and register steps, the Repository page and output, so nothing leaves your machine; save the skeleton catalog-info.yaml under skeleton/.
  3. Open /create/edit, choose Load Template Directory and fill in the form. Create runs the template “in dry-run mode”;[38] open the rendered catalog-info.yaml in 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.

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).

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).

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.consumesApis is 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-template names 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.
  • 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 accessRestrictions reach 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.

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).

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.

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.

  1. Declare (minutes 0–10). Platform on-call opens an incident (IncidentOps), posts status and pauses the rollout.
  2. Bound the impact (10–20). Which templates fail, at which step? Task logs show the failing action.
  3. Compare with the change (20–30). Read the release notes; Backstage minor releases may break things.[27]
  4. 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.
  5. Verify. Run the template end to end in a test organisation; watch success stay above objective for an hour.
  6. Clean up. Find half-created repositories and catalog entries; tell their requesters.
  7. Learn. Test every template against each release candidate; record budget spend.

Rollback criterion: no cause within 30 minutes means roll back first, investigate afterwards.

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.

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.

Figure 7. Should you build or extend a platform team? A decision tree that includes “don’t”
noyesnoyesnoyesyesno3+ teams repeat the samework?Don't build: share docs andscripts, revisit in six monthsManaged service meets theneed?Funded, with on-call, for 2+years?Thinnest viable platform overthe managed serviceTime-boxed enabling team;launch nothing you cannotoperatePM and design partner?Platform team: one goldenpath, SLOs, support, lifecyclepolicyFix that first, or it becomes anivory tower
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.

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
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

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

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
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]

  • 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: false on it can be reverted, like a brown-out.[54]
  • Participatory platforms. DORA advises designing “for extensibility”;[1] contributed capabilities must meet GA criteria.
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
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.

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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

“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 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:register evidence 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 resends limit with the cursor; operating loop drawn closed; deprecation-loop label shortened for phones.

  1. Capabilities: Platform engineering — DORA, updated 2026-01-12. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. CNCF Platforms White Paper — CNCF TAG App Delivery; v1 March 2023, web edition maintained. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. Platform Engineering Maturity Model — CNCF TAG App Delivery; v1 October 2023. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  4. How We Use Golden Paths to Solve Fragmentation in Our Software Ecosystem — Gary Niemen, Spotify Engineering, 2020-08-17. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  5. Reinventing the Wheels in DevOps Doesn’t Scale. Platform Ops Does. — Quali blog, 2020-07-14 (page metadata). Checked 2026-10-16. ↩︎ ↩︎

  6. Why Every Enterprise Should Be Implementing Platform Operations — EchoStor, 2021-03-02. Checked 2026-10-16. ↩︎ ↩︎

  7. What Is Platform Ops? — F5 glossary, undated. Checked 2026-10-16. ↩︎

  8. Shifting the focus to Platform Ops: Here’s why — Dario Esposito, Mia-Platform blog, 2024-07-24. Checked 2026-10-16. ↩︎

  9. Platform engineering product teams — Thoughtworks Technology Radar, 2017–2021. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  10. The Evolving SRE Engagement Model — Cruz and Bhambhani, Site Reliability Engineering (Google; online edition © 2017). Checked 2026-10-16. ↩︎

  11. What I Talk About When I Talk About Platforms — Evan Bottcher, martinfowler.com, 2018-03-05. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  12. Team Topologies (book) — Skelton and Pais, IT Revolution; 2019-09-07, second edition 2025-09-23. Checked 2026-10-16. ↩︎

  13. Key Concepts — Team Topologies, undated. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  14. What is Backstage? — Backstage (stable v1.55.0 when checked). Checked 2026-10-17. ↩︎ ↩︎

  15. Backstage project page — CNCF. Checked 2026-10-16. ↩︎ ↩︎

  16. Accelerate State of DevOps Report 2024 — DORA, 2024 (summary page). Checked 2026-10-16. ↩︎

  17. Descriptor Format of Catalog Entities — Backstage. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  18. Service Level Objectives — Jones, Wilkes and Murphy with Smith, Site Reliability Engineering (online edition © 2017). Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  19. Eliminating Toil — Vivek Rau, Site Reliability Engineering (online edition © 2017). Checked 2026-10-16. ↩︎

  20. Platform Manifesto — Team Topologies, undated. Checked 2026-10-16. ↩︎

  21. AIP-181: Stability levels — Google API Improvement Proposals, 2019-02-18. Checked 2026-10-16. ↩︎ ↩︎

  22. Kubernetes Deprecation Policy — Kubernetes, updated 2026-08-21. Checked 2026-10-16. ↩︎ ↩︎

  23. Backstage Threat Model — Backstage. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  24. Crossplane project page — CNCF. Checked 2026-10-16. ↩︎ ↩︎

  25. Embracing Risk — Marc Alvidrez, Site Reliability Engineering (online edition © 2017). Checked 2026-10-16. ↩︎ ↩︎

  26. Releases — Kubernetes (1.37 newest when checked). Checked 2026-10-17. ↩︎

  27. Release & Versioning Policy — Backstage. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  28. Deprecation (chapter 15) — Hyrum Wright, Software Engineering at Google (O’Reilly, 2020). Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  29. Well-known Annotations on Catalog Entities — Backstage. Checked 2026-10-16. ↩︎ ↩︎

  30. Use the Platform Engineering Capability Model — Microsoft Learn, updated 2025-10-27. Checked 2026-10-17. ↩︎

  31. Semantic Versioning 2.0.0 — semver.org. Checked 2026-10-16. ↩︎

  32. Roadie vs Backstage — Roadie (vendor page), undated. Checked 2026-10-16. ↩︎

  33. AWS Proton Service Deprecation and Migration Guide — AWS documentation, undated (end of support 2026-10-07). Checked 2026-10-17. ↩︎ ↩︎

  34. OpsLevel home page — OpsLevel (vendor page), undated. Checked 2026-10-16. ↩︎

  35. What Is Service Catalog? — AWS Service Catalog Administrator Guide, undated. Checked 2026-10-17. ↩︎

  36. Application Design Center overview — Google Cloud documentation, updated 2026-10-13. Checked 2026-10-17. ↩︎

  37. What is Azure Deployment Environments? — Microsoft Learn, updated 2026-09-14 (retirement notice for 22 February 2027). Checked 2026-10-17. ↩︎

  38. Writing Templates — Backstage. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  39. Authorizing scaffolder tasks, parameters, steps, and actions — Backstage. Checked 2026-10-16. ↩︎

  40. catalog:register action source at release v1.55.0 — Backstage repository, tag v1.55.0 (commit 23161ae, 2026-09-15). Checked 2026-10-17. ↩︎

  41. Defining recording rules — Prometheus. Checked 2026-10-16. ↩︎

  42. Alerting rules — Prometheus. Checked 2026-10-16. ↩︎

  43. Query functions — Prometheus. Checked 2026-10-16. ↩︎

  44. Software Catalog API — Backstage. Checked 2026-10-17. ↩︎

  45. Service to Service Auth — Backstage. Checked 2026-10-16. ↩︎ ↩︎

  46. Getting Started (standalone installation) — Backstage. Checked 2026-10-17. ↩︎ ↩︎

  47. Unlocking product success with DORA and H.E.A.R.T. — Eric Maxwell, Google Cloud Blog, 2025-07-25. Checked 2026-10-16. ↩︎

  48. Multi-tenancy — Kubernetes, updated 2025-05-22. Checked 2026-10-17. ↩︎ ↩︎

  49. Backstage Software Catalog — Backstage. Checked 2026-10-16. ↩︎

  50. Handling Overload — Alejandro Forero Cuervo, Site Reliability Engineering (online edition © 2017). Checked 2026-10-16. ↩︎

  51. Resource Quotas — Kubernetes, updated 2026-09-28. Checked 2026-10-17. ↩︎

  52. What is platform engineering? — Microsoft Learn, updated 2025-10-23. Checked 2026-10-16. ↩︎

  53. How Google does it: Your guide to platform engineering — Rivers and Brookbank, Google Cloud Blog, 2025-08-13. Checked 2026-10-16. ↩︎

  54. Versions in CustomResourceDefinitions — Kubernetes, updated 2026-06-29. Checked 2026-10-17. ↩︎

Related Ops

DevOps

DevOps is a way of organising software delivery so that the people who build a service and the people who run it share goals, tools and feedback, letting changes reach users quickly, safely and repeatedly. It is judged by delivery outcomes, not job titles.

DevExOps

DevExOps is a rare, unstandardised label. This page uses it for running developer experience as a continuous loop: measure friction with surveys and system data, prioritise it, get it fixed and verify the fix, without turning the data into surveillance of individuals.

InfraOps

InfraOps is informal shorthand for infrastructure operations. This Atlas uses it for running infrastructure through code: declaring it, reviewing every change as a plan, applying it from a pipeline and keeping the tool's state in step with reality.

KubernetesOps

KubernetesOps is this Atlas's name for operating Kubernetes clusters and the workloads on them: keeping the control plane, nodes and add-ons healthy and current, making workloads schedule and scale correctly, and diagnosing failures.

GitOps

GitOps is a way of operating software systems in which the desired state is declared in a versioned, immutable store such as Git, and software agents continuously pull it and reconcile the running system to match.

CloudOps

CloudOps (cloud operations) is running workloads on public cloud through the provider's control plane: account hierarchy, guardrails, quotas, Regions, provider signals and managed-service change. The label is common, but no neutral body defines it.

ServiceOps

Service operations and service management for digital services: service catalog, service ownership, service level management (SLA/SLO), service maps and dependencies, …

In preparation

SRE (Site Reliability Engineering)

Site Reliability Engineering (SRE) runs production services as a software-engineering problem: it sets explicit reliability targets (SLOs) from users' experience and uses the remaining error budget to decide how fast to ship and when to stop and fix.

Practise and get certified

From xOps School

Practise PlatformOps and get certified

The free PlatformOps track on xOps School has study notes, a 25-question practice quiz with explanations, and a verifiable certificate when you score 70% or more. Sign in with Google to start.

For teams

Instructor-led training

Live batches for your team, with completion certificates and reports for L&D/HR.

Source: master.md (the editorial source of this page) · built 2026-10-17 · diagrams rendered with @mermaid-js/mermaid-cli 11.17.0 (mermaid 11.17.2)