Managing dependencies with Dependabot and GitHub Actions

Martin Angelov
May 31, 2022

All of our projects have external dependencies.

It's always a good idea to keep them up-to-date. Here are a few reasons why:

  1. Have you ever been in the stinky situation where you're wondering why a third-party functionality is not working as expected? After spending hours in the docs, you figure out that it's because you're using an older version of the package.
  2. The maintainers have released a new security update, new feature, bugfixes or even a new major version. You don't want to miss them out, right?
  3. The more you postpone the update of the packages, the harder it becomes. It's definitely easier to update from v3.9.9 to v4.0.0 than from v2.0.5 to v4.0.0

Dependabot

Fortunately, dependabot is here to save the day.

It's a tool from GitHub that can be easily integrated with your repositories. If you haven't heard about it yet, go and check their awesome docs.

What does it do?

Dependabot monitors for vulnerabilities and updates in the dependencies of your project.

It opens PRs, to a target branch, on a scheduled period of time.

Each PR contains:

  1. A commit where the dependency is updated. For example - it might be a change in the package.json or the requirements.txt file.
  2. Information about what is updated - usually placed in the description of the PR.

The pull requests are opened per dependency, meaning that if there are 10 packages that need to be updated, dependabot will open 10 PRs.

Additionally, when we merge something, dependabot will rebase the rest of its opened PRs. This is great because dependabot will automatically make sure that the PR is against the latest version of the target branch.

We're free to decide what to do with the dependabot's PRs:

  1. We can merge them.
  2. We can test them in isolation and merge after that.
  3. We can close them.

In this blog post, we'll tell you what's the process that we follow.

Configuration

We'll go through the basic depentabot setup.

Create a dependabot.yml file. It should be located in the  .github/ directory of your repository.

your_repository/
|-- .github/
|   |-- dependabot.yml

Add the following lines to the file and save it:

version: 2
updates:
  # Enable version updates for Python
  - package-ecosystem: "pip"
    directory: "/"
    # Check for updates once a week
    schedule:
      interval: "weekly"
NOTE! We're going to setup dependabot for a Python repository. Change the package-ecosystem if you'd like to make it work for another language. Here is a list of all available options.

You can find a full list of the .yml config options here.

Push this file to the default branch of your your origin and you're ready! Dependabot will start making PRs to your repository every week.

You can assert if everything is fine by checking the Dependency Graph under the Insights tab in your GitHub repository. Click on Dependabot and you should see something like this:

Be Careful

In most cases, you'd be good to go with the default Dependabot configuration.

In case merging dependency updates straight to your default branch sounds a bit reckless to you, we can improve our setup.

The potential issues of merging straight to the default branch are:

  1. Breaking your default branch with untested dependency updates.
  2. Blocking releases because of that.
  3. Blocking other developers.

If we want to avoid that, we have a couple of options:

  1. Merge the dependabot pull requests locally on your machine. Install and test the new dependencies and push them to the default branch after that.
  2. Change the target branch of the dependabot PRs

We prefer the second option. Here is what we need to do next to accomplish that.

Target branch

Create a new branch and push it to our origin.

$ git checkout master
$ git pull origin master
$ git checkout -b dependencies
$ git push origin dependencies

Now, go to the .github/workflows/dependabot.yml file and add target-branch: "dependencies". Here is how the file should look like:

version: 2
updates:
  # Enable version updates for Python
  - package-ecosystem: "pip"
    directory: "/"
    # Check for updates once a week
    schedule:
      interval: "weekly"
    target-branch: "dependencies"

That's it! The next time dependabot creates a new PR it'll be targeted to the new dependencies branch.

Updating Dependencies

Our goal is to merge all dependabot PRs into the dependencies branch. This will allow us to test if everything is fine with them before we push them to our default branch.

Here is how a typical PR from the dependencies branch would look like once we merge all of the dependabot PRs:

There are a couple of considerations that we need to take into account about our new dependencies branch:

Protect your new branch

We have a branch rule in our repository - the branches of all PRs that are merged are being deleted after that. We don't want this for the dependencies branch. We don't want to allow anybody to delete it at all.

We can prevent this from happening by adding a new branch protection rule.

Go to your GitHub repository settings and a new branch protection rule:

We also want to allow force pushes to your branch (it's disabled by default). This will let you fix the branch if there is a problem with the git history.

Keeping the dependencies branch up-to-date

Here is what we've achieved so far:

  1. Dependabot finds outdated dependencies every week.
  2. It creates a new PR for each of them.
  3. These PRs are targeted to the dependencies branch.
  4. We merge all dependabot PRs in the dependencies branch.
  5. The CI runs our test suite against the new dependencies.
  6. We can then do more thorough tests locally.
  7. Once we assert that everything is fine, we create a PR to our default branch.
  8. We merge dependencies into the default branch and we are good to go.

The problem

The problem here is at the 4th step.

We want dependencies to always be up to date with our default branch.

This is something that we can easily forget to do, since it needs to be done by hand.

Wouldn't it be nice if we can keep dependencies equal with the default branch without doing any extra manual actions?

GitHub Actions

Here is how we solved this. GitHub Actions is the tool that we're searching for!

Heads up 👀 We have additional GitHub Actions related articles - GitHub Actions in action - Setting up Django and Postgres and Optimize Django build to run faster on GitHub Actions

Create a new workflow

To set up a new GitHub action, we need to add a new .yml file (we name it rebase_dependencies.yml) to our .github directory. It should be located in the workflows folder.

your_repository/
|-- .github/
|   |-- workflows/
|   |   | -- rebase_dependencies.yml
|   |-- dependabot.yml

Here is the content of your file:

name: Automatic Rebase
on:
  push:
    branches:
      - master
jobs:
  rebase:
    name: Rebase `dependencies` with `master`
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
          ref: dependencies
      - run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git rebase origin/master
          git push origin dependencies --force

Here is what this configuration means in simple words:

  1. When there is a new commit pushed to our default branch.
  2. Checkout to the dependencies branch
  3. Rebase it with latest from our default branch.
  4. Push it to the origin. We need --force because of the rebase
It's fine if you prefer to use --force-with-lease to prevent from some unexpected outcomes.
If you don't want to use the built-in github-actions user, you can add a new PAT and configure the action to work with the user that's associated with it.

You can refer to the docs of the actions/checkout base action if you want to change it to fit your needs. Here are the docs of GitHub Actions as well.

To assert that everything is fine with our new workflow, we can go to the Actions tab in the GitHub Repository. The name of the workflow ("Automatic Rebase") should be visible in the left sidebar:

We should see a new workflow run on every new commit in the master branch.

Summary

In this article, we showed you how we use the powers of GitHub Actions and dependabot to keep the external dependencies of our projects up-to-date.

We hope we make your dev life better!

Stay tuned to our blog for more posts like this!

HackSoft
now
Wanna become a part of our team?
Click here to see our open positions