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:
- 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.
- 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?
- 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
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:
- A commit where the dependency is updated. For example - it might be a change in the
- 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:
- We can merge them.
- We can test them in isolation and merge after that.
- We can close them.
In this blog post, we'll tell you what's the process that we follow.
We'll go through the basic depentabot setup.
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-ecosystemif 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:
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:
- Breaking your default branch with untested dependency updates.
- Blocking releases because of that.
- Blocking other developers.
If we want to avoid that, we have a couple of options:
- Merge the dependabot pull requests locally on your machine. Install and test the new dependencies and push them to the default branch after that.
- Change the target branch of the dependabot PRs
We prefer the second option. Here is what we need to do next to accomplish that.
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
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
- If it's deleted from GitHub by mistake, dependabot will fail.
- It has to be up-to-date with your default branch when the PRs are opened. There might be some new code there, that can break things.
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:
- Dependabot finds outdated dependencies every week.
- It creates a new PR for each of them.
- These PRs are targeted to the
- We merge all dependabot PRs in the
- The CI runs our test suite against the new dependencies.
- We can then do more thorough tests locally.
- Once we assert that everything is fine, we create a PR to our default branch.
- We merge
dependenciesinto the default branch and we are good to go.
The problem here is at the 4th step.
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?
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
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 email@example.com git rebase origin/master git push origin dependencies --force
Here is what this configuration means in simple words:
- When there is a new commit pushed to our default branch.
- Checkout to the
- Rebase it with latest from our default branch.
- Push it to the origin. We need
--forcebecause of the
It's fine if you prefer to use
--force-with-leaseto prevent from some unexpected outcomes.
If you don't want to use the built-in
github-actionsuser, you can add a new PAT and configure the action to work with the user that's associated with it.
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
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!