Dependency management is one of those tasks that’s easy to ignore until it becomes critical. Dependencies drift behind. A CVE drops. You discover your Node app is four major versions behind Express, your Dockerfile pulls node:14 which reached EOL two years ago, and your Terraform providers haven’t been updated since the initial commit.
Renovate Bot solves this systematically. It scans your repositories for dependencies across every package manager it knows about, opens PRs with precise, atomic updates, and optionally merges them automatically based on rules you define. Unlike Dependabot, which is tightly coupled to GitHub and limited in configurability, Renovate runs anywhere, understands more package managers, and has configuration deep enough to handle the most opinionated workflows.
This guide goes beyond the basics — configuration structure, grouping strategies, automerge rules, custom managers for non-standard files, and scaling to many repositories.
How Renovate Works
Renovate runs as a job (cron, CI pipeline, or GitHub App) and follows this loop for each repository:
- Clone the repository
- Extract dependencies by scanning files for known package manager patterns
- Look up current versions of each dependency from the appropriate registry
- Determine which dependencies have updates available
- Apply
packageRules to decide what to update, how to group PRs, and whether to automerge
- Open PRs (or update existing ones) with the changes
- Optionally merge passing PRs according to automerge rules
The config lives in renovate.json (or renovate.json5, .renovaterc, package.json#renovate) at the repository root. A shared renovate-config repository can define organization-wide presets that individual repos extend.
Getting Started
GitHub App (Easiest)
Install the Renovate GitHub App and authorize it on your repositories. Add a minimal renovate.json to each repo:
1
2
3
4
|
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"]
}
|
The config:recommended preset is a sensible baseline that groups monorepo packages, enables a dependency dashboard issue, and sets reasonable defaults. Renovate will immediately scan the repository and open an onboarding PR to confirm the config.
Self-Hosted (Docker)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# docker-compose.yml
services:
renovate:
image: renovate/renovate:latest
environment:
RENOVATE_TOKEN: ${GITHUB_TOKEN}
RENOVATE_PLATFORM: github
RENOVATE_AUTODISCOVER: "true" # Scan all accessible repos
RENOVATE_AUTODISCOVER_FILTER: "myorg/*" # Limit to org
LOG_LEVEL: info
volumes:
- ./config.js:/usr/src/app/config.js:ro
- renovate_cache:/tmp/renovate
restart: unless-stopped
volumes:
renovate_cache:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// config.js — global config for self-hosted Renovate
module.exports = {
platform: "github",
token: process.env.RENOVATE_TOKEN,
repositories: [
"myorg/api-service",
"myorg/frontend",
"myorg/infrastructure",
],
// Or autodiscover:
// autodiscover: true,
// autodiscoverFilter: ["myorg/*"],
};
|
Run on a schedule with cron or a Kubernetes CronJob:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
# kubernetes/renovate-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: renovate
namespace: renovate
spec:
schedule: "0 */4 * * *" # Every 4 hours
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: renovate
image: renovate/renovate:latest
env:
- name: RENOVATE_TOKEN
valueFrom:
secretKeyRef:
name: renovate-secrets
key: github-token
- name: RENOVATE_PLATFORM
value: github
- name: RENOVATE_AUTODISCOVER
value: "true"
- name: RENOVATE_AUTODISCOVER_FILTER
value: "myorg/*"
- name: LOG_LEVEL
value: info
volumeMounts:
- name: config
mountPath: /usr/src/app/config.js
subPath: config.js
- name: cache
mountPath: /tmp/renovate
volumes:
- name: config
configMap:
name: renovate-config
- name: cache
emptyDir: {}
restartPolicy: OnFailure
|
GitLab, Gitea, Bitbucket
Renovate supports all major platforms. For GitLab:
1
2
3
4
5
6
7
|
// config.js
module.exports = {
platform: "gitlab",
endpoint: "https://gitlab.yourdomain.com/api/v4/",
token: process.env.GITLAB_TOKEN,
autodiscover: true,
};
|
For Gitea/Forgejo:
1
2
3
4
5
6
|
module.exports = {
platform: "gitea",
endpoint: "https://gitea.yourdomain.com/",
token: process.env.GITEA_TOKEN,
repositories: ["myuser/myrepo"],
};
|
Understanding the Config Structure
Renovate’s config has a layered structure:
Global config (config.js / environment vars)
└── Preset libraries (config:recommended, etc.)
└── Repository renovate.json
└── packageRules (applied in order)
Each layer can override the previous. packageRules are the primary mechanism for customizing behavior per dependency or group of dependencies.
Core Configuration Fields
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"timezone": "America/New_York",
"schedule": ["after 10pm every weekday", "before 5am every weekday", "every weekend"],
"prConcurrentLimit": 10,
"prHourlyLimit": 2,
"branchConcurrentLimit": 20,
"labels": ["dependencies"],
"reviewers": ["team:platform"],
"assignees": ["octocat"],
"commitMessagePrefix": "chore(deps):",
"semanticCommits": "enabled",
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"],
"automerge": true
}
}
|
Key fields explained:
schedule: Human-readable schedule using later.js syntax. Renovate batches updates and opens PRs during this window. Outside the window, it doesn’t create new PRs (but checks CI on existing ones).
prConcurrentLimit: Maximum open Renovate PRs at any time. Prevents PR flood. Set to 0 for unlimited.
prHourlyLimit: Rate limit on PR creation. Useful for repos where CI is expensive.
branchConcurrentLimit: Maximum branches Renovate maintains simultaneously.
vulnerabilityAlerts: Separate config for security updates — often set to bypass normal scheduling and automerge.
Package Rules: The Heart of Renovate
packageRules is an array of matchers + actions. Rules are evaluated in order; all matching rules apply (last write wins for conflicts).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
{
"packageRules": [
{
"matchPackagePatterns": [".*"],
"matchUpdateTypes": ["major"],
"automerge": false,
"labels": ["breaking-change"]
},
{
"matchPackagePatterns": [".*"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
}
]
}
|
Matchers
| Matcher |
Description |
Example |
matchPackageNames |
Exact package names |
["lodash", "express"] |
matchPackagePatterns |
Regex on package names |
["^@types/", "^eslint"] |
matchDepTypes |
Dependency type |
["devDependencies"] |
matchManagers |
Package manager |
["npm", "docker"] |
matchUpdateTypes |
Update type |
["major", "minor", "patch", "pin", "digest"] |
matchCurrentVersion |
Current version range |
["< 1.0.0"] |
matchFileNames |
File path |
["**/package.json"] |
matchSourceUrls |
Source code URL |
["https://github.com/myorg/*"] |
matchCategories |
Renovate category |
["python", "node", "docker"] |
Actions
| Action |
Description |
automerge |
Merge automatically if CI passes |
automergeType |
"pr" (merge PR) or "branch" (push directly) |
enabled |
Include/exclude this dependency |
groupName |
Group multiple updates into one PR |
labels |
GitHub labels to apply |
reviewers |
Specific reviewers |
schedule |
Override global schedule for this group |
minimumReleaseAge |
Wait N days before opening PR |
stabilityDays |
Alias for minimumReleaseAge |
pinDigests |
Pin Docker images to SHA digest |
rangeStrategy |
How to update version ranges |
Grouping Strategies
Without grouping, Renovate opens one PR per dependency. A busy monorepo could generate hundreds of PRs a week. Grouping is essential.
Group by Category
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
{
"packageRules": [
{
"groupName": "ESLint and Prettier",
"matchPackagePatterns": ["^eslint", "^prettier", "@typescript-eslint/.*"],
"matchDepTypes": ["devDependencies"]
},
{
"groupName": "Testing libraries",
"matchPackagePatterns": ["^jest", "^@testing-library/", "^vitest", "msw"],
"matchDepTypes": ["devDependencies"]
},
{
"groupName": "TypeScript",
"matchPackageNames": ["typescript"],
"matchPackagePatterns": ["^@types/"]
}
]
}
|
Group All Dev Dependencies
1
2
3
4
5
6
7
8
9
10
|
{
"packageRules": [
{
"groupName": "dev dependencies",
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
}
]
}
|
Group by Monorepo
Renovate knows about common monorepos and automatically groups them. The monorepo preset activates this:
1
2
3
|
{
"extends": ["config:recommended", "group:monorepos"]
}
|
This groups e.g. all @babel/* packages, all @jest/* packages, all @aws-sdk/* v3 packages into single PRs.
For your own internal monorepo packages:
1
2
3
4
5
6
7
8
|
{
"packageRules": [
{
"groupName": "Internal platform packages",
"matchPackagePatterns": ["^@myorg/"]
}
]
}
|
Group Docker Base Images
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
{
"packageRules": [
{
"groupName": "Docker base images",
"matchManagers": ["dockerfile"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"schedule": ["on sunday"]
},
{
"matchManagers": ["dockerfile"],
"matchUpdateTypes": ["major"],
"automerge": false,
"labels": ["docker-major"]
}
]
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
{
"packageRules": [
{
"groupName": "Terraform AWS provider",
"matchManagers": ["terraform"],
"matchPackageNames": ["hashicorp/aws"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
},
{
"groupName": "Terraform providers (all)",
"matchManagers": ["terraform"],
"matchDepTypes": ["provider"],
"matchUpdateTypes": ["patch"]
}
]
}
|
Separate Major Updates
Always separate major updates from minor/patch. Major updates often require manual intervention:
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"separateMajorMinor": true,
"separateMultipleMajor": true,
"packageRules": [
{
"matchUpdateTypes": ["major"],
"addLabels": ["major-update"],
"automerge": false,
"schedule": ["at any time"] // Don't delay security-relevant majors
}
]
}
|
Automerge Rules
Automerge is powerful but needs careful scoping. The goal: merge boring, safe updates automatically; require human review for risky ones.
The Safe Automerge Pattern
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
{
"packageRules": [
// Automerge patch updates for non-breaking changes
{
"matchUpdateTypes": ["patch"],
"matchPackagePatterns": [".*"],
"automerge": true,
"automergeType": "pr",
"minimumReleaseAge": "3 days"
},
// Automerge minor updates for devDependencies (lower risk)
{
"matchUpdateTypes": ["minor"],
"matchDepTypes": ["devDependencies"],
"automerge": true,
"minimumReleaseAge": "5 days"
},
// Never automerge major updates
{
"matchUpdateTypes": ["major"],
"automerge": false
},
// Never automerge production dependencies' minor updates (default)
{
"matchUpdateTypes": ["minor"],
"matchDepTypes": ["dependencies"],
"automerge": false
}
]
}
|
minimumReleaseAge (Stability Days)
New releases sometimes get yanked or have critical bugs discovered shortly after release. minimumReleaseAge tells Renovate to wait before opening or merging a PR:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
{
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"minimumReleaseAge": "3 days",
"automerge": true
},
{
"matchUpdateTypes": ["minor"],
"minimumReleaseAge": "7 days"
},
{
"matchUpdateTypes": ["major"],
"minimumReleaseAge": "14 days"
}
]
}
|
This is particularly valuable after incidents like the colors and faker malicious updates, where a brief waiting period would have caught the issue before it hit your production dependencies.
Automerge Type: PR vs Branch
1
2
3
4
|
{
"automergeType": "pr" // Merge the PR after CI passes (default)
"automergeType": "branch" // Push directly to the base branch, bypass PR
}
|
branch automerge is faster (no PR overhead) but skips code review entirely. Only use it for truly low-risk updates like linter config changes or @types/* packages.
Renovate can use GitHub’s auto-merge feature instead of merging itself:
1
2
3
4
5
6
7
8
9
|
{
"platformAutomerge": true,
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"automerge": true
}
]
}
|
With platformAutomerge: true, Renovate enables GitHub’s auto-merge on the PR. The PR merges automatically once all required status checks pass and any required reviews are satisfied. This respects branch protection rules that Renovate’s direct merge might bypass.
Pinning Digests
Docker images referenced by tag (node:20-alpine) are mutable — the tag can be updated to a different image without warning. Pinning to a digest ensures reproducibility:
1
2
3
4
5
6
7
8
|
{
"packageRules": [
{
"matchManagers": ["dockerfile"],
"pinDigests": true
}
]
}
|
This transforms:
into:
1
|
FROM node:20-alpine@sha256:abc123def456...
|
Renovate then updates the digest when the tag’s underlying image changes — you get both reproducibility and automated updates.
For Kubernetes manifests and Helm charts, the same applies to container image references.
Custom Managers
Renovate knows about 90+ package managers out of the box. But every codebase has custom dependency patterns — version numbers hardcoded in scripts, CI config files, or custom manifests. Custom managers handle these.
Custom Regex Manager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
{
"customManagers": [
{
"customType": "regex",
"fileMatch": ["\\.github/workflows/.*\\.ya?ml$"],
"matchStrings": [
"TERRAFORM_VERSION: ['\"]?(?<currentValue>[\\d\\.]+)['\"]?"
],
"depNameTemplate": "hashicorp/terraform",
"datasourceTemplate": "github-releases",
"versioningTemplate": "semver"
}
]
}
|
This finds patterns like TERRAFORM_VERSION: "1.7.0" in GitHub Actions workflows and opens PRs when new Terraform releases are available.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
{
"customManagers": [
{
"customType": "regex",
"fileMatch": ["^\\.tool-versions$"],
"matchStrings": [
"nodejs (?<currentValue>[\\d\\.]+)",
"python (?<currentValue>[\\d\\.]+)",
"golang (?<currentValue>[\\d\\.]+)"
],
"depNameTemplate": "node",
"datasourceTemplate": "node-version",
"versioningTemplate": "node"
}
]
}
|
For .tool-versions (asdf/mise), each tool needs its own matcher with the appropriate datasource.
Update Versions in Shell Scripts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
{
"customManagers": [
{
"customType": "regex",
"fileMatch": ["scripts/install-deps\\.sh"],
"matchStrings": [
"KUBECTL_VERSION=\"(?<currentValue>v[\\d\\.]+)\""
],
"depNameTemplate": "kubernetes/kubectl",
"datasourceTemplate": "github-releases"
},
{
"customType": "regex",
"fileMatch": ["scripts/install-deps\\.sh"],
"matchStrings": [
"HELM_VERSION=\"(?<currentValue>v[\\d\\.]+)\""
],
"depNameTemplate": "helm/helm",
"datasourceTemplate": "github-releases"
}
]
}
|
Update Docker Image References in Kubernetes Manifests
Standard Kubernetes manifests are handled by Renovate’s built-in kubernetes manager. But for custom templating or non-standard patterns:
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"customManagers": [
{
"customType": "regex",
"fileMatch": ["k8s/.*\\.yaml$"],
"matchStrings": [
"image: (?<depName>[^:]+):(?<currentValue>[^\\s]+)"
],
"datasourceTemplate": "docker"
}
]
}
|
Update Versions in Markdown Documentation
Keep your README.md up to date automatically:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{
"customManagers": [
{
"customType": "regex",
"fileMatch": ["README\\.md"],
"matchStrings": [
"renovate-(?<currentValue>[\\d\\.]+)-blue"
],
"depNameTemplate": "renovatebot/renovate",
"datasourceTemplate": "github-releases"
}
]
}
|
Ansible Roles and Collections
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"customManagers": [
{
"customType": "regex",
"fileMatch": ["requirements\\.ya?ml$"],
"matchStrings": [
"version: (?<currentValue>[\\d\\.]+)\\s+name: (?<depName>[^\\s]+)"
],
"datasourceTemplate": "galaxy"
}
]
}
|
Organization-Wide Presets
For teams managing multiple repositories, define shared presets in a renovate-config repository:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
// renovate-config/default.json (in a repo named "renovate-config" under your org)
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"description": "Organization default Renovate config",
"extends": ["config:recommended"],
"timezone": "America/New_York",
"schedule": ["after 10pm and before 6am every weekday", "every weekend"],
"prConcurrentLimit": 5,
"prHourlyLimit": 2,
"labels": ["dependencies"],
"reviewers": ["myorg/platform-team"],
"commitMessagePrefix": "chore(deps):",
"semanticCommits": "enabled",
"vulnerabilityAlerts": {
"enabled": true,
"automerge": true,
"schedule": ["at any time"]
},
"packageRules": [
{
"description": "Automerge patch updates after 3 days",
"matchUpdateTypes": ["patch"],
"minimumReleaseAge": "3 days",
"automerge": true
},
{
"description": "Group all devDependency minor updates",
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["minor"],
"groupName": "dev dependencies (minor)",
"automerge": true,
"minimumReleaseAge": "7 days"
},
{
"description": "Label major updates for review",
"matchUpdateTypes": ["major"],
"labels": ["major-dependency-update"],
"automerge": false
}
]
}
|
Individual repositories extend this preset:
1
2
3
4
5
|
// myrepo/renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>myorg/renovate-config"]
}
|
To override specific rules in a repo:
1
2
3
4
5
6
7
8
9
10
11
|
{
"extends": ["github>myorg/renovate-config"],
"packageRules": [
{
"description": "This repo needs manual review of all production dep updates",
"matchDepTypes": ["dependencies"],
"automerge": false,
"reviewers": ["security-team"]
}
]
}
|
Preset Versioning
Reference a specific version of your preset to avoid unexpected changes:
1
2
3
|
{
"extends": ["github>myorg/renovate-config:default#v2.1.0"]
}
|
The Dependency Dashboard
When using the GitHub App, Renovate creates a “Dependency Dashboard” issue in each repository:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
## Pending Status Checks
These updates are awaiting CI results:
- [ ] Update dependency express to v5.0.0
## Awaiting Schedule
These updates are scheduled for a future time window:
- [ ] Update dependency lodash to v4.17.22 (patch)
## Open PRs
- #142 Update dependency typescript to v5.4.0
- #139 Update dev dependencies (minor) (grouped)
## Rate-Limited
- [ ] Update dependency react to v19.0.0 (minor)
## Detected Dependencies
<details>
<summary>package.json</summary>
| Package | Current | Available |
|---------|---------|-----------|
| express | 4.18.2 | 5.0.0 |
| lodash | 4.17.20 | 4.17.22 |
|
The dashboard gives you a single view of all pending updates. Checking a checkbox in the issue manually triggers Renovate to process that update immediately, bypassing the schedule. This is useful for expediting security updates or testing a specific update in isolation.
Managing Many Repositories
Autodiscovery with Filters
1
2
3
4
5
6
7
8
9
|
// config.js
module.exports = {
autodiscover: true,
autodiscoverFilter: [
"myorg/api-*", // All API repos
"myorg/frontend-*", // All frontend repos
"!myorg/*-archived", // Exclude archived repos
],
};
|
Repository-Specific Overrides in Global Config
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// config.js
module.exports = {
autodiscover: true,
repositories: [
{
repository: "myorg/legacy-monolith",
packageRules: [
{
// This repo has manual QA, don't automerge anything
matchPackagePatterns: [".*"],
automerge: false,
schedule: ["on the first day of the month"]
}
]
}
]
};
|
Force Onboarding
When rolling out Renovate to many repos, control the onboarding flow:
1
2
3
4
5
6
7
8
9
10
11
12
|
// config.js
module.exports = {
autodiscover: true,
onboarding: true, // Create onboarding PR (default)
onboardingConfig: { // Pre-fill the onboarding PR config
extends: ["github>myorg/renovate-config"],
},
requireConfig: "optional", // Don't require renovate.json to process the repo
// "optional": process even without renovate.json, using defaults
// "required": only process repos with renovate.json
// "ignored": process all, ignore missing config warning
};
|
Merge Confidence
Renovate can integrate with merge confidence data — statistics on how often a specific package update breaks things across all Renovate users:
1
2
3
|
{
"extends": ["mergeConfidence:all-badges"]
}
|
This adds badges to PR descriptions showing the merge success rate:
Merge confidence: 98% (based on 12,847 updates across all Renovate users)
High-confidence updates can be automerged more aggressively.
Language-Specific Configurations
Node.js / npm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
{
"npm": {
"rangeStrategy": "bump"
},
"packageRules": [
{
"matchManagers": ["npm"],
"matchDepTypes": ["engines"],
"enabled": false
},
{
"matchPackagePatterns": ["^@types/node$"],
"matchManagers": ["npm"],
"groupName": "Node.js types"
}
]
}
|
rangeStrategy controls how version ranges are updated:
| Strategy |
Behavior |
Example |
auto |
Renovate decides |
— |
pin |
Pin to exact version |
^4.18.2 → 4.18.2 |
bump |
Bump lower bound |
^4.17.0 → ^4.18.2 |
widen |
Widen range |
^4.17.0 → `^4.17.0 |
replace |
Replace entire range |
^4.17.0 → ^5.0.0 |
For applications (not libraries), pin is recommended — removes version range ambiguity. For libraries that others consume, bump or widen is appropriate.
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
{
"pip_requirements": {
"enabled": true
},
"packageRules": [
{
"matchManagers": ["pip_requirements", "pipenv", "poetry"],
"matchUpdateTypes": ["patch"],
"automerge": true,
"minimumReleaseAge": "3 days"
},
{
"groupName": "Python development tools",
"matchPackageNames": ["black", "isort", "mypy", "pylint", "flake8", "ruff"],
"matchDepTypes": ["dev"]
}
]
}
|
Renovate supports requirements.txt, Pipfile, pyproject.toml (Poetry/PEP 621), and setup.cfg.
Go Modules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
{
"gomod": {
"enabled": true
},
"packageRules": [
{
"matchManagers": ["gomod"],
"matchUpdateTypes": ["patch"],
"automerge": true,
"minimumReleaseAge": "5 days"
},
{
"matchManagers": ["gomod"],
"matchUpdateTypes": ["major"],
"automerge": false,
"labels": ["go-major"]
}
]
}
|
Go modules require attention to major version handling. v2+ modules have different import paths, so Renovate handles them as separate packages.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
{
"terraform": {
"enabled": true
},
"packageRules": [
{
"matchManagers": ["terraform"],
"matchDepTypes": ["provider"],
"groupName": "Terraform providers",
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"minimumReleaseAge": "7 days"
},
{
"matchManagers": ["terraform"],
"matchDepTypes": ["module"],
"automerge": false,
"reviewers": ["platform-team"]
}
]
}
|
Docker / Container Images
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
{
"docker": {
"enabled": true,
"pinDigests": true
},
"packageRules": [
{
"matchManagers": ["dockerfile"],
"matchUpdateTypes": ["digest"],
"automerge": true,
"groupName": "Docker digest updates"
},
{
"matchManagers": ["dockerfile"],
"matchUpdateTypes": ["patch"],
"automerge": true,
"minimumReleaseAge": "3 days"
},
{
"matchManagers": ["dockerfile"],
"matchUpdateTypes": ["major", "minor"],
"automerge": false,
"reviewers": ["ops-team"]
}
]
}
|
GitHub Actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
{
"github-actions": {
"enabled": true,
"pinDigests": true
},
"packageRules": [
{
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["patch"],
"automerge": true,
"minimumReleaseAge": "3 days",
"groupName": "GitHub Actions (patch)"
},
{
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor"],
"automerge": true,
"minimumReleaseAge": "7 days",
"groupName": "GitHub Actions (minor)"
}
]
}
|
Pinning GitHub Actions to a specific commit SHA (via pinDigests: true) is a security best practice — it prevents supply chain attacks where an action tag is moved to malicious code.
Debugging and Troubleshooting
Dry Run
Test your config without making changes:
1
2
3
4
5
6
|
docker run --rm \
-e RENOVATE_TOKEN=${GITHUB_TOKEN} \
-e LOG_LEVEL=debug \
renovate/renovate:latest \
--dry-run=true \
myorg/myrepo
|
Config Validation
1
2
3
4
5
|
# Validate renovate.json syntax
docker run --rm \
-v $(pwd)/renovate.json:/usr/src/app/renovate.json:ro \
renovate/renovate:latest \
--validate-config
|
Or use the online validator at docs.renovatebot.com/config-validator.
Log Analysis
Renovate’s debug logs are verbose but structured. Key things to look for:
1
2
3
4
5
6
7
8
|
# See what packages were detected
LOG_LEVEL=debug renovate --dry-run myrepo 2>&1 | grep "Found dependency"
# See which packageRules matched
LOG_LEVEL=debug renovate --dry-run myrepo 2>&1 | grep "Matched rule"
# See why automerge was skipped
LOG_LEVEL=debug renovate --dry-run myrepo 2>&1 | grep -i "automerge"
|
Common Issues
Renovate opens duplicate PRs: Usually caused by branch naming conflicts. Check branchPrefix setting or existing PRs from previous runs.
Automerge not working: Check CI status — Renovate only automerges when all required checks pass. Also verify platformAutomerge and branch protection rules aren’t conflicting.
Schedule being ignored: Renovate uses the repository’s configured timezone. Ensure timezone matches your expected schedule interpretation.
Custom manager not matching: Test your regex at regex101.com first. Common mistakes: not escaping dots in patterns, forgetting the currentValue named capture group.
“Repository is blocked”: Renovate will temporarily block a repository if it encounters repeated errors. Check the Dependency Dashboard issue for the unblock option.
Security Considerations
Vulnerability Alerts
Enable vulnerability scanning — Renovate integrates with GitHub’s security advisories and the OSV database:
1
2
3
4
5
6
7
8
9
10
|
{
"osvVulnerabilityAlerts": true,
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security", "priority:high"],
"automerge": true,
"schedule": ["at any time"],
"prPriority": 10
}
}
|
Security updates bypass the normal schedule and have elevated priority. If CI passes, they automerge immediately.
Restricting Renovate’s Access
Renovate only needs write access to create branches and PRs. For sensitive repositories, scope the token:
repo:write (create branches, open PRs)
read:org (discover repositories)
Do not give Renovate admin access, secrets access, or deployment permissions.
Reviewers on Automated PRs
Even with automerge, require at least one passing CI check before merge:
1
2
3
4
5
|
{
"automerge": true,
"automergeType": "pr",
"platformAutomerge": true
}
|
Combined with branch protection requiring status checks, this ensures Renovate never merges a broken dependency.
A Realistic Production Config
Here’s a complete, production-ready renovate.json for a typical web application:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"group:monorepos",
":separateMajorReleases",
":semanticCommits"
],
"timezone": "America/New_York",
"schedule": ["after 11pm and before 6am every weekday", "every weekend"],
"prConcurrentLimit": 8,
"prHourlyLimit": 3,
"labels": ["dependencies"],
"semanticCommitType": "chore",
"semanticCommitScope": "deps",
"osvVulnerabilityAlerts": true,
"vulnerabilityAlerts": {
"labels": ["security"],
"automerge": true,
"schedule": ["at any time"]
},
"packageRules": [
{
"description": "Automerge patch updates after 3 days",
"matchUpdateTypes": ["patch"],
"minimumReleaseAge": "3 days",
"automerge": true
},
{
"description": "Automerge devDependency minor updates after 7 days",
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["minor"],
"minimumReleaseAge": "7 days",
"automerge": true
},
{
"description": "Group ESLint ecosystem",
"matchPackagePatterns": ["^eslint", "^@typescript-eslint/", "^eslint-"],
"groupName": "ESLint ecosystem",
"automerge": true,
"minimumReleaseAge": "5 days"
},
{
"description": "Group testing libraries",
"matchPackagePatterns": ["^@testing-library/", "^jest", "^vitest", "^msw"],
"groupName": "Testing libraries"
},
{
"description": "Group TypeScript types",
"matchPackagePatterns": ["^@types/"],
"groupName": "TypeScript types",
"automerge": true,
"minimumReleaseAge": "3 days"
},
{
"description": "Pin Docker digests, automerge digest updates",
"matchManagers": ["dockerfile"],
"pinDigests": true
},
{
"description": "Automerge Docker digest updates",
"matchManagers": ["dockerfile"],
"matchUpdateTypes": ["digest"],
"automerge": true
},
{
"description": "Pin and automerge GitHub Actions patches",
"matchManagers": ["github-actions"],
"pinDigests": true,
"matchUpdateTypes": ["patch"],
"automerge": true,
"minimumReleaseAge": "3 days"
},
{
"description": "Major updates need review",
"matchUpdateTypes": ["major"],
"automerge": false,
"labels": ["major-update"],
"reviewers": ["team:platform"]
}
]
}
|
This config:
- Runs during off-hours to avoid PR noise during the workday
- Limits PR creation rate to avoid overwhelming CI
- Automerges safe patches and dev-dependency minors with a stability wait
- Groups related packages to reduce PR count
- Pins Docker and GitHub Actions to digests for supply chain security
- Always requires human review for major updates
- Handles security vulnerabilities immediately at any time
Dependency management shouldn’t be a manual chore. With Renovate configured this way, the default state is “dependencies are current” rather than “dependencies are drifting and we’ll deal with it when something breaks.” The occasional major update PR becomes a deliberate decision rather than a months-long deferral.
Comments