Declare Service Ownership in a Catalog
Add a catalog-info.yaml with owner, lifecycle, and on-call reference, and validate that every production service has an owner in CI.
Task
Make it obvious who owns each service when an alert fires or a dependency needs a change.
When to use this
An alert fires for orders-worker at 2 a.m. The repository has no owner file, the last commit is from a former contractor, and the on-call engineer pages three teams before finding someone who recognizes the service.
Prerequisites
- A service catalog such as Backstage or an internal registry
- Ability to add files to each service repository
- CI that can run yq or a small validation script
Steps
1. Add a catalog descriptor that names the component, its owner group, lifecycle, and paging integration.
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payments-api
annotations:
backstage.io/techdocs-ref: dir:.
pagerduty.com/service-id: PABC123
spec:
type: service
lifecycle: production
owner: group:default/payments
system: checkout
Expected output: The service appears in the catalog with a linked owner group and on-call service.
2. Register the descriptor with the catalog through its location API or GitHub discovery.
curl -s -X POST http://backstage.internal/api/catalog/locations -H 'Content-Type: application/json' --data '{"type":"url","target":"https://github.com/acme/payments-api/blob/main/catalog-info.yaml"}'Expected output: The catalog returns a location id and the component is visible in the UI after processing.
3. Validate ownership fields in CI so a missing or unknown owner fails the build.
test -f catalog-info.yaml
yq -e '.spec.owner' catalog-info.yaml
yq -e '.spec.lifecycle' catalog-info.yaml
yq -e '.metadata.name' catalog-info.yaml
Expected output: A service without spec.owner fails CI instead of silently appearing unowned in the catalog.
4. Cross-check the owner group against the organization registry and alert on components with no owner.
yq '.spec.owner' catalog-info.yaml | sed 's/group:default\///' | xargs -I{} curl -s -o /dev/null -w '%{http_code}\n' http://backstage.internal/api/catalog/entities/by-name/group/default/{}Expected output: HTTP 200 confirms the group exists; 404 flags the component for correction.
Troubleshooting
Problem
The owner group does not exist in the catalog
Solution
Create the group entity first or correct the reference; Backstage stores ownership as a relation and will show an orphan otherwise.
Problem
Two components share the same name
Solution
Component names must be unique within the catalog; include the domain or team prefix for common names such as api or worker.
Limitations
- Ownership metadata goes stale after reorganizations unless reviews update it.
- A catalog entry does not guarantee paging rotation coverage; verify the on-call integration separately.
Ownership is an operational dependency
Every alert, dependency upgrade, and deprecation needs a person or team to act. When ownership is implicit, responders waste time guessing and changes stall. A catalog descriptor makes ownership explicit and machine-readable, so paging tools, documentation, and dependency graphs can all point at the same team.
Model it in the repository
Put catalog-info.yaml next to the code so ownership changes travel through the same review process as the service itself. Name the component after the deployed service, set the lifecycle to production only when it really serves traffic, and reference the on-call service id. For monorepos, one descriptor per component keeps relations precise.
spec:
type: service
lifecycle: production
owner: group:default/payments
system: checkout
Validate in CI
Structure checks are cheap and catch the common failures: missing owner, empty component name, unknown lifecycle. yq -e exits non-zero when a field is absent, which is exactly the behavior you want. Add a check that the owner group exists in the catalog so typos surface in the pull request, not during an incident.
Keep it honest
Review ownership after every reorganization and when a team is dissolved. Alert on components whose owning group no longer exists. Link the catalog entry from the runbook and the alert annotation so a responder can go from page to owner in one step. Ownership that is outdated is worse than none, because it sends people to the wrong place with confidence. Verify the result in the catalog UI: the component page should show the owner group, the on-call reference, and a link back to the repository, and a deliberately broken owner value should fail CI.