Problem Statement: Internal Developer Portal
Problem Statement: Internal Developer Portal — developer portal system design
Problem Statement: Internal Developer Portal
Platform teams at scale (Backstage, Port, Cortex) converge on one thesis: developers should not hunt across GitHub, Confluence, Jenkins, PagerDuty, and cost dashboards to answer "what is this service, who owns it, and how do I ship a new one?"
What you are designing
- Service catalog — typed graph of Components, APIs, Resources, Systems, Domains
- Software templates — parameterized scaffolder that creates repos, wires CI, registers
catalog-info.yaml - TechDocs — docs built from
mkdocs.ymlin the repo, hosted by the portal - Plugins — entity-page tabs for K8s, CI, incidents, cost without forking core UI
Interview anchor numbers (state aloud)
| Signal | Assumption |
|---|---|
| Engineers | 3,000 |
| Registered components | 800 |
| Avg catalog reads / engineer / day | 40 → ~1.4 QPS average (bursty during incidents) |
| Template runs / day | ~200 |
| Ingestion lag target | < 5 minutes p95 |
Why this is not a wiki
Wikis are human-authored and stale. A portal ingests machine-readable metadata from Git (catalog-info.yaml) and automation (K8s, CI webhooks). Ownership and dependency edges are queryable—critical for blast-radius during outages.
Opening line for the whiteboard
"If search cannot surface the right component in one query, adoption dies—catalog and OpenSearch are first-class, not an afterthought."
1 public final class CatalogEntityValidator { 2 public ValidationResult validate(JsonNode spec, String kind) { 3 if (!spec.has("owner")) return ValidationResult.error("spec.owner is required"); 4 if ("Component".equals(kind) && !spec.has("lifecycle")) { 5 return ValidationResult.error("Component requires spec.lifecycle"); 6 } 7 return ValidationResult.ok(); 8 } 9 }
1 from dataclasses import dataclass 2 from typing import Literal 3 4 @dataclass 5 class EntityRef: 6 kind: str 7 namespace: str 8 name: str 9 10 def entity_key(ref: EntityRef) -> str: 11 return f"{ref.kind.lower()}:{ref.namespace}/{ref.name}" 12 13 def allows_action(role: str, action: Literal["read", "scaffold", "admin"]) -> bool: 14 perms = {"viewer": {"read"}, "developer": {"read", "scaffold"}, "admin": {"read", "scaffold", "admin"}} 15 return action in perms.get(role, set())
1 export interface CatalogEntity { 2 apiVersion: "backstage.io/v1alpha1"; 3 kind: string; 4 metadata: { name: string; namespace: string; tags?: string[] }; 5 spec: { owner: string; lifecycle?: string; type?: string }; 6 } 7 8 export function entityRef(e: CatalogEntity): string { 9 return `${e.kind.toLowerCase()}:${e.metadata.namespace}/${e.metadata.name}`; 10 }
Pitfalls to avoid
- Treating the portal as CRUD UI without ingestion processors
- Letting teams edit catalog only in UI (drifts from Git SoT)
- Skipping RBAC on templates that touch production cloud accounts
How to open this one
The framing that signals depth on an internal developer portal is the service catalog plus golden paths: one place that knows every service, its owner, its docs, and its health, with scaffolding templates that make the paved road the easy road. Lead with the catalog as machine-ingested source of truth (Backstage-style, fed from catalog-info.yaml in Git) rather than a hand-edited wiki, and the failure story that proves it: ownership goes stale, an incident hits a service nobody claims, and the catalog's dependency edges are the difference between fast blast-radius routing and a scramble. That shows you understand the portal reduces cognitive load, not just adds another dashboard.
Key Highlights
- •Portal = catalog + search + templates + TechDocs + plugins
- •Git catalog-info.yaml is the metadata source of truth
- •~1.4 QPS average catalog reads at 3K engineers
Section Rescue Kit
Buzzwords to use:
Safe statements:
- "I will separate catalog ingestion, search index, and scaffolder workers before discussing UI plugins."
- "Happy to deep dive Git-as-SoT policy or RBAC on production templates—your choice."