pinup: why I replaced my Renovate runner with a Go binary
For years, Renovate opened merge requests for our repositories, lately about 200 of them. Reliably, as far as anyone could tell. I still replaced it in September. Not because of the updates it made. Because of the updates it did not make, without saying so.
Its successor is called pinup. A static Go binary that reads the existing Renovate configuration, writes a plan before every change and states the reason for every update it holds back. It is open source, under Apache-2.0.
01 — Renovate ran. That was the problem
My setup was not small. Seven scan partitions every hour, a fast lane for my own releases, a default.json of 88 KB with 25 custom managers. Every hourly run cost 40 minutes of wall time and 128 runner minutes. On top of that came a Node sidecar for the apk datasource that Renovate does not have.
The real cost was something else. The failures were silent. An automatic config migration wrapped the fileMatch regexes twice. After that, not a single custom manager ran. A group rule compared the Composer name, but Renovate matched the internal package name. The group never applied, and the merge requests fought over the same composer.lock. Another lock file was not updated for a whole year.
None of these failures turned a job red. Renovate was green every time. I found them because expected updates did not arrive, not because anything told me.
02 — A plan before the first byte is written
Renovate tells you what it did. It does not tell you what it did not do, or why. That is the gap pinup closes.
Every run first produces a machine-readable plan. pinup whatif stops there, pinup run carries on. Every held-back update carries its reason in the plan, the rule that holds it, and the moment the hold lifts.
lodash 4.17.20 -> 4.17.21 ready
redis 20.13.4 -> 28.1.0 dependencyDashboardApproval packageRules[3]
The first line becomes a branch on the next run. The second names the rule that holds the major and where it lives. With --now, pinup plans for another moment. That is how I check a schedule or a minimumReleaseAge without waiting for it.
What a version label does not tell you
pinup sees two more things I was missing. A tag whose digest changed is not an update. For latest that is normal. For v2.10.0 a published version was rewritten, and the plan says which of the two it saw.
For Helm charts, an analyzer asks what actually changed: the app version, values keys that are gone, subcharts, and the images the chart sets itself. Those images are in no file of my repository, so nothing else would have pinned them. The result is an effective label next to the declared one. The stricter of the two decides automerge.
03 — Switching without changing a line of configuration
pinup reads the existing renovate.json: extends, presets, packageRules, custom regex managers, schedules, minimumReleaseAge, lock-file maintenance, postUpgradeTasks, OSV alerts and the dependency dashboard. pinup migrate reports, key by key, what a configuration uses and what pinup supports.
Branch names and merge request titles are the same as Renovate's. pinup adopts open branches. The switch did not create a single duplicate merge request.
Compare first, then switch
Before the switch, pinup ran in shadow mode. Run after run, it compared its plans with the merge requests Renovate had open. A control repository that must differ keeps the comparison from confirming itself. I switched only once the two agreed.
Clean room instead of a fork
Renovate is AGPL-3.0. I could keep the configuration language, but not read the code. So pinup is a clean-room rebuild. I ran the pinned Renovate container and rebuilt its behaviour from recorded input and output pairs. Those pairs are now the test suite. pinup itself is Apache-2.0, like all my Go tools.
04 — What changed, measured
The hourly run across all repositories now takes 10.6 instead of 40 minutes of wall time, and 75 instead of 128 runner minutes. pinup has no runtime and two third-party dependencies. The Node sidecar is gone.
The chain behind it matters more. On 25 September I measured it end to end, on a real release of this blog:
| Time (UTC) | Step |
|---|---|
| 07:02 | Blog package release 3.23.0 |
| 07:03 | pinup opens the merge request in the website app |
| 07:10 | Merge request merged, app release follows |
| 07:19 | pinup opens the deploy MR in the GitOps configuration |
| 07:48 | pinup merges the deploy MR |
| 07:57 | Change is live |
Just under an hour from tag to production, without me stepping in. A second release landed in the meantime. pinup moved the still-open deploy MR up to it instead of opening a second one.
Frequently asked questions
Do I have to change my renovate.json for pinup?+
No. pinup reads the existing configuration as it is. Beforehand, pinup migrate reports key by key what the configuration uses and what pinup supports. Branch names and titles stay the same, and open branches are adopted.
How risky is switching from Renovate to pinup?+
You can do it in stages. pinup whatif changes nothing and needs no token. Next comes a --dry-run against a real project. In shadow mode, pinup compares its plans with Renovate's open merge requests. Switching only pays off once the two agree.
Does pinup work with GitHub?+
The GitHub integration is implemented. It is tested against a fake of the API and in a read-only run against its own repository. A first production repository on GitHub is still pending. In production, pinup so far runs on GitLab, across about 200 repositories.
Why is pinup not a fork of Renovate?+
Renovate is AGPL-3.0, and a fork would have inherited that licence. pinup is a clean-room rebuild: behaviour was observed on the pinned Renovate container and rebuilt from recorded input and output pairs. No code was copied. That is why pinup can be Apache-2.0.
Conclusion
pinup does not replace Renovate's configuration language, it replaces its runner. The rules stay. Operations get cheaper and, above all, more honest: when an update does not come, the plan says why. For me that was the real reason. The saved runner minutes are the bonus.
Code, releases and documentation are on GitHub. An overview with a quickstart is on the pinup project page.
I move your Renovate setup to pinup. Or tell you why it is not worth it for your setup.
A configuration check with pinup migrate, shadow operation next to your existing runner, and a switch that does not duplicate a single merge request.
Platform operations rather than advice on paper: if you want, I also run the dependency automation for you on an ongoing basis, including the rules that explain why an update is waiting.
About the author
![[Translate to English:] Foto von Kai Ole Hartwig.](/fileadmin/_processed_/e/9/csm_ole-neu_73323ad80d.jpeg)
Kai Ole Hartwig
Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.