Published on
By Nic Wortel
Using outdated dependencies increases technical debt and security risks. Learn how to automate Composer updates in your PHP applications using Dependabot.
In this guide
Modern PHP applications rely on open-source frameworks and a variety of third-party libraries to provide common functionality and speed up development, so that development effort can be focused on the unique domain of the application. However, many teams struggle to keep their dependencies up-to-date, as frameworks and libraries are constantly evolving and new versions are released frequently.
Teams who do not make a habit of keeping their dependencies up-to-date risk missing out on new features, improvements, and security patches, leading to technical debt and security vulnerabilities. Even teams who regularly scan their dependencies for vulnerabilities may miss unreported vulnerabilities in end-of-life versions, or may find themselves in a situation where they are too far behind to upgrade without significant effort.
In this guide, we will explore how Dependabot can help teams by automatically creating pull requests to update Composer dependencies, turning dependency management into a proactive and continuous process.
Dependabot is a tool that was originally developed as an independent service and was acquired by GitHub in 2019. It helps developers keep their dependencies up-to-date by automatically creating pull requests to update them across a wide range of supported ecosystems, including PHP and Composer. The core of Dependabot is open-source and can be run on different platforms (such as GitLab), although it is most commonly used as a GitHub-native service, where it works out of the box and is tightly integrated with other security features.
Dependabot distinguishes two types of updates: version updates and security updates. Version updates are scheduled (for example daily or weekly), while security updates are triggered when a vulnerability is reported in one of your application's dependencies.
Version updates and security updates are enabled in different ways and are decoupled from each other: you can enable version updates without security updates, or vice versa. To stay up-to-date and secure, the best approach is to enable both.
Version updates are enabled by pushing a YAML file named .github/dependabot.yml to the default branch of your GitHub
repository.
A minimal configuration (containing only the required options) for Composer might look like this:
version: 2 updates: - package-ecosystem: composer directory: / schedule: interval: daily
This configures Dependabot to check Composer manifest files (composer.json and composer.lock) in the root directory
every weekday (Monday - Friday) and to create a pull request for each package that can be updated.
Once committed and pushed, Dependabot will automatically start creating pull requests for outdated Composer packages.
Enabling security updates works a little bit differently, and requires admin rights on the repository. Go to the repository's Settings tab, and in the side menu go to Advanced Security. On that page, enable Dependabot security updates (this will enable Dependency graph and Dependabot alerts as well, as security updates depend on those features).
Note that once enabled, some behavior of security updates can still be configured through dependabot.yml.
The minimal configuration shown above has enabled Dependabot version updates and pull requests should start coming in. However, the amount of pull requests can quickly take up a considerable amount of time to review and merge, especially for larger applications with many dependencies.
Luckily, you can tune the configuration to better suit your development process and to keep the number of pull requests manageable: how often Dependabot runs, how many pull requests it opens at once, and how it groups related updates together.
Instead of daily, we can configure Dependabot to check for version updates less often, for example weekly (by
default on Monday) or monthly (on the first day of the month):
version: 2 updates: - package-ecosystem: composer directory: / schedule: interval: weekly
Note that the schedule option only applies to scheduled version updates. Security updates (triggered when a
vulnerability is reported in one of your dependencies) will still arrive as soon as they're available.
If you want, you can further control Dependabot's schedule by specifying the day of the week and/or the time of the day. For example, if you want to receive PRs on Fridays at 13:00 in your local timezone:
version: 2 updates: - package-ecosystem: composer directory: / schedule: interval: weekly day: friday time: '13:00' timezone: Europe/Amsterdam
If what you're trying to accomplish is more complex than what a daily/weekly/monthly schedule allows, there is also a
cron interval which allows you to specify a cronjob expression.
By default, Dependabot opens no more than 5 pull requests at a time (for version updates) to not overwhelm you.
This limit can be changed to a lower or higher limit by configuring the open-pull-requests-limit option:
version: 2 updates: - package-ecosystem: composer directory: / schedule: interval: weekly open-pull-requests-limit: 3
This limit only applies to version updates. Security updates have a fixed limit of 10, which can't be changed.
With a less frequent schedule Dependabot will run less often, but when it runs there might be several dependencies
with available updates.
By default, Dependabot will create a separate PR for each of them, which can create a sudden influx of PRs which have to
be approved and merged.
By specifying groups we can tell Dependabot to group multiple updates together in a single PR.
A good approach is to group minor and patch updates together in a single PR, while keeping major version updates (which
often introduce backward-incompatible changes) in separate PRs:
version: 2 updates: - package-ecosystem: composer directory: / schedule: interval: weekly groups: minor-updates: update-types: - minor - patch
Note: each group applies to either version updates or security updates, not both.
By default, a group applies to version updates; add applies-to: security-updates to a group to group security updates
instead.
This section only applies to Symfony applications. If you're not using Symfony, you can safely skip it.
Symfony is released as a set of components (symfony/http-kernel, symfony/framework-bundle,
symfony/runtime, etc.) which share the same version number and are meant to be upgraded together.
This nuance has some impact on how Dependabot should be configured for Symfony projects.
Our existing configuration groups all minor and patch version Composer updates in a single PR, and creates separate PRs for major versions. This means that minor and patch Symfony updates can be mixed with other library updates, while major Symfony updates will create a bunch of PRs which will fail because the components require matching versions of each other.
The solution is to create a new group for Symfony upgrades, which also applies to major version updates, and has to be specified before the existing group:
version: 2 updates: - package-ecosystem: composer directory: / schedule: interval: weekly groups: symfony: patterns: - "symfony/*" minor-updates: update-types: - minor - patch
This also makes it easier to test Symfony updates and resolve deprecations separately from other library updates.
extra.symfony.require constraintIf you use Symfony Flex, your composer.json most likely contains a setting which
pins all Symfony components to a single minor version:
"extra": { "symfony": { "require": "8.1.*" } }
This ensures that all Symfony components are restricted to that version range, even transitive dependencies
(dependencies not required by your root composer.json but by other dependencies).
Unfortunately Flex is not executed during Dependabot runs, so the
extra.symfony.require constraint is ignored by Dependabot
and Dependabot can suddenly upgrade a Symfony component to a new major version as part of an update of a third-party
library.
Dependabot will also not update this value when proposing a major version upgrade for Symfony.
As a workaround for this limitation, you can mark your application as incompatible with newer versions of each Symfony
component by listing them under the conflict field of your
composer.json.
As a native Composer feature, this will be respected by Dependabot and will prevent Dependabot from proposing updates to
those packages.
As a convenient alternative to maintaining the conflict block by hand, there is a community-maintained Symfony LTS
Helper which contains all those conflicts and can be used as a meta-package:
composer require gared/symfony-lts:^7.4
By default, Dependabot proposes an update on its first run after a new version is published. But updating immediately after a new release appears increases the risk of pulling in a compromised version (for example containing a malicious contribution or pushed by a hijacked maintainer account).
A cooldown period reduces that risk by letting Dependabot wait a number of days before opening a PR for a new version.
That gives maintainers, the community and security tooling time to spot a bad release and yank it before it reaches your
application.
A cooldown period of 7 days is a good start:
version: 2 updates: - package-ecosystem: composer directory: / schedule: interval: weekly cooldown: default-days: 7
The cooldown option only applies to version updates and not to security updates, so it does not delay a fix for a
known vulnerability.
The delay can be further tweaked with additional options which allow you to specify different cooldown periods per
update type (major/minor/patch) and by including/excluding specific packages.
See the cooldown reference
for a full overview of available options.
If your project depends on packages from a private repository (Private Packagist, Satis, etc.), Dependabot needs
credentials to check for available updates.
Declare the source under a top-level registries key and reference it from the ecosystem config:
version: 2 registries: private-packagist: type: composer-repository url: https://repo.packagist.com/example-company/ username: token password: ${{secrets.PRIVATE_PACKAGIST_AUTH_TOKEN}} updates: - package-ecosystem: composer directory: / registries: - private-packagist schedule: interval: weekly
Store the token as a Dependabot secret in your GitHub repository or organization (Settings → Secrets and variables → Dependabot).
With Dependabot enabled, dependency updates arrive as a steady stream of pull requests instead of a periodic chore you have to remember. Combining the options from this guide gives a configuration that keeps that stream manageable:
version: 2 updates: - package-ecosystem: composer directory: / schedule: interval: weekly open-pull-requests-limit: 10 cooldown: default-days: 7 groups: symfony: # Only for Symfony applications patterns: - "symfony/*" minor-updates: update-types: - minor - patch
Make sure your test suite runs on these pull requests, so a breaking update is caught before you merge it. From there you review and merge them like any other change, and with Dependabot security updates enabled, vulnerability fixes still arrive the moment they're published.
Are you looking to implement PHP, Composer, Dependabot, or GitHub in your project or team? As a PHP consultant I can save you time and money by guiding you through the process and helping you avoid costly mistakes.