Keep Composer dependencies up-to-date with Dependabot

Published on

Nic WortelBy 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.

What is Dependabot?

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.

Enabling Dependabot

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.

Enabling version updates

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

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.

Keeping the amount of Dependabot PRs manageable

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.

Changing the frequency at which Dependabot checks for version updates

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.

Changing the maximum amount of open pull requests

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.

Grouping minor and patch version updates

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.

Using Dependabot for Symfony applications

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.

Grouping Symfony updates in a separate PR

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.

The extra.symfony.require constraint

If 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

Reducing the risk of supply-chain attacks with a cooldown period

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.

Updating dependencies from private registries

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 (SettingsSecrets and variablesDependabot).

Putting it all together

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.

Nic Wortel

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.

PHP consulting services