Code Agency
3 min read

Renovate on a self-hosted stack: dependency updates that merge themselves

Hundreds of images, charts and npm packages age every week across our clusters. Renovate turns that into reviewed pull requests, and CI gates plus a minimum release age turn most of those into merges nobody has to click.

A typical client stack we run has a package.json, a handful of Dockerfiles, and a dozen Helm charts — Postgres, Redis, cert-manager, ingress-nginx, the works. Every one of those has a version number, and every one of those numbers goes stale the moment it's pinned. Multiply by the number of clusters we operate and "check for updates" stops being a task a person can own.

So a person doesn't. Renovate runs as a bot against every repo, opens the PR, and — for the updates we've already decided are safe — merges it too.

One bot, every ecosystem

Dependabot only really knows package managers. Renovate reads package.json, Dockerfile FROM lines, Helm Chart.yaml dependencies, and our Woodpecker pipeline images from the same config, and proposes updates for all of them as one steady stream of small PRs instead of four separate tools with four separate blind spots:

renovate.json5
{
  extends: ["config:recommended"],
  timezone: "Europe/Brussels",
  minimumReleaseAge: "3 days",
  packageRules: [
    {
      matchUpdateTypes: ["patch", "minor"],
      matchCurrentVersion: "!/^0/",
      automerge: true,
    },
    {
      matchDatasources: ["docker", "helm"],
      matchUpdateTypes: ["major"],
      automerge: false,
      labels: ["needs-review"],
    },
  ],
}

That minimumReleaseAge line is the same discipline we wrote about when we locked pnpm to a one-day release window — Renovate just applies it one layer up, to the images and charts pnpm never touches. A version has to survive three days in the wild before Renovate will even propose it. Most compromised releases get pulled within that window; we simply never see them.

CI gates decide, not a human

An automerge: true rule is only as trustworthy as what has to pass before it fires. Every Renovate PR runs through the exact same Woodpecker pipeline as a human-authored one — lint, typecheck, build, the test suite — and merges only on green:

.woodpecker.yml
steps:
  install: { image: node:26-slim, commands: [corepack enable, pnpm install --frozen-lockfile] }
  quality:  { commands: [pnpm lint, pnpm typecheck] }
  test:     { commands: [pnpm test] }
  build:    { commands: [pnpm build] }

No separate "trust the bot" code path — a patch bump that breaks the build fails the pipeline exactly like a bad commit from any of us, and automerge simply never triggers. We're not trusting Renovate; we're trusting the pipeline it has to go through.

Grouping, so review stays sane

Ungrouped, a cluster running ten Helm charts generates ten separate PRs every time upstream has a quiet week. We group by blast radius instead:

{
  packageRules: [
    { matchPackagePatterns: ["^@opentelemetry/"], groupName: "opentelemetry" },
    { matchDatasources: ["helm"], matchPackageNames: ["cert-manager", "ingress-nginx"], groupName: "cluster platform" },
  ],
}

Related packages land in one PR with one changelog to skim, and the diff reads like a decision, not a diff.

What still needs a human

Major versions, anything touching cluster platform, and any package still on 0.x never auto-merge — those PRs sit open with a needs-review label until someone looks at the changelog on purpose. That's deliberate: automation should absorb the volume of safe changes so the team's attention goes to the handful of updates that actually deserve it, not the other way around.

This is the same policy we run on every cluster we host: dependency hygiene as a pipeline property, not a recurring calendar reminder nobody gets to before the sprint ends.

Want us to publish something specific?

Tell us what you'd like to read and we'll add it to our writing queue.

Get the next one in your inbox

New articles, videos and the occasional engineering note — a short mail when there’s something worth reading, nothing else.