Setup Dependabot to automatically update project dependencies on GitLab
Dependabot can be used to automatically update the dependencies of software projects. It not only works with open source projects on GitHub - you can set it up yourself in a private GitLab instance to keep your projects up to date and secure.
Keeping your own software and its dependencies up to date is one of the most important tasks in ensuring adequate security. However, it is often perceived as a tedious task and can cause a variety of problems in the long run if neglected. This can only be done manually with good and regular routines.
Dependabot provides help here by updating the package dependencies of a project as soon as the respective updates are released. A large number of languages are supported - Docker, PHP, JavaScript, Elixir and Go are particularly relevant for us at Sourceboat.
As soon as Dependabot is set up for a project, the configured dependencies are checked for updates at specially defined intervals. In the case of JavaScript, the package.json
is analyzed, in the case of PHP, the composer.json
is checked and for Docker updates, the FROM
statement in the respective Dockerfile
is compared with Docker Hub accordingly. If an update is available, a corresponding merge request will be created.
Dependabot automatically searches for available information such as changelogs, release notes or related commits since the last update and links them in the merge request.
From SaaS to Open Source Software
Dependabot was originally a SaaS for projects on GitHub.com. Public projects were free of charge and private projects could be integrated for a fee. Dependabot has now been purchased by GitHub and is to be permanently integrated into the platform. Any type of project can be added - this works quite easily after logging in via GitHub, but only with projects on GitHub.
However, the core of Dependabot is developed completely open source, so it is possible to run your own instance. This has enabled us to set up Dependabot in our own GitLab instance (and also on GitLab.com for another customer). The relevant repositories are:
dependabot/dependabot-core
, the heart of Dependabot. It contains the logic to resolve and update dependencies of different languages and to create the corresponding merge requests on GitHub, GitLab or Azure DevOps.dependabot/dependabot-script
demonstrates the use ofdependabot-core
. This example illustrates the setup for one or more of your own projects.
However, small adjustments are required for a smooth process with a recent version of GitLab using Docker runners. I will show you what our setup looks like in the following section.
Setup via GitLab
The setup requires a fresh GitLab project and runners that use the Docker executor. It is also recommended to create a separate user for Dependabot. This user will then have access to the projects to be checked and will be used to automatically create the merge requests. The actual work is done in CI jobs via scheduled pipelines, which can be set up individually for each project.
By the way, it does not matter whether it is GitLab.com or a private instance. In our case, the script runs both on the private instance and for a customer on GitLab.com.
Setup the Project
Exactly six files are required for a smooth process. The contents of dependabot/dependabot-script
can be used as the basis for the setup. However, we do not need all of it and need to make some adjustments.
First of all, the contents of .ruby-version
, Gemfile
and Gemfile.lock
can be adopted. This contains the Ruby dependencies for the Dependabot application. We also create a file called update.rb
and use the contents of generic-update-script.rb
. This file contains the actual update logic.
We create our own Docker image so that we can control the version of Dependabot we use. This has the advantage that we can automate the updating of the Dependabot version ourselves. This would not be possible if we defined the version in the .gitlab-ci.yml
. So we create the following manageable Dockerfile:
FROM dependabot/dependabot-core:0.118.7
Another nice side effect is that we store the image in our own registry. This means that we are not dependent on Docker Hub, at least for now. To build the image, we fill the .gitlab-ci.yml
with the following job definition:
build:
image:
name: gcr.io/kaniko-project/executor:debug-v0.24.0
entrypoint: [""]
script:
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile Dockerfile --destination $CI_REGISTRY_IMAGE:latest --cache=true
rules:
- if: $CI_COMMIT_BRANCH == "master" && $CI_PIPELINE_SOURCE != "schedule"
Then you add the base job for Dependabot, from which all other language-specific jobs will inherit.
.dependabot:
image: $CI_REGISTRY_IMAGE:latest
variables:
PACKAGE_MANAGER: $CI_JOB_NAME
COMPOSER_MEMORY_LIMIT: -1
before_script:
- mkdir ~/.ssh
- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- bundle install -j $(nproc) --path vendor
script:
- echo $PROJECT_PATH
- bundle exec ruby ./update.rb
cache:
paths:
- vendor/
For the before_script
to work properly, the SSH_KNOWN_HOSTS
environment variable must be created in the project in Settings > CI / CD > Variables. This will be added to the ssh-keyscan
result of the GitLab instance:
$ ssh-keyscan gitlab.mycompany.com
...
You will also need to set the following environment variables to allow Dependabot access to your projects. The latter is used to ensure that the rate limit of the GitHub API is not reached:
GITLAB_HOSTNAME
: the host name of the GitLab instance (e.g. gitlab.com or gitlab.mycompany.com)GITLAB_ACCESS_TOKEN
: a personal access token of the above-mentioned Dependabot user with theapi
,read_repository
andwrite_repository
scopesGITHUB_ACCESS_TOKEN
: a personal access token on GitHub.com with the scopepublic_repo
Now all that is missing are the job definitions for the different programming languages. All available languages can be seen in .gitlab-ci.example.yml
. It doesn’t hurt to add them all.
For NPM or yarn, this is as follows:
npm_and_yarn:
extends: .dependabot
only:
variables:
- $PACKAGE_MANAGER_SET =~ /(\bnpm|yarn\b)/
However, a new syntax is preferred since GitLab 13.0:
npm_and_yarn:
extends: .dependabot
rules:
- if: $CI_PIPELINE_SOURCE == "schedule" && $PACKAGE_MANAGER_SET =~ /(\bnpm|yarn\b)/
Once all the languages have been added, the foundation stone has been laid - the magic happens in the next step.
Setup the scheduled pipelines
The pipeline schedules in the Dependabot project are our overview of all projects. A scheduled pipeline is created for each project, which executes the Dependabot update script with a job for the desired language. The interval can be set as you wish.
When setting up the schedule, in addition to a meaningful description and the interval, three variables need to be set:
PROJECT_PATH
: the path to the GitLab project (e.g.mygroup/myproject
)GITLAB_ASSIGNEE_ID
: the ID of the user to whom the created merge requests are assignedPACKAGE_MANAGER_SET
: the package manager used in the project
For PACKAGE_MANAGER_SET
, any of the job names created above are available (e.g. npm_and_yarn
or composer
). Multiple package managers can be specified, separated by commas, and separate jobs will be started for each. Dependabot itself can then also be updated by specifying docker,bundler
here.
If the dependency config files are not located at the top level of the project, the path can be controlled using the DIRECTORY_PATH
environment variable.
Important: The Dependabot user must be added to the desired project with the Developer
role in order to be able to create merge requests.
Just wait and see…
Now it’s time to wait. Dependabot will now periodically check the dependencies in the configured project and create merge requests as soon as an update is available. Unfortunately, Dependabot can only handle one version of a language at a time. These versions are currently still hardcoded.
We are also looking forward to the upcoming Dart support for our Flutter apps. Since Dart 2.8, there is a pub outdated
command that provides the basis for Dependabot integration.