Sitemap

Let Bots Handle the Boring Stuff: Automating Dependency Updates with GitHub Actions & Dependabot

7 min readOct 21, 2025

--

How we turned hours of manual dependency babysitting into a hands-free automation flow.

The Problem: Mundane Reality of Multi-Repo Dependency Maintenance

Managing dependencies in a multi-repository architecture can get… messy. Especially when maintaining a bunch of internal packages that depend on each other in a long domino effect of releases, PRs, and version bumps across multiple repositories.

That was exactly our reality at Ergeon. Our team was spending an unreasonable amount of time every week babysitting dependency updates — waiting on CI pipelines, merging Dependabot PRs, bumping versions, releasing tags, and repeating the same routine across several repos. It wasn’t difficult work — just repetitive, boring, and expensive in terms of developer attention.

So, during one of our innovation days, I decided to do something about it.

Dependabot — GitHub’s dependency management tool — was already helping us a lot. It automatically checks for outdated dependencies and opens pull requests to update them. It’s great for managing both external and internal dependencies. But when you have a chain of internal dependencies, things get complicated.

To make this story easier to visualize, imagine a restaurant app ecosystem built as a set of interdependent repositories — each handling a different part of the business:

Press enter or click to view image in full size
The restaurant app dependency chain — changes flow from bottom to top through multiple layers.

At the foundation, we have the ingredient-catalog package that defines all available ingredients and their properties. On top of that, we have recipe-lib (which combines ingredients into recipes) and pricing-lib (which calculates costs based on ingredient prices). These feed into menu-builder, which organizes recipes into menus, and kitchen-system, which handles the operational side of the restaurant. Finally, our top-level restaurant-app brings together both the kitchen operations and menu management.

And honestly, thinking about this analogy — I kind of wish I was in the food industry. Free pizza samples? Worth it.

Now, imagine needing to update ingredient-catalog — maybe we’re adding nutritional information or introducing a new seasonal ingredient. That change needs to travel up through the entire dependency chain before it reaches restaurant-app. Manually, that looks like this:

1. Release `ingredient-catalog` with your changes
2. Trigger Dependabot in `recipe-lib` and wait till it opens the PR
3. Review, approve, and merge the Dependabot PR in `recipe-lib`
4. Release the new version of `recipe-lib` and wait till its CI/CD is done
5. Trigger Dependabot in `pricing-lib` and wait till it opens the PR
6. Review, approve, and merge the Dependabot PR in `pricing-lib`
7. Release the new version of `pricing-lib` and wait till its CI/CD is done
8. Trigger Dependabot in `menu-builder` and wait till it opens the PR
9. Review, approve, and merge the Dependabot PR in `menu-builder`
10. Release the new version of `menu-builder` and wait till its CI/CD is done
11. Trigger Dependabot in `kitchen-system` and wait till it opens the PR
12. Review, approve, and merge the Dependabot PR in `kitchen-system`
13. Release the new version of `kitchen-system` and wait till its CI/CD is done
14. Trigger Dependabot in `restaurant-app` and wait till it opens the PR
15. Review, approve, and merge the Dependabot PR in `restaurant-app`

This process can easily take 2–4 hours, largely due to waiting on CI/CD pipelines at nearly every step — with each build and test suite taking 10–15 minutes. It’s tedious and error-prone. Version mismatches can slip through, and the cognitive overhead of tracking which repositories are at which version is substantial. Clearly, this wasn’t sustainable.

The Solution: Dependabot + GitHub Actions

We needed a way to automate the entire dependency chain, so our team could focus on real development instead of babysitting PRs. While Dependabot opens update PRs, it doesn’t propagate changes through multiple internal packages automatically. That’s where GitHub Actions come in.

By combining Dependabot with custom workflows, we built a fully automated flow that takes a single dependency update and propagates it all the way up to the top-level application. Our approach revolves around two main workflows: Auto-Merge and Auto-Release.

Press enter or click to view image in full size
The automated flow: Dependabot opens PRs, Auto-Merge approves internal packages, and Auto-Release tags new versions in a continuous loop.

Auto-Merge

Previously, Dependabot supported auto-merge natively. However, after it became a GitHub-native feature, GitHub announced that auto-merge would not be supported for the foreseeable future. The reasoning is that automatically merging updates could potentially propagate a malicious package quickly across the ecosystem, so GitHub recommends always verifying dependencies before merging — which makes complete sense, especially for external packages.

Though, even when the native solution was available, it wouldn’t fully meet our needs.

In our case, we wanted to enable auto-merge and automate version bumping for internal packages only. While there’s still a minor risk of broken releases, the impact remains low due to the limited scope and controlled nature of our internal packages. That’s why we prioritized speed and flexibility over additional safeguards — and ultimately implemented our own Auto-Merge workflow.

Get Rami Yakoub’s stories in your inbox

Join Medium for free to get updates from this writer.

Our Auto-Merge workflow takes care of approving and merging internal dependency updates automatically. The workflow is triggered whenever Dependabot opens a pull request for an internal package. It first bumps the version of the package (all tests and lints automatically run via CI), and approves and merges the PR if everything passes.

By using GitHub’s workflow_call, we implemented the core Auto-Merge logic once in a root repository (utils-js) and then reused it across all internal packages. Each internal package calls this root workflow whenever Dependabot opens a PR. While the main workflow in utils-js is triggered via workflow_call, a typical repository configures a lightweight YAML that listens to pull request events and invokes the root workflow, like this:

name: Pizzeria Dependency Merge Trigger

on:
pull_request:
types: [opened, reopened]
branches: [develop]

jobs:
auto-merge:
uses: pizzeria/utils-js/.github/workflows/pizzeria-dependency-merge.yml@master
with:
skip_version_bump: true
secrets: inherit

This approach separates what happens during auto-merge (centralized in utils-js) from when it happens (triggered in each repo on Dependabot PRs), keeping the workflow DRY, easy to maintain, and fully automated across all internal packages.

Here’s an example of the main Auto-Merge workflow in utils-js:

name: Pizzeria Dependency Merge

on:
workflow_call:
inputs:
skip_version_bump:
description: "Skip version bumping for application repositories"
required: false
type: boolean
default: false

permissions: write-all

jobs:
auto-merge:
if: |
github.actor == 'dependabot[bot]' &&
contains(github.event.pull_request.title, 'Bump @pizzeria/')
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- name: Bump Version
if: ${{ !inputs.skip_version_bump }}
run: |
CHANGELOG_MESSAGE="${{ github.event.pull_request.title#*] }}"
make bump version="patch" message="$CHANGELOG_MESSAGE"
git push origin HEAD

- name: Generate GitHub App Token
id: app-token
uses: tibdex/github-app-token@v1
with:
app_id: ${{ secrets.PIZZERIA_DEPENDENCY_APP_ID }}
private_key: ${{ secrets.PIZZERIA_DEPENDENCY_APP_PRIVATE_KEY }}

- name: Approve and Merge
run: |
gh pr review ${{ github.event.number }} --approve --body "Auto-approved @pizzeria dependency update"
gh pr merge ${{ github.event.number }} --auto --squash
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}

For application repositories like restaurant-app, we set skip_version_bump to true because we don’t want to bump versions there; merging the PR is enough to trigger deployments.

Once Auto-Merge was in place, the next challenge was making sure new versions actually got released — that’s where Auto-Release comes in.

Auto-Release

Auto-Release ensures that once a dependency update is merged, a new version and Git tag are automatically created so that the next package in the chain sees the update. This keeps the entire flow moving without manual intervention.

Just like Auto-Merge, the core Auto-Release logic lives in a central workflow in utils-js — here’s an example workflow for tagging and releasing:

name: Pizzeria Tag Release

on:
workflow_call:

jobs:
auto-tag:
if: |
github.event.head_commit.author.name == 'dependabot[bot]' &&
contains(github.event.head_commit.message, 'Bump @pizzeria/')
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Create and Push Tag
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'

VERSION=$(node -p "require('./package.json').version")
TAG="v$VERSION"

if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists"
exit 0
fi

make tag

Each internal package triggers this workflow after a merge, releasing a new version that Dependabot then picks up in dependent packages — effectively closing the loop and automating the entire chain.

A lightweight trigger YAML in a package might look like this:

name: Pizzeria Tag Release Trigger

on:
push:
branches: [master]
paths: ["package.json", "package-lock.json"]

jobs:
auto-tag:
uses: pizzeria/utils-js/.github/workflows/pizzeria-tag-release.yml@master
secrets: inherit

With Auto-Merge and Auto-Release together, any change in a foundational package like ingredient-catalog now flows all the way to restaurant-app without a single manual step. Developers no longer need to track dozens of Dependabot PRs or worry about version mismatches — the system handles it reliably in the background. On top of that, we leveraged Dependabot’s grouping feature, which merges related updates together. This not only accelerates the propagation of changes but also keeps the number of automated PRs manageable, providing a cleaner and more predictable workflow.

Conclusion: Automate the Busywork, Reclaim Developer Time

What started as a small experiment to save a few hours a week quickly became one of the most impactful internal improvements we’ve made. Automating dependency updates wasn’t just about speeding up pipelines — it was about freeing developers from repetitive, low-value work and letting them focus on the parts of engineering that actually require human creativity.

By combining Dependabot with GitHub Actions, we built a system that keeps our repositories in sync, eliminates version drift, and ensures dependency updates propagate reliably from the bottom of the chain to the top — all without manual intervention.

This approach works best for trusted internal packages, while external dependencies still require careful review.

In short: if a task feels tedious but predictable — it’s a perfect candidate for automation. Invest once, and let the bots handle it forever. Your future self (and your team) will thank you for it.

How do you currently handle dependency updates in your multi-repo projects? What strategies have you found effective?

--

--

Rami Yakoub
Rami Yakoub

Written by Rami Yakoub

Software engineer documenting real-world technical solutions and hard-won lessons from production systems.