Project timeline | August 8 – 20, 2026
Start to live in twelve days
The full arc of the Backstage refresh: a 106k-line legacy monolith on day zero; a production GitOps deployment serving at its own URL on day twelve. Every entry below is dated from the project's decision log, status reports, and pull-request history — including the days things broke.
12days, plan → production
5–10×faster than estimated
71decisions recorded on the way
~1,900tests written
The timeline
Fri Aug 8
Planning: the shape of the migration
- Two planning rounds set the architecture in one day (decisions 1–8): the existing repo becomes the new instance and is progressively emptied; business logic moves to one repository per topic following community-plugins conventions; the Business-Unit layer is dropped in favor of GitHub organizations; RBAC is rebuilt as a two-tier org-scoped model.
- Full phased plan through legacy decommission, with per-phase estimates — the baseline the 5–10× speedup is measured against.
Sat Aug 9
Phases 0–3 complete in one day
- Foundation (Backstage pinned, changesets, release scaffolding), the Grafana pilot repo, the ~20k-LOC github-org family rewrite, and the from-scratch two-tier RBAC build — originally estimated at 4–7 weeks, done in a day with the AI-assisted workflow, every package at ≥80% coverage.
- BU→org mapping approved and the annotation namespace decided (decisions 9–13); instance boots on the new auth stack.
Sun Aug 10 – Tue Aug 12
The heavy ports
- Terraform workspace: 14 packages, ~75k LOC, 1,141 tests — estimated 2.5–4 weeks, landed in about two days. Dataproxy and infoblox follow.
- Distribution v1 decided: immutable per-package GitHub Releases with changeset-driven SemVer (decision 14).
Wed Aug 13 – Thu Aug 14
Alignment and the cutover clock starts
- Platform alignment meeting fixes the timeline (decisions 15–21): refresh live in a new namespace on the existing AKS cluster, both instances coexist, no legacy code freeze — merged legacy PRs are ported forward continuously.
- Technical review workshop held Aug 14, including a walkthrough of the AI-assisted migration workflow; reviewer sign-off becomes the hard gate for any DNS/auth switchover.
- Business Units return in a constrained, org-aligned form (decisions 22–27) — a real requirements reversal absorbed mid-flight.
Fri Aug 15 – Sun Aug 17
Hardening, guardrails, and the k8s scaffold
- All six plugin repos pushed to GitHub with release trains running; release immutability enabled everywhere and the changeset version-bump promoted to a CI-enforced check (decisions 32–34).
-
Kubernetes/Flux scaffold written: base manifests, ExternalSecrets overlay with zero plaintext secrets, image automation targeting
the dedicated
backstage-refreshnamespace. - GitHub API rate-limit remediation designed and shipped (config + a released plugin version with caching, timeouts, and fail-loud credentials).
Tue Aug 18
The registry pivot
- The vendored-tarball workflow is failing; decision: stand up a private npm registry before deploying (decisions 39–43) — Verdaccio via Flux HelmRelease, registry-only distribution, engineered as a stop-gap with a config-only exit.
- Self-service infrastructure in one evening after the shared resources proved inaccessible: own Key Vault (working around missing role-assignment rights), workload-identity federation, own container registry, Flux deploy key and flux-system secrets.
Wed Aug 19
Bring-up day: everything breaks once, nothing breaks twice
-
First Flux reconciles surface and resolve, in sequence: a gitignore pattern silently eating ESO manifests, a missing
workload-identity annotation, and a registry host with no working DNS — fixed by discovering the
.cloudwildcard already pointed at the public controller (zero DNS/cert ops). - Reachability reality check: no ingress on the cluster is internet-facing, so GitHub-hosted runners can never reach the registry → CI moves in-cluster (decisions 44–45). A foreign runner-controller silently deleting our resources is diagnosed with a 13-version probe matrix and blocked with an admission policy.
- Registry live and backfilled: every released package version republished and integrity-verified; all seven repos switched to basic-auth idents after root-causing a Verdaccio limitation from source.
Thu Aug 20
Live in production 🎉
- Custom runner image built in-cluster with kaniko (the corporate proxy blocked the upstream registry from workstations); the full CI gauntlet goes green on in-cluster runners — two node versions plus a Docker-in-Docker image build.
-
The first production deploy runs hands-off end to end: image push → Flux image automation commits the tag bump → rollout →
readiness green at
backstage-refresh.dev.providesops.io, ~04:20 UTC. - Infrastructure architecture documented as a TechDocs page inside the portal itself, lifecycle diagrams included.
Aug 21 – 26
Parallel run: hardening and cleanup
- Legacy and refresh run side by side; catalog-migration dry runs land the scaffolder-skeleton exclusion rule after a false-positive scare (decision 52).
- Documentation sweep aligns the Grafana plugin's config keys to upstream camelCase convention, a breaking change shipped as a major version with the matching instance config change staged to deploy in lockstep (decision 53).
Wed Aug 26
Cutover-readiness review: technically done, gate shifts to communication
-
After roughly two weeks running parallel with legacy, a formal review confirms smoke tests, deployment/rollback scripts, and
the legacy rollback alias (
aspirations-legacy.dev.providesops.io) all validated; theme extraction into its own plugin is committed as a follow-up. - Stakeholder communication content is agreed (primary URL, legacy availability, expected differences, rollback plan, timing, issue reporting) with a named recipient-gathering owner — the review concludes readiness is a communications problem now, not an engineering one.
Thu Aug 27
Refresh becomes the primary environment — no DNS swap
-
Decision 54 drops the planned DNS repoint entirely: the refreshed instance gets its own permanent host,
portal.dev.providesops.io(production later atportal.providesops.io), while every legacyaspirations.*hostname stays untouched. portal.dev.providesops.iois announced as the primary environment for new feature development; legacy moves to maintenance-only (bug fixes only) rather than being frozen out entirely.
Fri Aug 28
Branding moves into a plugin repo
-
A new
backstage-plugins-themerepository takes over the portal's look-and-feel as a hard "everything lives in a plugin" rule (decision 55) — no shell-code exception, even for theming. Ships with its own CI, release train, and in-cluster runner scale set; first release published the same day.
Mon Aug 31
Legacy port wave: no code freeze, proven at scale
- A full sweep of a colleague's month of legacy pull requests — merged stacks, singletons, and every closed-unmerged branch verified superseded — produces six port pull requests in one day across github-org, dataproxy, and terraform, all changeset-gated with local and remote CI green.
Tue Sep 1
Stakeholder update: parity confirmed, template validation next
- Weekly platform-engineering sync: refresh reported successfully deployed and ready to direct users to, the 80% coverage goal met, and entity-model changes landed so users no longer need personal IMP-organization access under the cross-organization template model.
- Next focus set explicitly as validating every template path — deployment is framed as a milestone, not the finish line.
Thu Sep 3
CI modernization and catalog namespace enforcement
- A repository-wide sweep modernizes CI installs across five plugin repositories and cleans up two stale working trees duplicating already-merged work.
- Entity namespace enforcement is hardened end to end: the catalog-migration script now forces the namespace to the owning GitHub organization instead of flagging a conflict (decision 56), with a matching runtime catalog processor and an explicit shared-organization opt-out for company-wide templates and resources.
- Diagnosis-only sessions catch a duplicate in-flight fix (a scaffolder picker multi-select already solved correctly in an open pull request) and confirm a private-registry authentication gap in a Backstage CLI tool, both before any code changed.
Fri Sep 4
Fleet-wide 1.0.0 stable promotion, and a new integration stream opens
- Every remaining 0.x package across all seven plugin repositories gets a major changeset to 1.0.0 — seven pull requests, all checks green, changeset-only. Signals the plugin fleet has left its extraction-era pre-1.0 phase.
- A read-only Backstage-to-ServiceNow integration kicks off in parallel: service-account request scoped and submitted, with password-rotation handling flagged for the eventual operations handoff.
Sep 5 onward
Planned Beyond 1.0.0
- Merge the seven 1.0.0 pull requests and their version-bump trains, then the cross-repository dependency-range bumps and the instance's own pin wave.
- Swap the instance's interim theme code for the released theme package; continue the scaffolder template train and per-org service skeletons.
- Design the production namespace and QA-to-production promotion workflow before the non-dev production domain goes live.
- Continuous legacy-PR porting continues indefinitely under the no-freeze policy; total completion (all templates, all products onboarded, roles documented) still targeted for end of September.
What made the pace possible
Calibration, honestly stated
- Engineering compressed 5–10× under estimate (a ~20k-LOC rewrite in a day; ~75k LOC in two) through an AI-assisted porting workflow with human review gates, standing conventions, and a decision log that kept every reversal cheap.
- What did not compress — and therefore set the schedule — were decision gates, ops dependencies, review cycles, and the parallel-run window. The infrastructure work eliminated the ops dependencies it could (self-service vault, registry, ACR, CI) and documented the rest.
Supporting sources:
migration roundup decisions log (71 decisions, rounds 1–27), timeline and calibration tables, work-done summary and work-remaining
tracker; daily status reports; Aspirations-Backstage pull requests #8–#25; ai-state save-off notes 2026-08-25 through 2026-09-22