Skip to content

Adopt Port (getport.io) as the internal developer portal; decommission Backstage #2637

Description

@devantler

🤖 Generated by the Daily AI Engineer

Problem

The platform runs Backstage, and it delivers nothing today while charging real operational rent.

It is a demo, by design and by admission. docs/backstage.md labels it "Status: scaffold", and it runs the upstream demo image (ghcr.io/backstage/backstage:1.52.0) with an example catalog and example plugins. The manifest states the reason plainly: the Kubernetes plugin and GitHub catalog discovery are "compiled in at build time and CANNOT be enabled through values". So the thing that makes a portal a portal — a real catalog of our services — is simply not there, and cannot be switched on.

The only documented path to value is owning a codebase. docs/backstage.mdProductionisation requires:

  1. scaffolding a new devantler-tech/backstage repo with @backstage/create-appa TypeScript monorepo we own, build, and upgrade forever;
  2. publishing our own image via publish-app.yaml;
  3. a Dex static OIDC client + sign-in resolver (replacing the forward-auth transport);
  4. a read-only Kubernetes ServiceAccount/ClusterRole, a GitHub App, and an api.github.com netpol egress.

That is four workstreams and a permanent maintenance surface before the portal does anything useful.

Meanwhile it already costs us: a CloudNativePG database (2 instances, synchronous, Longhorn), forward-auth SSO, an HTTPRoute, a CiliumNetworkPolicy, and VPA interplay. On 2026-07-14 a routine rolling node reboot put it into CrashLoopBackOff at 17 restarts, 0/1 ready — it could not survive a cold start (a 30s startup budget against a ~60s boot; fixed in #2636). It was only ever "healthy" because nothing had restarted it.

Net: all cost, no benefit, and the way out is the expensive way.

Proposed direction

Adopt Port on its Free tier and decommission Backstage.

Port is a hosted internal developer portal. Its Free tier is $0 — up to 15 seats, 10,000 catalog entities, 500 automation runs, full platform, no card (pricing). We have ~14 repos and a handful of services, so we would use a rounding error of that quota, indefinitely. Crucially it is configuration-driven, not compile-time: the catalog, scorecards and self-service actions we actually want need no owned codebase.

Decommissioning Backstage also removes infrastructure rather than adding it — the CNPG database, the forward-auth wiring, the netpol, the route and the crash-prone deployment all go away.

The trade-off, stated explicitly

This platform is deliberately SaaS-free: Coroot was chosen precisely as "Community Edition, fully self-hosted — no SaaS tier, no remote-write" (docs/dr/alerting.md), and OpenBao, Longhorn and Velero→R2 follow the same principle. Port would be the first SaaS in the estate, and it is not passive — it needs a Kubernetes exporter with cluster read access and a GitHub App with repository read. Metadata about our repos and cluster topology would leave the estate.

Accepted, with reasons: a portal is not a control plane. If Port disappears, production keeps running — categorically unlike putting secrets or observability in SaaS. And the blast radius is boundable.

Mitigations (part of the definition of done):

  • Kubernetes exporter: read-only, least-privilege ClusterRole, no write/exec.
  • GitHub App: scoped to public repos + platform initially. Private tenants (wedding-app, ascoachingogvaner, unifi) are excluded from discovery until we consciously decide otherwise.
  • No secrets, no runtime data, no logs — catalog metadata only.

Scope

  • Stand up Port — free workspace; GitHub App scoped as above; read-only Kubernetes exporter. Catalog populates.
  • Port the intent — the golden-path/scorecard/self-service value Backstage was meant to deliver, expressed in Port's config.
  • Decommission Backstage — remove k8s/bases/apps/backstage/ (HelmRelease, CNPG backstage-db, HTTPRoute, CiliumNetworkPolicy, backend-secret Password + ExternalSecret), its provider/cluster overlay entries, and its Dex/forward-auth wiring. Retire the backstage.${domain} record.
  • Docs — replace docs/backstage.md with docs/portal.md; update AGENTS.md / stack map references.

Each step is independently shippable; split into child issues when work starts.

Rough size

M. Standing Port up is S (config, no code). The decommission is a clean subtraction. The bulk is deciding what the catalog and scorecards should actually say — which is the work Backstage was deferring anyway.

Notes

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions