Start of Main Content

Maintaining dependency updates is one of the most common ongoing tasks you’ll encounter in any web application or software project. At Velir, we take them very seriously, so we employ proactive measures to ensure our clients don’t fall prey to security vulnerabilities.

Dependencies for a project can come from multiple people pulling in third-party libraries and packages. Examples range from NodeJS NPM packages, Docker images, Go modules, Python libraries, NuGet, Maven, or Composer PHP packages.

Like all software, dependencies are often updated and typically contain bug fixes or new features, but once and a while, security updates for them are released without notice. When you learn about these updates, you should install them on your production server as soon as possible. Failure to update will put your application and users at risk of a security breach since attacks are increasing year over year.

Here are some recent examples of security vulnerabilities:

Keeping track of updates is a daunting task whether you have one site or dozens. What makes it even harder is that your dependencies may have multiple dependencies of their own, and those may have dependencies too. 

Fortunately, some tools can help you stay ahead of the game. In this post, we’ll look at one called Renovate which automates dependency updates. It can be used for both GitHub and GitLab, the two most popular code repository services in the world.

The rest of this piece assumes that you know either GitHub or GitLab as your day-to-day code management service. It also assumes that you utilize your package managers effectively, specifying your dependencies under appropriate semantic versioning guidelines for your language, platform, and package manager of choice. Each system may work a little bit differently, so please familiarize yourself with those first before attempting to automate updates.

We use Drupal, Docker, and NPM to demonstrate how you can automate those technologies, but our examples will fit generally any package manager or platform you work with.

Need assistance automating your website updates and keeping up with important security patches? Reach out.

Our experts can help you configure automatic updates that prevent you from falling victim to the latest security vulnerabilities.

Renovate

Renovate is a dependency scanning tool made by the folks at Mend. Renovate can analyze your project to automatically find which dependencies have updates. When it finds an update, it will create a pull request for those changes to be merged into the main branch. From here, you can review the proposed changes and merge them. 

This means that you can keep working on value-add features while Renovate functions in the background. Without Renovate, you must go through all your dependencies per technology one by one and check for updates. Then you need to parse them out (is it a bug fix, feature addition, or security update), and branch and create pull requests for each one, which can take a lot longer on a per-project basis. 

Renovate supports most major package managers across Rust, Go, PHP, C#, Python, Ruby, Java, Docker, and more—making it a great tool to work into any stack or type of project—web-based or not. The online docs contain a wealth of information, and the tool has a plethora of configuration options, but we’ll walk you through the important bits to get you started.

Setting up the Workflow

First, we’ll walk through an example project to show you how Renovate can help keep your dependencies up to date.

We have a project built with Drupal 9 and a handful of contributed modules. The front end is built using Storybook and an assortment of NPM packages. Local development is handled within a custom Docker stack using Apache, PHP, MySQL, Traefik, and Solr for search. There are several aspects that we depend on, and we must keep them updated, particularly when there is a security release. Developers can’t be vigilant 24 hours a day but Renovate can.

Our project lives in GitLab and contains a basic GitLab CI setup for building and running integration tests. With continuous integration, you should know that every pull request that Renovate opens automatically is subject to the same continuous integration checks. This helps ensure and monitor the stability of your code before approving merges. Writing automated tests and continuous integration scripts is outside the scope of this post but you can easily look up how to create them.

Creating a New Project

We need to create a new project in GitLab which Renovate will operate from. We’ll provide just enough configuration to make Renovate run, and then use the Scheduler in GitLab to run the task every 2 hours.

The “Create blank project” screen in Gitlab, where a new project is being created to run Renovate.

To run Renovate, you’ll need to create a new project for it in GitLab.

Go ahead and create a blank project. We’ve named ours “Renovate Bot” and set it as a private repository.

Configuring Renovate

We need to do three things for this project:

  • Create a .gitlab-ci.yml script
  • Create a config.js file for Renovate
  • Create a personal access token for Renovate

We can start by adding a GitLab CI script. This is straightforward, and while there are several things you could do in this pipeline script, not much is needed to enable Renovate to run on schedule:

image: renovate/renovate:32.195 

variables: 
  RENOVATE_GIT_AUTHOR: Renovate Bot <[email protected]> 

renovate: 
  script: 
    - renovate $RENOVATE_EXTRA_FLAGS 
  only: 
    refs: 
      - schedules

This script is using the official renovate/renovate Docker image. When it runs this job, it’s executing renovate with general options and only runs on schedules—this is a GitLab-specific indicator that says that this step should only run when scheduled.

Next, we need to set up Renovate so it has some baseline configuration when it runs. There are several items you can set in this file and the Renovate docs go through them thoroughly—but sticking with our project scenario outlined above, we can set some minimal configuration to get going. 

Create a file called config.js, and inside it, place the following:

module.exports = { 
  endpoint: 'https://gitlab.com/api/v4/', 
  token: process.env.GITLAB_TOKEN, 
  platform: 'gitlab', 
  enabledManagers: ['composer'], 
  prHourlyLimit: 0, 
  rebaseWhen: "behind-base-branch", 
  "constraints": { 
    "php": "^8.0" 
  }, 
  onboardingConfig: { 
    extends: [ 
      "config:base", 
      ":preserveSemverRanges", 
      ":rebaseStalePrs", 
      ":enableVulnerabilityAlertsWithLabel('security')", 
      "group:recommended" 
    ] 
  }, 
  repositories: ['YOUR-ORG-NAME/YOUR-REPONAME'], 
}; 

This is mostly a boilerplate to get you started. The code informs Renovate that we want to target the Composer package manager (for PHP). It also tells it when to rebase changes, the platform endpoint it will use to talk to repositories, and then at the end, the actual project repositories we want to support for updates. We only listed one repository above, but you can enter as many as you need. For a Drupal-based project, this would be enough to support any site running Drupal 8 or higher. 

When Renovate runs, it can update every repository it has access to automatically. For example, if you have 15 projects and there was a security release that came out for Drupal core—all 15 would have pull requests generated by Renovate and ready to roll, significantly accelerating your operations.

The last step is to generate a personal access token and add it to your Renovate project under CI/CD Settings > Variables:

The “Variables” screen in GitLab which shows a protected, masked variable for all environments called “GITLAB_TOKEN”.

You’ll need to create a protected, masked, personal access token variable for your Renovate project in GitLab.

This value can be read in config.js script in the line token: process.env.GITLAB_TOKEN. This allows Renovate to talk to all the repositories that you have access to—the same ones listed in the repositories line of the configuration.

Now we can go configure our downstream projects to take advantage of Renovate.

Configuring a Project to Use Renovate

With the baseline configuration of Renovate in place, we use our example project from the start of this post to configure how Renovate responds to updates. To do this, we need to place a renovate.json file at the root of the project and commit it.

In our example Drupal project, that would look like this:

{ 
  "$schema": "https://docs.renovatebot.com/renovate-schema.json", 
  "gitAuthor": "Renovate Bot <[email protected]>", 
  "extends": [ 
    "config:base", 
    ":preserveSemverRanges", 
    ":rebaseStalePrs", 
    ":enableVulnerabilityAlertsWithLabel('security')", 
    "group:recommended" 
  ], 
  "packageRules": [ 
    { 
      "managers": ["composer"], 
      "matchPackagePatterns": ["^drupal/"], 
      "rangeStrategy": "update-lockfile", 
      "groupName": "Drupal contributed modules" 
    }, 
    { 
      "managers": ["composer"], 
      "matchPackagePatterns": ["^drupal/core-recommended", "^drupal/core-composer-scaffold", "^drupal/core-project-message"], 
      "rangeStrategy": "update-lockfile", 
      "groupName": "Drupal core" 
    }, 
  ] 
}

The key part of this is the packageRules section. Package rules allow you to dictate exactly how Renovate should handle updates for packages, even ones within the same package manager (like Composer). This lets you split out updates into individual parts which is useful when dealing with updates for Drupal core versus those for contributed modules or themes. Otherwise, you wind up with a single pull request containing updates for everything, which is too risky to deploy.

You can group fairly “trusted” modules, like Pathauto, Token, or Metatag for Drupal into the same group while setting aside less “trusted” modules for their own pull request or testing. That would be another set within packageRules.

    { 
      "managers": ["composer"], 
      "matchPackagePatterns": ["^drupal/module_name"], 
      "rangeStrategy": "update-lockfile", 
      "groupName": "(Module Name) update" 
    }, 

This rule would now handle module_name on its own. You can repeat this as many times as your project requires.

How does Renovate know exactly how to update a package? We haven’t specified the type of updates we want, i.e., major, minor, or patch. What prevents it from bumping to the next major version of Drupal before we’re ready? The rangeStrategy configuration value forces Renovate to abide by how we’ve requested packages in the defined package manager. For Composer we require packages in composer.json under require:

"drupal/core-composer-scaffold": "^9.4.0", 
"drupal/core-project-message": "^9.4.0", 
"drupal/core-recommended": "^9.4.0", 
"drupal/memcache": "^2.0", 
"drupal/metatag": "^1.0", 
"drupal/pathauto": "^1.0", 

Every package manager is different in how version constraints work, so be sure to check the documentation for each one. In the case of Composer, ^9.4.0 will let us get any updates available from 9.4.0 or higher, but never version 10 or above.

Next, we’ll show you how to schedule Renovate to run, so you can see automatic updates in action.

Scheduling Renovate

Now that we have a Renovate project created and listed, our Drupal project in it needs to be updated. So, we’ll schedule Renovate to run.

Over in the project that we created for Renovate, go to CI/CD > Schedules and click on New Schedule:

A screenshot of the “Schedules” section under “CI/CD” in a GitLab project, which shows there are no schedules, and a button that says, “New Schedule”.

We can schedule Renovate to run every two hours:

A screenshot of the “Edit Pipeline Schedule” screen where the “Interval Pattern is set to “0*/2***” which means that it will run every two hours.

Now Renovate is ready to run! After we click Save pipeline schedule, it will run every two hours and check for updates.

When Renovate runs, new pull requests come in against our Drupal repository with pending updates. There are two groups just as we specified, one for contributed modules, and one for Drupal core updates.

A GitLab screenshot of available updates for Drupal contributed modules and information about merging them.

And the other for Drupal core:

A GitLab screenshot of available updates for Drupal core and information about merging them.

Pull requests are now created automatically when updates are available and stick within your specified range of updates. This automation frees up developers from doing this manually on a per project basis and performs the exact same task they would be doing otherwise so you can focus on valuable feature development instead.

Automerging Updates

Automating this work and creating pull requests is a big step forward. One last thing you can do to streamline the process is to make this an automated update. In Renovate this is called “automerge”. In a scenario where you have project dependencies that you do not want to delay deploying a critical update for, the automerge option makes that a reality.

In our package rules, we can enable automerge and set it to true:

   { 
      "managers": ["composer"], 
      "matchPackagePatterns": ["^drupal/core-recommended", "^drupal/core-composer-scaffold", "^drupal/core-project-message"], 
      "rangeStrategy": "update-lockfile", 
      "automerge": true, 
      "groupName": "Drupal core" 
    }, 

If we run Renovate again, we can see that automerge was enabled and this pull request was merged automatically:

A GitLab screenshot of automerged updates for Drupal core.

This update has been automatically merged and deployed to our web server, protecting us from any potential vulnerabilities and putting us on the latest patch version of 9.4.5.

Automerging has its advantages, but it is not without some added complexity. You may want to add steps in your continuous integration to obtain a copy of your production database, apply the Drupal update(s), then export any updated configuration to commit with the update before this gets automatically deployed—the same for contributed module updates. Configuration changes like that are somewhat rare, but they do happen. When they do happen, they may break a deployment. With a healthy and thoughtful continuous integration process, this becomes smoother the more you automate away.

Adding Docker and NPM

We’re now covered with any updates to Drupal or third-party packages that may occur, and we can copy our template into every project that needs it. This is a big step forward for our teams employing automation to stay up to date—but what about the other technologies in our stack we mentioned, Docker and NPM?

First, we’ll tackle NPM. NPM is very similar to Composer, so adding it to our package rules is straightforward. We need to enable NPM in the renovate.json configuration:

 "enabledManagers": [ 
    "composer", 
    “npm" 
  ], 

Then we can begin adding package rules:

{ 
      "managers": ["npm"], 
      "matchPackagePatterns": ["react"], 
      "matchUpdateTypes": ["minor", “patch”], 
      "rangeStrategy": "update-lockfile" 
}, 

Assuming you version your NPM packages for non-major versions, this small addition would keep ReactJS and relevant libraries up to date with the same approach we used for Drupal core and contributed modules above. You can add as many rules as you need here to cover different packages (beyond React). This is all we need to do—Renovate will now keep them updated.

We can employ the same style of rules for Docker too, whether you use Dockerfiles, Docker Compose, or a mix of both. Like NPM, we just need to enable it in the configuration:

 "enabledManagers": [ 
    "composer", 
    “npm", 
    “docker-compose” 
  ], 

By doing that, Renovate will now keep any Docker image found in your docker-compose.yml file up to date, like Traefik:

A GitLab screenshot showing that Traefik has been updated.

This is good for base utility images like Traefik, Mailhog, Memcache, or others—we don’t need to set any package rules for this to just work. What about PHP, Apache, or Solr? We certainly don’t want those to update major versions, since things would break.

One difference with Docker images from Composer and NPM though is that you may encounter images that don’t fit conventional semantic versioning notation. For example, if we’re using a PHP container versioned as “8.0-dev-4.37.10”, Renovate does not understand how to handle this out of the box in a logical way. You’ll encounter version schemes like this a lot in the Docker space.

Fortunately Renovate provides a way to handle these instances. We’ll once again add a package rule and this time we can inform Renovate how the version scheme works:

   { 
      "managers": ["docker-compose"], 
      "automerge": true, 
      "groupName": "Docker images [wodby]", 
      "matchPackagePrefixes": ["wodby/"], 
      "bumpVersion": "patch", 
      "versioning": "regex:(?\\d+\\.\\d+)-(dev-)?(?\\d+)\\.(?\\d+)\\.(?\\d+)$", 
    }, 

Without getting into deep details on regular expressions, know that Renovate supports describing how particular versioning schemes work for packages and package managers if need be. At a high level, this is telling it how to read the PHP tag above as (compatibility series)--(major)-(minor)-(patch).

After implementing that change Renovate now handles such images correctly:

A GitLab screenshot of a completed update to wodby, a Docker tag.

Conclusion

At this point, we’ve set up automated updates for our entire project stack to execute dependency updates no matter when they happen. With automerge and good continuous integration and testing packages, we’re confident that all projects that deploy this system will stay up-to-date, and won’t fall several months behind on updates. We’re now protected from security vulnerabilities without the risk of a data or application breach.

The examples we’ve covered in this piece only scratch the surface of what Renovate can do for you. From here you can experiment and tailor a setup that works for your project.

In a future post, we’ll take a look at a product from GitHub called Dependabot, another tool that helps you maintain automated dependency updates.

If you’re interested in learning how you can create secure automated updates for Drupal, Sitecore, Adobe AEM, or various frontend stacks, contact us. We’re happy to help you configure them, or to manage your automated updates ourselves.

Published:

Latest Ideas

Take advantage of our expertise with your next project.