A year after the team’s repositories had been moved from a self-hosted GitLab installation to GitHub, the per-repo CI workflows had grown into a maintenance problem. There were around fifty repositories with near-identical workflow files, and any improvement to the build steps had to be made dozens of times. This post is about consolidating those workflows behind a small set of reusable ones.
The Drift Problem
The original per-repo workflows were hand-rolled and copy-pasted, because reusable workflows were not yet GA when the migration started. Over a year of daily edits, the files drifted. Some repositories had the latest version of the build steps; others had the version that worked nine months ago. A bug fix or a base-image update meant opening a PR against every relevant repository, reviewing them all, and merging in the same week to keep behaviour consistent. This did not scale.
The Reusable-Workflows Framework
The new layout is three reusable workflows in a dedicated build-actions repository:
- one for Debian packaging repositories,
- one for Java repositories,
- one for repositories that produce both a Java artifact and a Debian package.
A per-repo workflow is now a single uses: line that points at one of
those reusable workflows, with a few inputs for the variant (target Debian
distribution, JDK version, package name). The shared work (checking out
the source, configuring the toolchain, building the artifact, uploading it
to JFrog Artifactory, posting a status) lives in one place.
The build-actions repository also owns a provisioner workflow that adds or updates the per-repo workflow file across the entire matrix in a single run. Adding a new build flag is now one PR against the reusable workflow, plus a provisioner run to refresh the thin per-repo files.
Stakeholder Onboarding
Migration of the per-repo workflows was done in waves: first a small batch of low-traffic repositories to shake out the reusable-workflow inputs, then the language-family batches (Debian first, Java next, mixed last), then the awkward edge cases. Each batch involved coordinating with the repository maintainers, agreeing on which inputs they needed, and replacing the legacy workflow file in a PR. By the end of the wave there were about fifty repositories on the new framework.
A status dashboard summarises the CI state of every repository on the framework, with a build badge per repository and a link to the latest run. This made the onboarding visible to the team and surfaced regressions quickly when a reusable-workflow change broke something downstream.
Sunset
The legacy private actions that the per-repo workflows used to call have been sunset. The central CI orchestrator repository is still in place but its surface area is much smaller — it owns the artifact-registry-touching workflows and the runner monitoring, and that is all. The day-to-day path for adding a new repository is now to call the appropriate reusable workflow with the right inputs, rather than to copy a workflow file from a similar repository and modify it.
Added Later
The provisioner workflow has since been extended to manage the branch protection rules and the required status checks across the matrix, so onboarding a new repository is genuinely one command. The reusable workflows now also produce job summaries with a per-step breakdown of what was built and uploaded, which has cut the time to debug a failed build to roughly the time it takes to read the summary.