Automatic dependency updates – a Renovate Bot introduction

Renovate bot is a tool that automatically updates software dependencies declared in your Git repository via pull requests. In this Renovate bot introduction I explain the benefits of automated dependency updates, how the Renovate bot works, how to operate/run the bot yourself (if necessary), and how you can configure the bot’s behavior for each repository.

Originally posted on 2021-07-11, updated on 2023-07-30.

Renovate bot introduction

Renovate (Bot) is a CLI tool that regularly scans your Git repositories for pinned software dependencies (e.g. Node modules in package.json). It then checks the corresponding repositories that host these dependencies (e.g. npmjs.com) for newer versions. If it finds any, it automatically creates a branch and a Pull Request (PR) that contains the updated dependency version. For GitLab, the terminology for PR would be Merge Request.

Renovate is highly configurable. For instance, you can instruct Renovate to fully automatically merge the PRs it created, given that your automated tests (run by the CI pipeline for the PR) have successfully passed. You can also customize the configuration for specific dependencies – e.g. tell Renovate to ignore updating a particular dependency, because you are using an outdated version of it, on purpose, for some good reason.

Why use automated dependency updates at all?

Who needs a tool like Renovate, and why? I would say: any software development team! In any case, not updating your dependencies is not a viable option. That would cause technical debt to quickly accumulate, without you being even aware of it. Renovate provides transparency regarding how many dependencies are outdated, and eliminates the routine activity of manually checking which dependencies are out of date. Even if you don’t plan to actually be on the latest and greatest version for every dependency, transparency is still a good thing, as it helps you plan and schedule the maintenance work for dependency updates. Updating dependencies regularly with tool support (as opposed to updating them very rarely) has the following benefits:

  • The increment of each individual upgrade is small (e.g. just a minor version update), which means that the update bears less risk of breaking your application.
  • It is easier to plan the work, because integrating small increments is less work.
  • The chances of being affected by security vulnerabilities is smaller, if you update to the latest version of a dependency, shortly after it has been released.
  • It pushes your team towards building better automated tests, because then you can configure Renovate to fully automatically merge successfully-tested PRs, reducing the maintenance work even further.

Supported SCM platforms and dependency types

Renovate only supports a selected set of SCMs, including GitLab, GitHub, Bitbucket, Azure DevOps and Gitea (including the SaaS and self-hosted alternatives). These are probably the most popular SCM platforms that offer PRs. For those SCMs, Renovate knows how to use their APIs, e.g. to authenticate, create branches, or open PRs.

Similarly, for the dependency types, Renovate supports most of the popular dependency systems (see here for a complete list), such as Gradle/Maven (Java) or Cargo (Rust). You could argue that those (popular) dependency types that are not supported (e.g. CMake for C/C++) also don’t offer a central repository that Renovate could query, to find newer versions of a dependency.

Using Renovate Bot

How you choose to run Renovate highly depends on the SCM platform. If you happen to use GitHub.com (SaaS), then you don’t need to configure any operations-related aspects. As documented here, the makers of Renovate (Mend) provide and maintain the Renovate GitHub App, which you can install into your repository with a single click. In other words: you don’t have to worry about things like making sure that the most recent Renovate Bot version runs, and that it runs regularly – Mend does all that for you.

However, if you use any other SCM platform, e.g. GitLab (self-hosted or SaaS), you have to take care of these operations-related aspects yourself!

The following sections go into further detail about how you can operate the Renovate bot yourself (Operations section), and how developers can configure what the bot does when visiting a repo (Development section), including the ability to use a different configuration for each repository.

Operations (self-hosting)

The bot can be run in a few ways (see docs), such as:

  • Run as NPM CLI tool (installed via “npm install -g renovate“, in an environment where npm is available), e.g. triggered regularly via a cron job
  • Run as a Docker image: for instance, you install Docker on a Linux box, and have a cron job create and run a Docker container for the ready-to-use renovate/renovate image in regular intervals.
  • Run in Kubernetes, as CronJob
  • Run as pipeline in GitLab CI, which you can regularly trigger using a schedule (docs)

Irrespective of the concrete environment you choose, you need to configure the bot so that it knows which SCM instance to connect to, or which repos to scan. The configuration can be either done as a config.js / config.json file, or via CLI arguments, or environment variables. The configuration options include:

  • The type of SCM you are targeting with the bot (e.g. gitlab vs. gitea)
  • The SCM server URL (e.g. https://github.company.com/api/v3 for GitHub Enterprise, or https://gitlab.company.com/api/v4 for a self-hosted GitLab instance)
  • Authentication credentials: the typical approach is that you create a separate (Bitbucket / GitLab / GitHub / Gitea / Azure DevOps) account for the bot, and then create some kind of personal access token for that account. You configure the bot to use this personal access token, and thus any actions it performs (like creating a branch or PR) will be done with that user account. For GitHub Enterprise, using a GitHub app as user account is recommended, see here for details.
  • A GitHub.com personal access token: even though your chosen SCM platform is not GitHub.com (since you are choosing the self-hosted approach), the Renovate bot still needs a GitHub.com personal access token, to avoid that you hit the rate limit of the github.com API (which the bot queries a lot, to find new releases or load change logs). I recommend you create a new GitHub account for this, and create a personal access token (the “classic” variant!) as described here. You don’t need to check any checkboxes in the scopes / permissions dialog when creating the PAT.
  • The Git identity the bot should use for commits it creates (e.g. “Renovate Bot <bot@your-org.com>“)
  • Which repositories the bot should scan: you can either configure the autodiscover mode, where Renovate runs on every repository that the bot’s SCM account can access, or you explicitly declare a list of repositories.
  • Various other options, as explained in the Self Hosted Configuration documentation page.
  • Repository-specific options (further elaborated on in the Development section below) that you want to configure globally for any repository by default. See all those configuration options on the Repository configuration options documentation page that indicate that they are mergeable. A good example is the hostRules key which lets you configure credentials for various repositories (e.g. a private Artifactory server). Renovate automatically merges the repo-specific settings from the config.js file with the repo-specific settings of the repository itself (renovate.json[5]).

Once the bot is running (e.g. triggered once per hour via cron or a scheduled CI/CD pipeline), the development teams need to configure it to visit their repositories. This either happens when a developer invites the Renovate Bot’s SCM account into their repo (if the bot is configured in autodiscover mode), or the developer asks the bot’s operations team to add the repo’s name explicitly in the config.js file (if the bot is configured with a list of repositories).

If you are interested in running your own Renovate bot instance in GitLab, take a look at the official GitLab runner repository.

Development

Whenever the Renovate bot scans your repository, it uses a configuration file such as renovate.json5, located at the root of your repo. It tells the bot how it should behave specifically for this repository. The reference for what you can put into this file is found on the Repository configuration options documentation page. Don’t confuse this renovate.json5 file with the config.json file that you (may have) used to configure the bot’s operational aspects (in case you are self-hosting the bot, which I explained above)!

To avoid that the Renovate bot wreaks havoc the first time it visits your repo (due to an incorrect configuration), the bot’s default behavior is to first initiate an onboarding phase (as described in the docs). The bot basically runs the following logic:

  • If there is a renovate.json5 file (or one of its alternatives) in the root of the repo’s default branch (typically master / main): the bot assumes onboarding has completed, and it will scan your repo for outdated dependencies (unless you set enabled to false, in which case Renovate will leave your repo immediately)
  • Else:
    • Is there already an onboarding-PR (typically named “Configure Renovate”) in the repo?
      • If yes:
        • If it is still open, or already closed (but not merged): don’t further scan the repo – wait for the user to merge that PR first
        • If it is merged: don’t further scan the repo – there used to be a renovate.json5 file in the past (created when merging the onboarding PR) in the default branch. But since then, the devs have deleted the file from the default branch on purpose, indicating that they no longer want the repository to be scanned.
      • If not: the bot creates a new onboarding PR titled “Configure Renovate” (and a corresponding Git branch, typically named “renovate/configure“) that contains a commit with a new renovate.json[5] file.

The description text of the onboarding PR explains many details, such as the detected dependency types (e.g. pip, docker, etc.), a summary of Renovate’s configuration in understandable plain English (rather than printing the configuration JSON object), and which PRs the bot would create on its next visit, given that you merged the onboarding PR by then.

The initial content of the renovate.json5 file will be a very basic configuration. Typically, the developer tweaks its content prior to merging the onboarding PR, by checking out the “renovate/configure” branch and updating the file’s content with a new commit. Note that the developer may want to use the renovate-config-validator CLI tool (see docs) to validate the renovate.json5 file, prior to committing. The next time the Renovate bot visits the repo (which may take a little while), it detects that you changed the renovate.json5 file in the (still-open) onboarding PR, and updates the PR’s description accordingly.

Of course, you can continue updating the renovate.json5 file even after the onboarding PR was merged. Renovate always uses the most recent renovate.json5 file located at the root of the repository’s default branch. You can change this behavior via the configuration options useBaseBranchConfig and baseBranches.

Understanding the configuration file

A developer who wants to tune the Renovate configuration for their repo for the first time will be overwhelmed by Renovate’s configuration flexibility. On my first attempt, I had questions like these:

  • There are dozens of Configuration Options – which ones should I change, and what are their default values?
  • How do packageRules (reference docs) work, which tweak the bot’s behavior for specific packages?

Understanding default values

In a typical onboarding PR, the configuration file looks like this:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"]
}
Code language: JSON / JSON with Comments (json)

This file looks rather empty, so your first instinct might be to just look at the many options on the Configuration Options page and inspect their default values. For instance, you might think that the Dependency Dashboard feature is disabled (see here) or that there are no packageRules configured by default.

However, this is not true, because of Renovate’s presets feature. The above example makes use of this feature, by inheriting from the config:recommended preset, as indicated by the highlighted line. As explained here, a preset essentially bundles one or more configuration options, setting specific values for them. Presets can be hierarchically nested, to increase their level of reuse. You can reference presets that you host yourself, via a notation like “gitlab>abc/foo:xyz.json5” (docs). However, you are most likely going to use the built-in presets shipped with Renovate, referencing then by a string of the form
<preset-category>:<preset-name>“. Each preset category has its own page in the Renovate documentation that explains its concrete presets:

  • config:<preset-name>” presets are explained on the Full config page
  • :<preset-name>” (which is the same as "default:<preset-name>") are explained on the Default presets page
  • All other presets (“<something>:<preset-name>“) have a corresponding preset page named like “something” (e.g. “docker:<preset-name>” presets are on the Docker presets page)

So, in the above renovate.json5 example file, the config:recommended preset is expanded to many other presets (such as ":ignoreModulesAndTests"). Next, these more concrete presets are translated to actual configuration options, as found on the Configuration Options page.

If you want to dive deeper into the topic, it makes sense to study these presets referenced by config:recommended more closely, to understand which of these default settings may not be suitable for your project. But if you don’t have time for that, just leave the defaults, then tweak the rules over time whenever you discover that the bot’s behavior is not suitable.

The main problem with the default config:recommended preset is that the bot creates PRs immediately after the bot discovers an outdated dependency. If that dependency is updated often (e.g. multiple times per day), this also results in many PR updates in a short amount of time, and a lot of (email) spam and merging-work. The Noise reduction page has pointers on how to handle this problem.

Understanding packageRules to tweak your config

The packageRules key in the renovate.json5 file lets you fine-tune / override the bot’s behavior for specific dependencies. Selecting these dependencies is done via the matchXXXX keys, e.g. matchFileNames, matchPackagePatterns or matchUpdateTypes. packageRules is an array of (JSON) objects. The allowed keys in each object can be one or more of the sub-keys explained in the packagesRules documentation (like matchPackageNames), but can also be (most of the) other keys documented on the Configuration Options page, like automerge.

You should know that all rule objects in the array are valid at the same time! The matching-logic does not work as you know it from many other systems, where rules #3 and #4 are no longer evaluated if rule #2 was a match. Instead, the better matching rules take precedence. For instance, if rule #1 sets automerge=true for matchPackagePatterns=["foo"] and rule #2 sets automerge=false for matchPackagePatterns=["foo"] and matchUpdateType=["major"], then automerge would be true for all dependencies with an updates type of minor or patch. However, the order of rule objects in the packageRules array does still matter: rules declared later (towards the end of the array) overwrite values of an equally-well-matching rule declared earlier.

Let’s look at a concrete example:

{
...  // other stuff here, e.g. "extends": ["config:base"],
"packageRules": [
    {
      // Whenever Renovate finds updates for any (*) package (except those starting with
      // @material-ui, @types, react, or typescript), it will create a single(!) branch+PR for
      // all the detected changes, instead of creating multiple branches/PRs (one per package)
      "matchPackagePatterns": [ "*" ],
      "excludePackagePatterns": [
        "^@material-ui",
        "^@types",
        "^react",
        "typescript"
      ],
      "groupName": "deps" // note: "deps" is just a self-chosen name without any meaning
    },
    {
      // Renovate will create a single branch/PR for any changes detected for NPM/YARN
      // dependencies of type "dependencies" and "devDependencies" which start with @material-ui
      "matchPackagePatterns": [ "^@material-ui" ],
      "groupName": "Material-UI",
      "matchDepTypes": [
        "dependencies",
        "devDependencies"
      ]
    },
    ...
  ],
...
}Code language: JSON / JSON with Comments (json)

I provide a list of recommended configuration options in the cheat sheet article.

Understanding update types

When the author of a software dependency offers an update, there are different types of updates. Renovate distinguishes between the following different types, which you should understand properly:

  • major: when the dependency version changed from e.g. 1.0.3 to 2.0.0
  • minor: when the dependency version changed from e.g. 1.0.3 to 1.1.0
  • patch: when the dependency version changed from e.g. 1.0.3 to 1.0.4
  • digest: when the dependency’s version did not change, but the checksum of the binaries have updated. At the time of writing, Renovate bot only seems to be detect this update type for Docker images. As an example, consider a specific Python Docker image you use in your software, e.g. python:3.9.2 – this particular image tag still changes regularly over time, because the Python release team has a CI pipeline that rebuilds the python:3.9.2 image regularly (e.g. to keep up with the latest security patches in the base OS image). The digest will be different for each built image.
  • rollback: applies when Renovate detected that the concrete version of the dependency referenced in your Git repo is no longer available in the corresponding artifact repository, e.g. because the dependency was removed by the author as it was incorrectly built or buggy. By default, Renovate bot will leave such dependencies untouched, but you can enable the rollbackPrs feature to change this behavior.

These update types are used in different places in the renovate.json5 file, e.g. as value in the matchUpdateTypes element (within packageRules), in bumpVersion, or as a key on the root level of the configuration JSON object (e.g. major)

Conclusion

Renovate is the perfect tool to automate the maintenance of software dependencies. It offers a bit more configuration flexibility, compared to other solutions such as GitHub’s dependabot. This is, however, also a bit of a downside! You should expect increased workload and “annoyed” developers at the beginning (during the onboarding phase), because it takes time to configure the Renovate bot appropriately. The configuration is a bit tricky, due to the huge flexibility.

I recommend that whoever introduces Renovate reads the docs carefully and builds a cheat sheet for the rest of your company (your developers), which includes the most common configuration options. Assume that your developers don’t want to spend a huge amount of time learning Renovate’s configuration syntax, but the default values (of the config:recommended preset) are often unusable in practice, leading to a massive amount of spam. Take a look at the cheat sheet I created in this article!

Once you have become somewhat familiar with Renovate, I also recommend my advanced tips and tricks article.

By the way: if you are operating the bot yourself, I recommend you change the configuration file name created as part of the onboarding from renovate.json to renovate.json5. This lets your developers add explanatory comments to their JSON file. You can make this change in the bot’s configuration (see onboardingConfigFileName).

Leave a Comment