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.

September 17, 2020
7 min read
Software Docker

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 of dependabot-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 the api, read_repository and write_repository scopes
  • GITHUB_ACCESS_TOKEN: a personal access token on GitHub.com with the scope public_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 assigned
  • PACKAGE_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.

Back to posts

Software Docker
Phil-Bastian Berndt

Phil-Bastian Berndt
Tech Lead at Naymspace