tooling

Automate dependency updates with Renovate

Updated
7 min read

Every modern web project has a plethora of dependencies bundled together. It may not seem much initially, but managing dependencies becomes a project’s greatest maintenance nightmare in the long run.

The hard truth is dependencies go out-of-date rapidly, with patches and minor releases coming out every other week. It is important to keep them updated continuously rather than deferring this activity for later every time. The added advantage is that updates could bring potential bug fixes and patches for critical security vulnerabilities. (or introduce new ones as well, we’ll see how we can tackle this later in the article)

Developers dread getting assigned a dependency updation task especially when it’s been ages since they’ve last done it.

Boromir is frustrated dealing with dependencies

Even Boromir is frustrated!

We should leverage the tools available to solve such problems. Renovate is one such tool that does the job swimmingly!

Setup Renovate

Let’s take a look at setting it up. There are three ways to set up Renovate:

  • Self Hosting a docker image
  • Using the built-in GitHub / GitLab app
  • NPM package

Usually, self-hosting is the better approach for private projects, but as my use case already revolved around projects hosted on GitHub, we’ll take that route instead.

You can add the GitHub app from the marketplace and give it access to your repository.

Renovate will create an onboarding PR shortly to merge a renovate.json configuration file into your repository.

Onboarding PR created by Renovate Bot

Onboarding PR created by Renovate Bot

A basic configuration file is created at the repository <root> with the PR.

json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:base"]
}

Renovate will now scan your package.json file for dependencies. The default update style is via PRs which are raised for all dependencies, dev dependencies, and peer dependencies. (you can customize this)

Renovate raised a PR automatically to update eslint-plugin-import

Renovate raised a PR automatically to update eslint-plugin-import

A PR is raised with details such as release notes, age, adoption rate, and more for every dependency. This helps us make an informed decision and ensures we stay in the loop in case there are any breaking changes or vulnerabilities.

Supported package managers include npm, yarn, and pnpm, so your lock files will be kept in sync with the updates too.

The customizability part is where it really shines. Let’s check out some ways to make it even more seamless.

Schedules

You can set schedules to tell Renovate when to run the updates.

An example schedule looks like:

json
"schedule": [
  "before 11am every saturday"
]

The default timezone is UTC which you can override:

json
"timezone": "Asia/Kolkata"

You can also set it to rebase automatically whenever the PR is behind to avoid merge conflicts. It can come in handy when we are running it on a schedule.

json
"rebaseWhen": "behind-base-branch"

With scheduling, I know precisely when the updates will be raised which makes me plan better for that day.

Refer to the documentation on schedules to learn more about the syntax.

Grouping updates

Let’s consider we have 10 dependencies in our repository that are out-of-date. Renovate will see this and raise 10 PRs on your branch. A better approach is to group dependency updates that belong together.

You can think of creating meaningful groups. For example, you can group all updates to eslint and its plugins in a single PR.

json
{
  "packageRules": [
    {
      "matchPackagePatterns": ["^eslint"],
      "groupName": "eslint and its plugins",
      "groupSlug": "eslint"
    }
  ]
}

Or you could create a generic group of minor and patch releases for all dependencies. Now, all updates for the group happen in a single PR.

json
{
  "patch": {
    "groupName": "non-major dependencies",
    "groupSlug": "minor-patch"
  },
  "minor": {
    "groupName": "non-major dependencies",
    "groupSlug": "minor-patch"
  }
}

Think deeply as to which dependencies belong together. There could be some you know that are always used together, for example, build-time dependencies used for image compression.

Automerge

By default, Renovate raises a PR that must be manually reviewed and merged. While this flow is ideal for any critical dependencies and updates to major versions but for dev dependencies or any trivial ones, this will slow you down.

To configure Renovate to automerge the PRs:

json
{
  "packageRules": [
    {
      "matchUpdateTypes": ["minor", "patch"],
      "matchPackageNames": ["rimraf"],
      "automerge": true
    }
  ]
}

This will automerge PRs for the rimraf package only for minor and patch releases, but for major releases, you must merge the PR manually.

You can even tell Renovate to automerge a branch instead of a PR. Whenever a PR gets merged on GitHub, you get an email adding noise to your mailbox.

To avoid it, we can tell Renovate to create a temporary branch instead and merge it automatically and silently in ninja style!

json
{
  "automerge": true,
  "automergeType": "branch",
  "automergeStrategy": "rebase",
  "commitBodyTable": true
}

If you prefer to avoid merge commits, then set rebase as the automerge strategy.

In the branch strategy, details such as release notes and adoption rates are absent as they are generated only for PRs. So, we can set the commitBodyTable flag to at least get the bare minimum details.

datasourcepackagefromto
npmlint-staged13.1.013.1.1
npmrimraf3.1.03.1.1

Renovate creates branches with the prefix renovate/, so you can configure your CI to run relevant tests and build jobs on it before it gets merged.

On a GitHub repository, Renovate requires passing status checks before merging, if you don’t have any workflows configured you can bypass this temporarily:

json
{
  "ignoreTests": "true"
}

Learn more about automerge on Renovate’s official docs.

Dependency Dashboard

The Dependency Dashboard is one of the coolest features of Renovate. If enabled, Renovate creates an issue in your repository containing the status of all your dependencies about to be merged, and this works nicely when automerge and scheduling are enabled.

Dependency Dashboard listing all pending updates

Dependency Dashboard listing all pending updates

To enable it:

json
{
  "dependencyDashboard": true
}

An additional feature supported by the Dashboard is package approval. For example, if you have automerge enabled, you can tell Renovate that all major dependencies need to be approved first before are automerged.

All major dependencies require approval

All major dependencies require approval

You can configure it like so:

json
{
  "major": {
    "dependencyDashboardApproval": true
  }
}

Or you can even tell it require approval only for certain packages:

json
{
  "packageRules": [
    {
      "matchPackagePatterns": ["^my-package"],
      "dependencyDashboardApproval": true,
    }
  ]
}

Not all platforms support this feature. Here is an excerpt from the Renovate docs:

Learn more about the dashboard in the docs.

My Configuration

I’ve set up Renovate for some of my projects and NPM packages. My main focus was on reducing PR noise and simplicity.

Scheduling is set for before 11am every saturday with branch automerge and package groups set up for minor + patch and major versions. The dashboard approval flow is set for all major versions.

renovate.json
json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:base"],
  "labels": ["dependencies"],
  "schedule": ["before 11am every saturday"],
  "timezone": "Asia/Kolkata",
  "enabledManagers": ["npm"],
  "rangeStrategy": "bump",
  "automerge": true,
  "automergeType": "branch",
  "automergeStrategy": "rebase",
  "commitMessagePrefix": "chore(deps): ",
  "commitBodyTable": true,
  "dependencyDashboard": true,
  "dependencyDashboardAutoclose": true,
  "configMigration": true,
  "platformCommit": true,
  "lockFileMaintenance": { "enabled": true },
  "rebaseWhen": "behind-base-branch",
  "patch": {
    "groupName": "non-major dependencies",
    "groupSlug": "minor-patch"
  },
  "minor": {
    "groupName": "non-major dependencies",
    "groupSlug": "minor-patch"
  },
  "vulnerabilityAlerts": {
    "labels": ["security"]
  },
  "major": {
    "automerge": false,
    "dependencyDashboardApproval": true,
    "commitMessagePrefix": "chore(deps-major): ",
    "labels": ["dependencies", "breaking"]
  },
  "packageRules": [
    {
      "matchPackageNames": ["node"],
      "enabled": false
    },
    {
      "matchDepTypes": ["peerDependencies"],
      "enabled": false
    }
  ]
}

I’ve also disabled the auto-updation of peer dependencies and the Node version since updating them can be problematic for other dependencies.

With this configuration, I take one quick look at Renovate’s dashboard early morning on Saturday while having my hot cup of coffee and approve requests seamlessly. And this is precisely the kind of automation I was looking for

Wrapping up

Renovate is free, feature-packed, has a regular maintenance schedule, and is used globally by several top-tier companies.

I enjoy automating things like this. I used to constantly think about what would happen to my projects given the rapidly changing state of the Web. And this is what led me to Renovate. In a way, it has helped me stay on top of the latest advancements. I consider it a prerequisite now for setting up any new project.

Take a look at its super detailed docs. You can also check out my repository as a reference.

So, what are you waiting for? Time to get automatin! 🚀