Until last week each of my public repos carried its own copy of CI, Dependabot config, a security audit and a release workflow. Four repos, four copies, and they had drifted the way copies always do: one pinned actions by tag and another by commit, one audited every week and another never, and in one of them a new push could cancel a release halfway through publishing the image.
So on 25 September I pulled all of it into one place: KN990x/.github. It went from v1.0 to v1.2 in two days, and PullPilot, vuzon and EasyPages now release through it. Glassys, the newest one, uses the shared CI check too.
What lives there now
validate-config is the first step of every CI run: actionlint (with shellcheck) over the workflows and a schema check of dependabot.yml. That last one sounds like overkill until you remember that an invalid dependabot.yml doesn't fail anything. Dependabot just quietly stops, and you notice months later.
majors-report is a monthly security audit that rewrites a single "Pending major updates" issue per repo: the majors Dependabot won't open by itself, the packages held back on purpose, and every runtime against its end of support. One issue, always current, instead of a pile of PRs I'd close without reading.
ghcr-publish is the release pipeline. It checks that the tag matches the version, checks that CI actually passed for that exact commit, smoke-tests the image on amd64 and arm64, and only then pushes a multi-arch image with provenance and an SBOM. Once a month it runs as a dry run that doesn't push anything, so if something rots in the pipeline I find out before a release instead of during one.
dependabot-merge lets routine Dependabot PRs merge themselves once CI is green and there's an approving review on the current head. A major, a PR that touches anything other than dependency files, or one with a commit pushed on top never qualifies.
fleet-check opens a monthly "Fleet status" issue with the state of every public repo: CI, audit, release dry run, and which version of the shared pieces each one pins.
Each repo pins a release of this repo by commit SHA and moves that pin through its own Dependabot PR, so a change in the shared pieces goes through each repo's CI before that repo uses it. The shared repo tests itself too: the majors report runs against a deliberately stale fixture project, and the whole release pipeline builds a tiny image for both architectures on every change.
The small rules that came along
Putting everything in one place forced a few decisions I'd been putting off:
- Every GitHub Action is pinned by commit SHA, and every Docker base image by digest. Patches for them now arrive as Dependabot PRs I can read, not as silent changes under a moving tag.
- Dependabot runs monthly instead of weekly, with security updates in their own group so they never wait for the batch.
- Release permissions are scoped per job, and a newer run can no longer cancel a publish that's already running.
Less noise, and when something does show up it's worth a look. That was the whole point.
And what shipped just before
The pipeline came right after a round of fixes that went out on 3 September as PullPilot 2.2.5, vuzon 2.0.5 and EasyPages 2.1.4. The common thread is a bit embarrassing: places where the panel said everything was fine when it wasn't.
- PullPilot. Creating a schedule could reset the next run of every other job, so that day's cron simply didn't happen. A failure saving state after a perfectly good deploy marked the deploy as failed. And the UI only followed updates launched from the same browser tab.
- vuzon. An empty or malformed listing from Cloudflare was taken as a complete, empty page, so deleting a destination could skip aliases that were still using it. It now fails closed. I also tightened how sessions are revoked after a password change.
- EasyPages. Running it in Docker over plain HTTP, the session cookie was marked Secure, so the browser never kept it and login just didn't stick. Cloudflare replies with
success: falselooked like empty lists, account lookup stopped after the first page, and the recommended compose file pointed at an image tag that was never published.
If you run any of the three, update. And if you maintain a handful of small repos yourself, the idea is worth stealing: one repo for the shared pieces, pinned by SHA everywhere, tested against fixtures. It's less work than it sounds, and a lot less than keeping four copies in sync by hand.
Comments