Configuring Renovate for a monorepo Jump to heading
Pointing a dependency bot at a monorepo with default settings produces a specific, memorable failure: one pull request per package per dependency, all touching the same lockfile, all conflicting with each other. Twelve workspaces sharing a test framework generates twelve pull requests for one version bump. The fix is to tell the bot about the repository’s structure rather than letting it infer one from the manifests it can see, extending automated dependency updates.
When to use this approach Jump to heading
- Several packages live in one repository and share a lockfile.
- The same dependency appears in multiple manifests at the same version.
- Some packages depend on others in the same repository.
- Update pull requests currently conflict with each other more often than they merge.
- If the repository has one manifest, the default configuration is already correct; start from the parent topic instead.
Step 1 — Declare the workspace layout explicitly Jump to heading
Autodiscovery finds manifests, not intent. Listing the paths makes the bot’s view match the build tool’s view.
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"includePaths": ["package.json", "packages/**", "services/**", "tools/**"],
"ignorePaths": ["**/fixtures/**", "**/__tests__/**", "examples/**"],
"postUpdateOptions": ["npmDedupe"]
} # Verification: which manifests does the bot consider in scope?
npx --yes renovate --dry-run=full --platform=local 2>&1 | grep -i 'packageFile' | sort -u The ignore list is doing real work. Test fixtures frequently contain deliberately old manifests, and a bot that updates them breaks the tests those fixtures exist to support.
Step 2 — Group by the thing that changes together Jump to heading
In a monorepo the useful grouping is usually per dependency across all packages, not per package across all dependencies.
{
"packageRules": [
{
"description": "One pull request per dependency, covering every workspace that uses it",
"matchPackagePatterns": ["*"],
"groupName": "{{depName}}",
"groupSlug": "dep-{{depNameSanitized}}"
},
{
"description": "Test and build tooling moves together",
"matchPackageNames": ["jest", "ts-jest", "@types/jest", "vitest", "esbuild"],
"groupName": "test and build tooling"
}
]
} # Verification: count how many branches a dry run would create
npx --yes renovate --dry-run=full --platform=local 2>&1 | grep -c 'branchName' What changed: bumping a shared framework now produces one branch that edits every manifest using it, so the lockfile is regenerated once and the change is reviewed as a single decision.
Step 3 — Keep internal packages out of the stream Jump to heading
Workspace packages that depend on each other are resolved by the build tool, not fetched from a registry. Updating them through the bot produces churn with no meaning.
{
"packageRules": [
{
"matchPackagePatterns": ["^@acme/"],
"matchDepTypes": ["dependencies", "devDependencies"],
"enabled": false,
"description": "Internal workspace packages are linked, not fetched"
}
]
} # Verification: internal names resolve to workspace links, not registry versions
npm ls --workspaces --depth=0 2>/dev/null | grep '@acme/' | head Step 4 — Contain the lockfile churn Jump to heading
Even with perfect grouping, several branches will be open at once and each holds a different lockfile. Make regeneration the standard resolution rather than a manual merge.
# .gitattributes — the lockfile is generated; do not merge it line by line
echo 'package-lock.json merge=ours linguist-generated=true' >> .gitattributes
git config merge.ours.driver true # Rebase an update branch and regenerate rather than resolving
git rebase origin/main || true
git checkout origin/main -- package-lock.json
npm install --package-lock-only --workspaces
git add package-lock.json && git rebase --continue # Verification: the regenerated file is deterministic
npm install --package-lock-only --workspaces && git diff --exit-code package-lock.json \
&& echo "lockfile is stable" SAFETY WARNING —
merge=ourson a lockfile means Git will silently keep your side during a merge. That is correct only because the file is regenerated immediately afterwards. If a branch merges without the regeneration step, the lockfile no longer matches the manifests and the next install resolves versions nobody tested. Make the regeneration part of the pipeline, not part of a habit.
Step 5 — Cap concurrency to what the pipeline can absorb Jump to heading
Each update branch runs the full pipeline. In a monorepo that is expensive unless the pipeline is already path-filtered.
{
"prConcurrentLimit": 4,
"branchConcurrentLimit": 6,
"rebaseWhen": "conflicted"
} # Verification: how much pipeline time do open update branches represent?
gh pr list --label dependencies --json number --jq 'length' | \
awk '{printf "%d open update PRs\n", $1}' rebaseWhen: conflicted is the setting that keeps cost sane. The default rebases branches whenever the default branch moves, and in an active monorepo that means every update branch re-runs the pipeline several times a day for no new information. The path filters in optimizing CI triggers for path-specific changes cut the remaining cost further.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Should each workspace have its own bot configuration? Jump to heading
No — one configuration at the repository root with package rules is easier to reason about and avoids contradictory settings. Per-package overrides belong in packageRules entries matched by path, which keeps every decision visible in one file.
How do we handle a dependency that must differ between packages? Jump to heading
Match the path and set an explicit rule for it, with a comment saying why the divergence exists. Without the comment, the next person removes the rule to “clean up”, and the divergence returns as a bug.
What about the repository’s own release versions? Jump to heading
Keep them out of the update stream entirely. Internal version numbers are set by the release process described in release tagging and versioning, and a dependency bot editing them creates a second source of truth.
Related Jump to heading
- Automated Dependency Updates — the parent topic and the scheduling levers.
- Keeping Lockfiles Conflict-Free During Bulk Updates — the regeneration discipline in detail.
- Sparse-Checkout for Large Monorepos — the sibling concern of working in a repository this size.