Skip to content

Renovate presets

phpboyscout Renovate policy lives in a set of composable presets hosted in this repo. A consumer's renovate.json is just the leaves its project is made of — Renovate merges them. Change a preset here, and every repo that extends it picks the change up on its next run.

Pair these with the renovate-group bot (which runs Renovate across the whole group) — the bot decides when and which repos; these presets decide policy.

How composition works

extends is an array. Each leaf's rules are scoped by matchManagers, so combining leaves never collides — a :go leaf only touches gomod, a :javascript leaf only npm. A project extends the bag of capabilities it has:

{ "extends": ["gitlab>phpboyscout/cicd:go", "gitlab>phpboyscout/cicd:application"] }

Every leaf already extends the base, so you never name the base yourself.

The base (root)

gitlab>phpboyscout/cicd — extended (directly or via a leaf) by every repo. It carries the universal policy:

  • the custom manager that tracks gitlab.com/phpboyscout/cicd/<component>@vX.Y.Z pins in .gitlab-ci.yml, and automerges those first-party pins on green;
  • two custom managers for tool versions pinned inside .gitlab-ci.yml, which no stock manager can see — gomod reads go.mod, not shell commands, so a CLI a pipeline depends on stays frozen indefinitely and the failure mode is an absence of MRs:

    • go install gitlab.com/phpboyscout/<tool>/cmd/<bin>@vX.Y.Z — matched directly, with depName captured from the module path so one rule covers every first-party tool;
    • any CI variable carrying a version, when annotated on the line above:
    variables:
      # renovate: datasource=gitlab-tags depName=phpboyscout/go-tool-base
      GTB_VERSION: v0.36.1
    

    This is the escape hatch for the common GTB_VERSION shape, where the variable name says nothing about which module it pins and the go install below references it as ${GTB_VERSION}. It is opt-in by annotation — an unannotated variable is untouched, so the rule is inert wherever it is not wanted, and the annotation is not restricted to Go or to GitLab (datasource=docker depName=alpine works the same way); - osvVulnerabilityAlerts + security-fix fast-tracking (no soak, automerge); - patch + minor automerge on green (platformAutomerge), majors drafted for review; - the pre-commit manager, enabled — Renovate ships it opt-in (documented as beta and off unless a config turns it on), so without this a repo with a .pre-commit-config.yaml gets no hook updates and no signal that it is missing them: the failure mode is an absence of MRs, indistinguishable from "nothing to update". It sits in the base rather than an ecosystem leaf because .pre-commit-config.yaml is language-agnostic — the repos carrying one span the Go, Tofu and mixed tracks. It is a no-op where no such file exists. See spec 0062; - house posture: semantic commits, immediate PRs, strict internal checks, a 3-day soak for third-party updates (first-party is exempt), dependencies label.

Leaves

Leaf Extends Adds
:go base group all gomod into one MR (rangeStrategy: bump); postUpdateOptions: [gomodTidy, gomodUpdateImportPaths] so major bumps rewrite /vN import paths + land a tidy manifest
:rust base group all cargo into one MR
:javascript base group all npm into one MR
:tofu base group terraform providers/modules; prHourlyLimit: 4; re-point required_version at OpenTofu and track .opentofu-version — see OpenTofu, not Terraform
:docker base group Dockerfile/compose image bumps
:python base group pip/poetry/pipenv — bolt onto any repo with stray Python
:mise base group patch-level mise tool bumps — for image-build repos that pin their toolchain via mise
:hugo :javascript + Hugo modules (gomod) for a Hugo site
:zensical :javascript a Zensical site (toolchain baked into infra-tools)
:library base keep version ranges flexible (replace) — reusable modules
:application base pin version ranges — reproducible builds / executables

OpenTofu, not Terraform

Renovate's terraform manager hardcodes hashicorp/terraform as the depName for every required_version constraint. From the shipped extractor:

dep.depType    = 'required_version';
dep.datasource = GithubReleasesDatasource.id;
dep.depName    = 'hashicorp/terraform';   // not a default — hardcoded

No manager option changes it, so an OpenTofu estate gets its core constraint bumped against a different product on a different version line. On 2026-08-17 Terraform was at 1.15.8 and OpenTofu at 1.12.5, so the offered ~> 1.15.0 was a constraint no OpenTofu release satisfies — it broke tofu init, validate and plan on every stack in phpboyscout/infra.

:tofu fixes it two ways:

  • required_version is re-pointed, not disabled, with overrideDepName / overridePackageName. Everything else the extractor set is already right for OpenTofu — github-releases, v-prefixed extractVersion, hashicorp versioning — so only the repository changes, and the merge request is named renovate/opentofu-opentofu-1.x rather than claiming to update Terraform.
  • .opentofu-version is tracked by a custom manager. That is the file tenv/mise actually read, no built-in manager sees it, and before this it received no update merge requests at all.

Declaring customManagers in a leaf is safe: the option is mergeable: true, so it concatenates with the base preset's rather than replacing them. The base component-pin tracker keeps working. (labels and extends are not mergeable and genuinely are replaced.)

See spec 0072.

Ecosystem and role are orthogonal: pick one ecosystem (or more), plus one role.

Recipes

// go library
{ "extends": ["gitlab>phpboyscout/cicd:go", "gitlab>phpboyscout/cicd:library"] }

// go CLI with a JS frontend
{ "extends": ["gitlab>phpboyscout/cicd:go", "gitlab>phpboyscout/cicd:javascript", "gitlab>phpboyscout/cicd:application"] }

// rust crate
{ "extends": ["gitlab>phpboyscout/cicd:rust", "gitlab>phpboyscout/cicd:library"] }

// tofu module
{ "extends": ["gitlab>phpboyscout/cicd:tofu", "gitlab>phpboyscout/cicd:library"] }

// hugo site
{ "extends": ["gitlab>phpboyscout/cicd:hugo"] }

// a go service that also has a python helper
{ "extends": ["gitlab>phpboyscout/cicd:go", "gitlab>phpboyscout/cicd:python", "gitlab>phpboyscout/cicd:application"] }

Anything genuinely repo-specific still goes in that repo's own renovate.json below the extends — it is applied last and wins.

See also