In a nutshell
Renovate is a bot that watches every dependency your projects rely on — npm and PyPI packages, Docker base images, GitHub Actions, Terraform providers, Helm charts — and, whenever a newer version ships upstream, opens a pull request that bumps it for you, updates the lockfile, and links the changelog so you can see what changed before you merge. You configure it as code, once, and it does the tedious watching-and-bumping forever.
Picture the perishable stock in a busy kitchen’s pantry. Every tin has an expiry; if nobody restocks, one day you open the cupboard mid-service and half the shelf is out of date at once — and now dinner is a scramble. Renovate is the night-shift stockroom clerk. Every night it walks the shelves, and for the boring staples — flour, salt, the dev tools nobody tastes directly — it quietly restocks and files the paperwork (a pull request); if the kitchen’s automated taste-test (your CI) passes, the fresh tin goes straight on the shelf before morning service (auto-merge). For the one hero ingredient the whole menu is built around — a major framework upgrade — it does not touch it; it leaves a note for the head chef to decide. And the moment a health inspector flags a contaminated batch (a published CVE), it drops everything and swaps that tin immediately, no waiting for the night shift.
Why a beginner should care: keeping dependencies current is the single highest-leverage security and maintenance habit a team has, and doing it by hand does not scale past a handful of repos. Renovate turns “someone should really update these” into an automated stream of small, reviewable, testable changes — and lets you set the policy once for an entire organization instead of nagging each team. What makes it more than a dumb version-bumper is exactly the boring 20% of its config: grouping, scheduling, rate-limiting, and auto-merge policy. That machinery is the difference between a tool people love and one they mute on day two.
The diagram traces one update left to right: Renovate’s managers discover dependencies per file, one shared preset encodes org-wide policy, updates are grouped, scheduled, and rate-limited into a trickle of PRs (with the dependency dashboard as the control surface), each PR is gated on required CI checks and a release-age bake, and only then does the safe 80% auto-merge while majors wait for a human.
Level: Intermediate · Time: ~30 min
Prerequisites & what you’ll be able to do
You will get the most from this lesson if you already know:
- The pull-request flow — branches, diffs, required status checks, merging. If that is shaky, start with Git deep dive: internals, branching, merge, rebase and GitHub Actions fundamentals.
- What semantic versioning (semver) is:
major.minor.patch, and why a major bump can break you while a patch usually will not. - Roughly how CI runs on a pull request — a workflow that must go green before the merge button lights up.
- Helpful but not required: the dependency-scanning half of the picture in Building a DevSecOps pipeline — SCA finds the vulnerable dependency; Renovate is how you fix it at scale.
After working through it you will be able to:
- Stand up Renovate for one repo (the hosted app) or a whole org (self-hosted on a schedule), and explain which model to choose and why.
- Encode org-wide policy once in a shared preset and consume it with a one-line
renovate.jsonin every repo. - Group, schedule, and rate-limit updates so automation produces a steady trickle of reviewable PRs, not a Monday-morning flood.
- Make security fixes jump the queue while routine bumps wait for the overnight window.
- Turn on auto-merge safely — gated on required checks and a release-age bake — for exactly the update classes you trust, and never for majors.
- Steer monorepos per-package, run post-upgrade codegen, read the dependency dashboard, and debug “why didn’t it update X?” with a dry run and
skipReason.
Dependency rot is a slow-motion incident. Every week a repo sits unpatched, the diff to current widens, the CVE backlog grows, and the eventual upgrade goes from a one-line bump to a weekend migration. Dependabot handles the simple case, but once you have dozens of repos, monorepos, internal registries, and a real auto-merge policy, you want Renovate: it speaks far more package managers, its config is composable across an entire org, and its grouping and scheduling controls are what keep automation from becoming a PR flood nobody reads.
This guide sets up Renovate as a fleet-wide platform: one shared preset that encodes policy, grouped and rate-limited updates, vulnerability fixes jumped to the front of the queue, and auto-merge that only fires when CI is green and a package has had time to bake. The goal is the same one I hold every dependency platform to: the boring 80% of updates merge themselves overnight, and humans only look at the 20% that actually need judgment.
Renovate vs. Dependabot: which to reach for, and when
Renovate and Dependabot solve the same problem — keep dependencies current — but at different scales. Dependabot is GitHub-native, near-zero-setup, and genuinely good if you are a small all-GitHub shop that mainly wants security updates. Renovate is what you reach for once “a handful of repos on GitHub” becomes “a fleet across several ecosystems with real policy.” Both read the same OSV and GitHub advisory data for security fixes, so the security coverage is similar; the difference is everything around it.
| Dimension | Dependabot | Renovate |
|---|---|---|
| Setup | Built into GitHub; enable in settings or dependabot.yml |
Hosted Mend app, or self-hosted CLI |
| Platforms | GitHub only | GitHub, GitLab, Bitbucket, Azure Repos, Gitea/Forgejo, self-hosted |
| Package managers | ~20 | 90+ |
| Config sharing | Per-repo dependabot.yml (limited org defaults) |
Shareable presets extends-ed org-wide from one repo |
| Grouping | Yes (groups:), coarser |
Rich packageRules + group:* presets |
| Scheduling | daily / weekly / monthly | Natural-language schedule, per-rule |
| Rate limiting | open-pull-requests-limit |
prHourlyLimit + prConcurrentLimit, priority queue |
| Release-age gate | No | minimumReleaseAge |
| Auto-merge | Via Actions + gh pr merge --auto |
Native automerge + platformAutomerge |
| Control surface | None | The Dependency Dashboard issue |
| Custom/regex managers | No | customManagers (regex) |
| Merge-confidence data | No | Mend Merge Confidence badges |
| Hosting | GitHub-managed | You self-host, or Mend hosts |
The honest summary: if you are one repo on GitHub and want security PRs with no infrastructure, Dependabot is the right first tool and you may never need more. If you have many repos, multiple ecosystems, internal registries, monorepos, or you want a single policy file that governs the whole fleet, Renovate is the platform. The two are not mutually exclusive — some orgs leave Dependabot on purely for GitHub’s security alerting and run Renovate for the actual updates — but running both against the same manifests produces duplicate PRs, so pick one bumper per ecosystem.
1. How Renovate discovers managers, datasources, and versioning
Before configuring anything, understand the three-layer model Renovate uses, because almost every “why didn’t it update X” question maps to one of these layers.
- Manager – the parser that finds dependencies in a file.
npmreadspackage.json,dockerfilereadsFROMlines,terraformreads.tf,helmv3readsChart.yaml,github-actionsreadsuses:pins in workflows. Managers are matched by filename patterns, so a Dockerfile namedDockerfile.buildneeds afileMatchextension to be seen. - Datasource – where Renovate looks up available versions: the npm registry, Docker registry, PyPI, the GitHub releases/tags API, a Maven repo. The manager tells the datasource what to query.
- Versioning – the scheme used to compare and sort versions:
semver,docker,pep440,maven,loose. Pick the wrong one and Renovate either skips updates or proposes nonsense (treating1.10as older than1.9).
You can preview exactly what a manager finds without opening any PRs by running an explain pass against one repo:
LOG_LEVEL=debug renovate --dry-run=full my-org/my-service 2>&1 | grep -E 'depName|datasource|currentValue|skipReason'
The skipReason field is the single most useful debugging output in the tool. disabled-by-config, is-pinned, unsupported-datasource, and invalid-version each tell you which layer to fix.
Renovate’s defaults are conservative on purpose: it will not update a dependency it cannot resolve a datasource for, and it will not change versioning it does not understand. When a dep is silently ignored, run the dry run before assuming a bug.
2. Self-hosting the bot vs. the hosted app, and the platform token
You have two deployment models. Mend Renovate Cloud (the hosted GitHub App, formerly the “Renovate” app on the Marketplace) requires no infrastructure: install it, grant repo access, done. Self-hosting runs the open-source CLI on your own schedule against a platform token, which you want once you need private registries unreachable from the internet, custom run cadence, air-gapped environments, or strict data-residency.
For self-hosting on GitHub Actions, the canonical path is the official action driven by a cron:
# .github/workflows/renovate.yml in your "renovate-config" repo
name: Renovate
on:
schedule:
- cron: '0 */4 * * *' # every 4 hours; Renovate's own schedule narrows this further
workflow_dispatch:
inputs:
logLevel:
default: 'info'
permissions:
contents: read
jobs:
renovate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: renovatebot/github-action@v41
with:
token: ${{ secrets.RENOVATE_TOKEN }}
configurationFile: config.js
env:
LOG_LEVEL: ${{ inputs.logLevel || 'info' }}
The token is the critical decision. A classic Personal Access Token works but ties the bot to a human and PRs show up as that user. The right answer at scale is a GitHub App: create an app, give it Contents: read/write, Pull requests: read/write, and Checks: read, install it on the org, then mint a short-lived installation token at runtime. Renovate reads it from RENOVATE_TOKEN. App identity means a clean bot author, per-repo install control, and no token rotation tied to staff turnover.
// config.js -- self-hosted (global) config, distinct from per-repo presets
module.exports = {
platform: 'github',
autodiscover: true,
autodiscoverFilter: ['my-org/*'],
// Required so PRs that change CI run against your *real* workflows:
onboardingConfig: { extends: ['local>my-org/renovate-config'] },
// Self-hosted-only knobs:
prHourlyLimit: 2,
dryRun: process.env.RENOVATE_DRY_RUN ? 'full' : null,
};
Some options are self-hosted only (
autodiscover,binarySource,dryRun) and are rejected if placed in a repo’srenovate.json. Keep host-level settings inconfig.jsand policy in the shared preset. Mixing them is the most common onboarding failure.
3. One shared preset for the whole org
The thing that makes Renovate a platform rather than per-repo YAML sprawl is config presets: a repo can extends a config published in another repo, and you change policy for the entire fleet by editing one file. Create a renovate-config repo with a default.json:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":dependencyDashboard",
":semanticCommits",
"group:monorepos",
"group:recommended"
],
"timezone": "Europe/London",
"schedule": ["after 9pm and before 6am every weekday", "every weekend"],
"prHourlyLimit": 2,
"prConcurrentLimit": 10,
"rangeStrategy": "bump",
"labels": ["dependencies"],
"packageRules": [
{
"description": "Group all non-major npm devDependencies",
"matchManagers": ["npm"],
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "dev dependencies (non-major)"
}
]
}
Every other repo then carries a one-line renovate.json:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["local>my-org/renovate-config"]
}
The local> prefix resolves on the same platform without a network round trip to the public registry, which matters for GitHub Enterprise Server and self-hosted GitLab. Now group:monorepos and your schedule apply everywhere, and a policy change is a single PR against renovate-config. The config:recommended preset (the modern replacement for the deprecated config:base) supplies the sane defaults you would otherwise re-derive.
4. Grouping, scheduling, and rate-limiting to kill the PR flood
The failure mode of naive automation is forty PRs on Monday morning. Three levers fix it.
Grouping collapses related updates into a single PR. Group by ecosystem so one review covers a coherent set:
{
"packageRules": [
{
"matchManagers": ["github-actions"],
"groupName": "github actions",
"schedule": ["before 6am on monday"]
},
{
"matchPackageNames": ["/^@aws-sdk//"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "aws sdk v3"
},
{
"matchDatasources": ["docker"],
"matchUpdateTypes": ["patch"],
"groupName": "docker base image patches"
}
]
}
Renovate v40+ treats values wrapped in slashes as regex for
matchPackageNames(e.g."/^@aws-sdk//"). The oldermatchPackagePatterns/matchPackagePrefixesfields are deprecated – use the unifiedmatchPackageNameswith bare strings for exact matches and/regex/for patterns.
Scheduling keeps PRs out of working hours so they batch overnight and CI runs when nobody is fighting for runners. Times use Renovate’s own later syntax, anchored to your timezone.
Rate-limiting caps the firehose. prHourlyLimit throttles creation velocity; prConcurrentLimit caps how many open Renovate PRs a repo carries at once. With prConcurrentLimit: 10, Renovate opens its highest-priority ten, and as each merges it backfills the next from the queue – so the backlog drains steadily instead of arriving all at once.
5. Prioritizing vulnerability fixes
Routine bumps can wait for the overnight window. A known-exploited CVE in a transitive dep cannot. Renovate pulls advisory data from the OSV database and GitHub’s advisory feed, and you want those updates to ignore your schedule and limits:
{
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security", "priority/high"],
"schedule": ["at any time"],
"prCreation": "immediate"
},
"osvVulnerabilityAlerts": true
}
Setting schedule: ["at any time"] inside vulnerabilityAlerts overrides the org-wide overnight window for security PRs specifically; prCreation: "immediate" skips the internal stability delay. The osvVulnerabilityAlerts flag broadens coverage beyond GitHub’s feed to the full OSV dataset, which matters for ecosystems GitHub indexes less completely (Go modules, crates, PyPI transitive chains). On GitHub the underlying alerts require Dependency Graph and Dependabot alerts enabled at the org level – Renovate reads them, it does not turn them on for you.
The payoff: a security fix jumps the queue, lands a PR within minutes carrying a security label, and – if you trust your tests – can auto-merge the moment CI passes, which is exactly the behavior we wire next.
6. Safe auto-merge gated on required checks and stability days
Auto-merge is where teams either save hundreds of review-hours or cause an outage. The discipline is simple: never auto-merge anything CI did not vet, and never auto-merge a release that is hours old.
Two safety mechanisms combine. Branch protection with required status checks means Renovate physically cannot merge a red PR – it sets the PR to auto-merge and the platform completes it only after checks pass. minimumReleaseAge (the renamed, current form of the old stabilityDays) refuses to even open a PR until a release has existed for N days, which dodges the all-too-common “package author published a broken patch, then yanked it an hour later” trap.
{
"minimumReleaseAge": "3 days",
"internalChecksFilter": "strict",
"packageRules": [
{
"description": "Auto-merge non-major dev/CI updates once CI is green and aged",
"matchDepTypes": ["devDependencies"],
"matchManagers": ["npm", "github-actions"],
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true,
"automergeType": "pr",
"platformAutomerge": true
},
{
"description": "Never auto-merge majors -- humans only",
"matchUpdateTypes": ["major"],
"automerge": false,
"addLabels": ["needs-human-review"]
}
]
}
platformAutomerge: true hands the actual merge to GitHub/GitLab’s native auto-merge, so it happens the instant the last required check turns green even between Renovate runs – no waiting for the next cron tick. internalChecksFilter: "strict" makes Renovate honor minimumReleaseAge as a hard gate rather than a soft preference.
Auto-merge requires required status checks configured in branch protection. Without them, “auto-merge a green PR” is meaningless – there is nothing defining “green.” If your repos have no required checks, fix that before enabling
automerge, or you are just merging unverified diffs on a timer.
Start auto-merge narrow: dev dependencies, lockfile maintenance, and digest pins only. Promote runtime minor/patch to auto-merge per ecosystem once you trust each one’s test coverage. Majors stay human, always.
7. Monorepos, lockfile maintenance, and post-upgrade tasks
Monorepos break naive tooling because a single version bump must propagate to many lockfiles and workspaces atomically. Renovate handles this natively but you steer it.
The group:monorepos preset (already in our base) keeps families like @nestjs/*, @angular/*, or the AWS SDK on matched versions so you never get a half-upgraded framework. For your own internal workspaces, enable lockfile maintenance to refresh transitive deps that no direct bump would touch:
{
"lockFileMaintenance": {
"enabled": true,
"schedule": ["before 6am on the first day of the month"],
"commitMessageAction": "Refresh lock file",
"automerge": true
}
}
For repos where a dependency bump must trigger codegen, schema regeneration, or a re-vendored file, post-upgrade tasks run commands and commit the result into the same PR. These require self-hosting and an explicit allowedCommands allowlist for safety:
{
"postUpgradeTasks": {
"commands": ["npm run codegen", "npm run format"],
"fileFilters": ["src/generated/**", "**/*.ts"],
"executionMode": "branch"
}
}
// config.js -- the allowlist that authorizes the above
module.exports = {
allowedCommands: ['^npm run codegen$', '^npm run format$'],
};
executionMode: "branch" runs the commands once per branch after all updates are applied, rather than once per dependency – correct for codegen, where you want one regeneration over the final state, not N intermediate ones.
8. Dashboard, pinning strategy, and measuring lead time
The Dependency Dashboard (enabled via :dependencyDashboard in the base preset) creates a single tracking issue per repo listing every pending, rate-limited, and errored update, with checkboxes to force a specific PR or rebase. It is your fleet’s control surface – the place an engineer goes to say “open the React 19 major PR now” without waiting for the schedule.
On pinning strategy, the principal-level call is application-versus-library. Applications (anything you deploy) should pin exact versions so builds are reproducible and the lockfile is the source of truth – this is what config:recommended does for app dependencies. Libraries (anything you publish) should keep wide semver ranges so they do not force version constraints on consumers. Encode the split explicitly:
{
"packageRules": [
{
"description": "Apps: pin exact for reproducible deploys",
"matchFileNames": ["services/**/package.json"],
"rangeStrategy": "pin"
},
{
"description": "Published libs: keep ranges wide",
"matchFileNames": ["packages/**/package.json"],
"rangeStrategy": "widen"
}
]
}
For lead time – the metric that proves the platform works – measure the gap between a release being published upstream and your repo merging it. Renovate stamps every branch with structured commit metadata; the practical measurement is the delta between a dependency’s date_published (from the datasource) and your PR’s merge time. Track the org-wide p50 and p90. A healthy fleet sits at a p50 of single-digit days for patches and trends down over time. When p90 spikes, it is almost always a stuck repo with failing CI blocking auto-merge – the dashboard issue for that repo will already be flagging it.
Verify
Run these before declaring the rollout done.
- Dry run finds dependencies. Confirm the explain pass lists real deps with no unexpected
skipReason:
LOG_LEVEL=debug renovate --dry-run=full my-org/my-service 2>&1 \
| grep -E 'depName|skipReason' | sort | uniq -c | sort -rn
- The shared preset resolves. A repo’s onboarding PR or dashboard should show your org defaults (the
dependencieslabel, your schedule). Validate any preset edit locally before it hits the fleet:
npx --package renovate -- renovate-config-validator default.json
- Security PRs ignore the schedule. Introduce a knowingly-vulnerable pin in a sandbox repo and confirm Renovate opens a
security-labeled PR immediately, not in the overnight window. - Auto-merge respects CI. Open a grouped dev-dependency PR, force a test to fail, and confirm it does not merge. Fix the test and confirm it merges without human action.
minimumReleaseAgeholds. Confirm a release published today does not get a PR until it clears the configured age – check the dashboard’s “Pending Status Checks” / “Awaiting Schedule” section.
Enterprise scenario
A platform team running ~280 microservice repos on GitHub Enterprise Server moved off Dependabot because it could not reach their internal Artifactory npm and Docker registries, and because every repo’s dependabot.yml had drifted into a bespoke snowflake. They self-hosted Renovate on a scheduled Actions workflow with a GitHub App token and a single renovate-config preset repo.
The constraint that bit them: auto-merge worked for two weeks, then a routine patch took down a payments service. Root cause was not Renovate – it was that the payments repo’s “required” check was advisory, not enforced in branch protection, so a green-looking-but-not-actually-required test let a bad bump merge at 3am. The same automerge: true policy was correct for 279 repos and catastrophic for the one without enforced checks.
The fix was twofold. First, they made enforced-required-checks a precondition: a small org-policy job audits branch protection and Renovate auto-merge is gated behind it via a packageRules entry that disables auto-merge on any repo missing the marker file. Second, they added a blast-radius brake – payments and other tier-0 services keep minimumReleaseAge: "7 days" and never auto-merge runtime deps, only dev/CI tooling:
{
"packageRules": [
{
"description": "Tier-0 services: longer bake, no runtime auto-merge",
"matchFileNames": ["services/payments/**", "services/ledger/**"],
"minimumReleaseAge": "7 days",
"matchDepTypes": ["dependencies"],
"automerge": false
}
]
}
Six months in, ~70% of all dependency PRs across the fleet merge with zero human touch, security fixes land a PR within minutes of disclosure, and the median patch lead time dropped from 41 days (the Dependabot-era backlog) to 4. The lesson the team internalized: auto-merge is only as safe as your weakest branch protection, so the policy that enables it must verify the gate exists, not assume it.
Checklist
Going deeper
How packageRules actually match — and override
packageRules is the whole configuration language, so understand its two rules precisely.
First, within a single rule, every match* condition is ANDed. A rule with matchManagers: ["npm"], matchDepTypes: ["devDependencies"], and matchUpdateTypes: ["minor", "patch"] applies only to updates that are all three: an npm dev-dependency, minor or patch. Add one more matcher and you make the rule stricter, not broader. This is the number-one source of “my rule isn’t firing” — one over-specific matcher silently excludes everything.
Second, rules are evaluated top-to-bottom, and for any given option the last matching rule wins. So order is policy. Put broad defaults first and specific overrides last:
{
"packageRules": [
{ "matchUpdateTypes": ["minor", "patch"], "automerge": true },
{ "matchPackageNames": ["/^@myorg//"], "automerge": false }
]
}
The first rule turns auto-merge on for all minor/patch updates; the second turns it back off for internal @myorg/* packages, because it matches later and last-match-wins for the automerge option. Reverse the two entries and your internal packages would auto-merge — the exact opposite policy from the same two rules. matchPackageNames takes bare strings for exact matches and /regex/ for patterns (the modern, unified replacement for the removed matchPackagePatterns/matchPackagePrefixes); prefix a name with ! to negate. When a rule is not doing what you expect, the dry-run log prints which packageRules matched each dependency.
Preset resolution, config:best-practices, and config migration
extends is an ordered merge: presets are applied left to right, later values overriding earlier ones, and your own top-level keys override all of them. Presets resolve by prefix — local>org/repo (same platform, no public round-trip), github>org/repo, gitlab>org/repo, or an npm-published preset package. You can pass parameters to presets that accept them (for example helpers:pinGitHubActionDigests) and drop an inherited rule you dislike with ignorePresets.
Two presets are worth knowing beyond config:recommended. config:best-practices is the supply-chain-hardened profile: it pins GitHub Actions and Docker images by digest, enables lockfile maintenance, and sets a stricter posture — the right base once your team is comfortable and wants reproducibility and provenance by default. And configMigration: true tells Renovate to open a PR that automatically rewrites deprecated options in your config (for instance migrating a legacy stabilityDays to minimumReleaseAge), which is how you keep a large fleet’s config current without hand-editing every repo.
Custom (regex) managers for versions Renovate can’t natively see
Sometimes a version lives somewhere no built-in manager parses — a tool version pinned in a Dockerfile ARG, a Makefile variable, an install script. A custom manager (customType: "regex", the current name for what used to be regexManagers) teaches Renovate to extract a dependency from a capture group and hand it to a datasource:
{
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": ["/^Dockerfile$/"],
"matchStrings": ["ARG TERRAFORM_VERSION=(?<currentValue>.*?)\\s"],
"depNameTemplate": "hashicorp/terraform",
"datasourceTemplate": "github-releases"
}
]
}
The named capture group (?<currentValue>…) tells Renovate which text is the version; depNameTemplate and datasourceTemplate tell it what that version is and where to look up newer ones (here, GitHub releases of hashicorp/terraform). managerFilePatterns is the current field for scoping which files the manager reads (it superseded the older fileMatch). With this in place, a bare ARG TERRAFORM_VERSION=1.9.5 in a Dockerfile gets the same automated bumps, grouping, and auto-merge as a first-class npm dependency.
Monorepo internals: per-package isolation and stepping majors
Renovate detects multiple manifests in one repo (npm/pnpm/yarn workspaces, multiple go.mod, several Dockerfiles) and treats each as its own dependency surface, so a bump in services/api does not touch services/web’s lockfile. matchFileNames scopes a rule to a workspace path, and additionalBranchPrefix keeps per-package branches from colliding. Two defaults matter for readability at scale: separateMajorMinor (on by default) splits a major update into its own PR so it never blocks the safe minor/patch train, and separateMultipleMajor lets Renovate offer v3 → v4 and v4 → v5 as separate stepping-stone PRs instead of one giant leap — invaluable for migrating a framework one major at a time. For repos where you want the lockfile refreshed inside an existing range rather than the manifest bumped, rangeStrategy: "update-lockfile" does exactly that. The monorepo grouping preset (group:monorepos) is the complement: it keeps upstream families like Angular or NestJS version-locked so you never ship a half-upgraded framework. (For the versioning side of publishing monorepo packages, see Semantic-release automated versioning.)
Merge Confidence, private registries, and host rules
Two production concerns round out the platform. Merge Confidence is Mend’s telemetry layer: PRs get badges for a release’s age, adoption percentage, and test pass-rate across the ecosystem, so you can promote auto-merge based on confidence, not just update type — “auto-merge patches with high merge-confidence” is a stronger policy than “auto-merge all patches.” It is free on the hosted app.
Private registries authenticate through hostRules, which live in the self-hosted config.js (or the hosted app’s encrypted secrets) and never in a repo’s plaintext renovate.json:
// config.js -- authenticate datasources to an internal registry
module.exports = {
hostRules: [
{
matchHost: 'artifactory.internal.example.com',
hostType: 'npm',
token: process.env.ARTIFACTORY_TOKEN, // placeholder, from the runner's env
},
],
};
matchHost scopes credentials to one host, hostType says which datasource they authorize, and the token comes from the runner’s environment — the same GitHub-App-not-a-human-PAT discipline as the platform token. This is what lets a self-hosted bot resolve versions from Artifactory or Nexus that the public internet cannot see.
Rebasing, dashboard approval, and PR hygiene at fleet scale
A busy repo’s Renovate PRs drift out of date as main moves. rebaseWhen controls the refresh: "conflicted" rebases only on an actual merge conflict (cheapest on CI runners), "behind-base-branch" keeps every PR mergeable at the cost of re-running CI on each base change, and "auto" lets Renovate decide. For changes you want a human to opt into rather than see auto-opened, dependencyDashboardApproval: true (globally, or per-packageRules for just majors) makes Renovate list the update on the dashboard with an unchecked box and open the PR only after someone ticks it — a gentle brake on the scariest updates without disabling them. Combined with prConcurrentLimit, this keeps the open-PR count bounded and every branch either mergeable or explicitly parked.
Practice challenges
Work these in order — they escalate from beginner to advanced. Each has a worked solution; try it before you expand it.
Challenge 1 — Onboard a repo with one line (beginner)
Give a brand-new repository the entire org policy without copying any rules into it.
<details> <summary>Solution</summary>
Add a single renovate.json at the repo root:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["local>my-org/renovate-config"]
}
Why: all policy lives in the shared renovate-config repo; extends pulls it in, so the repo inherits grouping, scheduling, and auto-merge rules with nothing to maintain locally. Change the fleet’s behavior by editing the preset, not this file.
</details>
Challenge 2 — Collapse all GitHub Actions bumps into one weekly PR (beginner)
Stop Actions updates from trickling in as separate PRs all week; batch them into a single Monday-morning PR.
<details> <summary>Solution</summary>
{
"packageRules": [
{
"matchManagers": ["github-actions"],
"groupName": "github actions",
"schedule": ["before 6am on monday"]
}
]
}
Why: matchManagers: ["github-actions"] selects every workflow uses: pin, groupName merges them into one branch/PR, and the per-rule schedule overrides the global window so the batch appears once a week — one review instead of a dozen.
</details>
Challenge 3 — Auto-merge patch-level dev deps, never majors (intermediate)
Turn on hands-off merging for the safe class of updates while guaranteeing a major version is always seen by a human.
<details> <summary>Solution</summary>
{
"packageRules": [
{
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true,
"platformAutomerge": true
},
{
"matchUpdateTypes": ["major"],
"automerge": false,
"addLabels": ["needs-human-review"]
}
]
}
Why: the first rule auto-merges only low-risk dev-dependency updates once CI is green, and platformAutomerge lets the platform merge the instant the last check passes. The second rule matches later, so for any major it overrides automerge back to false (last-match-wins) and labels it for a person.
</details>
Challenge 4 — Make CVE fixes ignore your overnight schedule (intermediate)
Your global schedule batches PRs to nighttime. Ensure a security fix does not wait for that window.
<details> <summary>Solution</summary>
{
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security", "priority/high"],
"schedule": ["at any time"],
"prCreation": "immediate"
},
"osvVulnerabilityAlerts": true
}
Why: the schedule inside vulnerabilityAlerts overrides the org-wide window for security PRs only, and prCreation: "immediate" skips the stability delay so the fix lands within minutes. osvVulnerabilityAlerts widens coverage to the full OSV dataset beyond GitHub’s feed.
</details>
Challenge 5 — Give tier-0 services a longer bake and no runtime auto-merge (advanced)
Your fleet default is minimumReleaseAge: "3 days" with runtime auto-merge on. Payments and ledger must bake for 7 days and never auto-merge a runtime dependency — using the same shared preset.
<details> <summary>Solution</summary>
Keep the global defaults, then add a later rule that overrides them for those paths:
{
"minimumReleaseAge": "3 days",
"packageRules": [
{ "matchUpdateTypes": ["minor", "patch"], "automerge": true },
{
"description": "Tier-0: longer bake, no runtime auto-merge",
"matchFileNames": ["services/payments/**", "services/ledger/**"],
"matchDepTypes": ["dependencies"],
"minimumReleaseAge": "7 days",
"automerge": false
}
]
}
Why: because the tier-0 rule appears after the global auto-merge rule and matches those file paths, it wins for both minimumReleaseAge and automerge on runtime deps in payments/ledger — a per-service blast-radius brake expressed entirely through rule order, not a separate config file.
</details>
Challenge 6 — Teach Renovate a version it can’t natively see (advanced)
A Dockerfile pins ARG TERRAFORM_VERSION=1.9.5. No built-in manager reads it. Get Renovate to bump it like any other dependency.
<details> <summary>Solution</summary>
{
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": ["/^Dockerfile$/"],
"matchStrings": ["ARG TERRAFORM_VERSION=(?<currentValue>.*?)\\s"],
"depNameTemplate": "hashicorp/terraform",
"datasourceTemplate": "github-releases"
}
]
}
Why: the named capture group (?<currentValue>…) marks the text to bump, datasourceTemplate: "github-releases" tells Renovate where to find newer versions of hashicorp/terraform, and managerFilePatterns scopes it to the Dockerfile. Now that ARG gets the same PRs, grouping, and auto-merge treatment as a first-class dependency.
</details>
Common beginner mistakes
- “Renovate updates everything automatically the moment I install it.” By default Renovate is a pull-request bot — it opens PRs and waits for you. Auto-merge is strictly opt-in and gated on required checks and release age. Installing it floods you with proposals, not merges; the config work is what turns that flood into a trickle you trust.
- “I’ll put
autodiscover/dryRuninrenovate.json.” Those are self-hosted-only options and are rejected in a repo’s config. Host-level knobs live inconfig.js; per-repo policy lives in the shared preset. Mixing the two layers is the most common onboarding failure. - “
matchPackagePatternsis how you regex-match a package.” Deprecated and removed. Renovate v40+ uses the unifiedmatchPackageNames— a bare string is an exact match, a/…/-wrapped value is a regex. Old blog snippets will steer you wrong. - “Auto-merge means merging without checks.” Auto-merge with no required status check in branch protection is just merging unverified diffs on a timer — there is nothing defining “green.” Define and enforce required checks first; only then is
automergesafe. - “
config:baseis the base preset.” It was renamed. The current sane-defaults preset isconfig:recommended;config:baseis deprecated. Start new presets fromconfig:recommended(orconfig:best-practicesfor a digest-pinned, supply-chain-hardened posture). - “Grouping and scheduling are optional polish.” They are the core value, not a nicety. Without them, naive Renovate opens forty PRs on Monday and the team mutes the bot by Wednesday. Batching, scheduling, and rate-limiting are what make the automation survivable.
- “A dependency is silently ignored, so it must be a bug.” Almost always it is one of the three layers. Run
renovate --dry-run=fulland readskipReason:disabled-by-config,unsupported-datasource, orinvalid-versioneach point at the exact fix. Diagnose before you file an issue.
Glossary
- Renovate — an open-source (and hosted-as-Mend-app) bot that watches your dependencies across 90+ ecosystems and opens pull requests to update them, configured as code.
- Dependabot — GitHub’s built-in dependency updater; simpler and GitHub-only, the natural first tool for a small all-GitHub repo.
- Manager — the parser that finds dependencies in a given file type (
npminpackage.json,dockerfileinFROM/ARG,terraformin.tf,github-actionsinuses:). - Datasource — where Renovate looks up available versions for a dependency (npm registry, Docker registry, PyPI, GitHub releases, a Maven repo).
- Versioning — the scheme used to compare versions (
semver,docker,pep440,maven,loose); the wrong one makes Renovate skip or mis-order updates. renovate.json— a repo’s Renovate config file; at scale it is usually one line thatextendsa shared preset.- Preset — a reusable config published in another repo/package and pulled in with
extends; the mechanism that makes Renovate a fleet-wide platform. config:recommended— the modern sane-defaults preset (replaces the deprecatedconfig:base).config:best-practices— a stricter preset that pins Actions and images by digest and enables lockfile maintenance; supply-chain-hardened defaults.packageRules— the ordered list of conditional rules; matchers within a rule are ANDed, and the last matching rule wins for each option.match*matchers — conditions likematchManagers,matchDepTypes,matchUpdateTypes,matchPackageNames(bare = exact,/regex/= pattern),matchFileNames(scope to a path).- Grouping — collapsing related updates into one PR via
groupNameor agroup:*preset, so one review covers a coherent set. - Schedule — Renovate’s natural-language time windows (
"after 9pm and before 6am every weekday"), anchored to yourtimezone, to batch PRs off-hours. prHourlyLimit/prConcurrentLimit— rate limits on how fast, and how many at once, Renovate opens PRs; the backlog drains as a priority queue.- Dependency Dashboard — a single tracking issue per repo (via
:dependencyDashboard) listing pending, rate-limited, and errored updates with checkboxes; the fleet’s control surface. minimumReleaseAge— the current option (formerlystabilityDays) that refuses to open a PR until a release is N days old, dodging yanked-patch traps.rangeStrategy— how Renovate rewrites version ranges:pin(exact, for apps),widen(for published libs),bump,replace, orupdate-lockfile.automerge/platformAutomerge— enable hands-off merging of a matched update class;platformAutomergedelegates the actual merge to GitHub/GitLab so it fires the instant the last check passes.vulnerabilityAlerts— a config block that gives security fixes their own schedule and immediate PR creation so they jump the queue.osvVulnerabilityAlerts— broadens security coverage to the full OSV database beyond GitHub’s advisory feed.- OSV — the Open Source Vulnerabilities database; the cross-ecosystem advisory source Renovate reads.
- Merge Confidence — Mend’s telemetry badges (release age, adoption, pass-rate) that let you gate auto-merge on confidence, not just update type.
lockFileMaintenance— a scheduled refresh of the lockfile to pull in transitive-dependency updates no direct bump would touch.postUpgradeTasks— commands (codegen, formatting) run after an update and committed into the same PR; self-hosted only, constrained by anallowedCommandsallowlist.- Custom manager (
customType: "regex") — teaches Renovate to extract a version from arbitrary text via a named capture group; the current name for the oldregexManagers. hostRules— host-scoped credentials that authenticate datasources to private registries; kept inconfig.js/encrypted secrets, never in a repo’s plaintext config.- Self-hosted vs. Mend app — run the CLI yourself (private registries, custom cadence, air-gapped) or install the hosted GitHub App (zero infrastructure).
- GitHub App token — the recommended bot identity for self-hosting: a clean bot author, per-repo install control, and short-lived tokens instead of a human’s PAT.
skipReason— the dry-run field that explains why a dependency was not updated (disabled-by-config,unsupported-datasource,invalid-version); the first thing to read when a dep is ignored.- Semver — semantic versioning (
major.minor.patch); majors may break, minors add features, patches fix bugs — the basis for most update-type policy. - Transitive dependency — a dependency of your dependency; often where CVEs and lockfile-maintenance updates live.
- Digest pin — pinning an image or action by its immutable
@sha256:…digest rather than a mutable tag, for reproducibility and provenance.